Problem A 树状数组
Solution :
上来就做如此恶心的数位$DP$,不过也好,复习了一下数位$DP$。
观察到$f(l,r)$答案的构成是$l$的二进制个数+$r$的二进制个数 - $l,r$二进制表示形成字符串的lcp的$1$的个数。
对$n$二进制拆分,从高位到低位依次考虑,设$f[i][op1][op2][j]$当前考虑到第$i$位,当前$r$对$n$是否有限制(op1),当前$l$是否对$r$有限制(op2)。
而$j$这一维状态,分两次$dp$考虑。
第一次,我们要求出所有数对$(0 \leq l< r \leq n)$中两个数的二进制$1$的个数的和。
所以,容易的设$j$为更高位$1$的数目,而整个dp的值表示方案数,总和就是方案数$\times j$的和
第二次,我们要求出所有数对$(0 \leq l< r \leq n)$中两个数二进制串的$lcp$的$1$的个数
所以,容易的设$j$为更高位公共$1$的个数,同时整个$dp$的值表示方案数,总和就是方案数$\times j$的和
按照普通的数位$dp$转移即可,需要注意一些细节,这里就不再赘述一些沙雕错误了,代码用了循环展开,可读性极差。
复杂度是$O(T {log_2}^2 n)$
# pragma GCC optimize(3,"Ofast") # include<bits/stdc++.h> # define Rint register int using namespace std; const int mo=1e9+7; long long n; int a[105]; int f[64][2][2][2*64]; inline void pls(int &a, int b) { a = (a + b >= mo ? a + b - mo : a + b); } signed main(){ int T; scanf("%d",&T); while (T--) { scanf("%lld",&n);a[0]=0; while (n) { a[++a[0]]=n&1; n>>=1;} f[a[0]][0][1][0]=1, f[a[0]][1][0][1]=1, f[a[0]][1][1][2]=1; int HJCAK = a[0] << 1; for (Rint i=a[0];i>=2;i--) for (Rint op1=0;op1<=1;op1++){ Rint op2 = 0; for (Rint j=0;j<=HJCAK;j++) { pls(f[i-1][(op1)&&!a[i-1]][op2][j],f[i][op1][op2][j]); if (op1 && a[i-1]) pls(f[i-1][1][op2][j+2],f[i][op1][op2][j]); if (!op1) pls(f[i-1][0][op2][j+2],f[i][op1][op2][j]); if (!op2) pls(f[i-1][op1&&!a[i-1]][0][j+1],f[i][op1][op2][j]); if (op1 && a[i-1]) pls(f[i-1][1][0][j+1],f[i][op1][op2][j]); if (!op1) pls(f[i-1][0][0][j+1],f[i][op1][op2][j]); } op2 = 1; for (Rint j=0;j<=HJCAK;j++) { pls(f[i-1][(op1)&&!a[i-1]][op2][j],f[i][op1][op2][j]); if (op1 && a[i-1]) pls(f[i-1][1][op2][j+2],f[i][op1][op2][j]); if (!op1) pls(f[i-1][0][op2][j+2],f[i][op1][op2][j]); if (!op2) pls(f[i-1][op1&&!a[i-1]][0][j+1],f[i][op1][op2][j]); if (op1 && a[i-1]) pls(f[i-1][1][0][j+1],f[i][op1][op2][j]); if (!op1) pls(f[i-1][0][0][j+1],f[i][op1][op2][j]); } } int res1=0; for (Rint op1=0;op1<=1;op1++) for (Rint j=1;j<=HJCAK;j++) pls(res1,1ll*f[1][op1][0][j]*j%mo); memset(f,0,sizeof(f)); f[a[0]][0][1][0]=1, f[a[0]][1][0][0]=1, f[a[0]][1][1][1]=1; for (Rint i=a[0];i>=2;i--) for (Rint op1=0;op1<=1;op1++){ Rint op2 = 0; for (Rint j=0;j<=a[0];j++) { pls(f[i-1][op1&&!a[i-1]][op2][j],f[i][op1][op2][j]); if (op1 && a[i-1]) pls(f[i-1][1][op2][j+op2],f[i][op1][op2][j]); if (!op1) pls(f[i-1][0][op2][j+op2],f[i][op1][op2][j]); if (!op2) pls(f[i-1][op1&&!a[i-1]][0][j],f[i][op1][op2][j]); if (op1 && a[i-1]) pls(f[i-1][1][0][j],f[i][op1][op2][j]); if (!op1) pls(f[i-1][0][0][j],f[i][op1][op2][j]); } op2 = 1; for (Rint j=0;j<=a[0];j++) { pls(f[i-1][op1&&!a[i-1]][op2][j],f[i][op1][op2][j]); if (op1 && a[i-1]) pls(f[i-1][1][op2][j+op2],f[i][op1][op2][j]); if (!op1) pls(f[i-1][0][op2][j+op2],f[i][op1][op2][j]); if (!op2) pls(f[i-1][op1&&!a[i-1]][0][j],f[i][op1][op2][j]); if (op1 && a[i-1]) pls(f[i-1][1][0][j],f[i][op1][op2][j]); if (!op1) pls(f[i-1][0][0][j],f[i][op1][op2][j]); } } int res2=0; for (Rint op1=0;op1<=1;op1++) for (Rint j=1;j<=a[0];j++) pls(res2,1ll*f[1][op1][0][j]*j%mo); res2 <<= 1; res2 >= mo && (res2 -= mo); printf("%d\n",(res1-res2+mo)%mo); if(T) memset(f,0,sizeof(f)); } return 0; }
Problem B 雇佣妹抖
Solution :
这道题目让我们想到了氨基酸和肽链。肽链数 $=$ 氨基酸数 $-$ 肽键数。(过于形象引起不适~)
考虑答案是如何构成的,大于等于$b$的个数,减去相邻两个都大于等于$b$的个数。
可以将单点和相邻数的$min$分开维护,离散化以后用树状数组或者直接写平衡树都行。
时间复杂度为$O(mlog_2n)$
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int maxn = 400010; int n, qq, B, c, d, d1, T, num; int a[maxn], b[maxn], t[maxn], tr[maxn], q[maxn][3]; int lowbit(int x) { return x & (-x); } void add1(int x, int y) { for (int i = x; i <= num; i += lowbit(i)) t[i] += y; } void add2(int x, int y) { for (int i = x; i <= num; i += lowbit(i)) tr[i] += y; } int find1(int x) { int ans = 0; for (int i = x; i >= 1; i -= lowbit(i)) ans += t[i]; return ans; } int find2(int x) { int ans = 0; for (int i = x; i >= 1; i -= lowbit(i)) ans += tr[i]; return ans; } int main() { scanf("%d%d", &n, &qq); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); b[i] = a[i]; } int cnt = n; for (int i = 1; i <= qq; i++) { scanf("%d", &T); if (T == 1) scanf("%d", &q[i][1]); else { scanf("%d%d", &q[i][0], &q[i][1]); } b[++cnt] = q[i][1]; } sort(b + 1, b + cnt + 1); num = unique(b + 1, b + cnt + 1) - b - 1; for (int i = 1; i <= n; i++) { a[i] = lower_bound(b + 1, b + num + 1, a[i]) - b; add1(a[i], 1); } for (int i = 1; i < n; i++) add2(min(a[i], a[i + 1]), 1); for (int i = 1; i <= qq; i++) { if (q[i][0] == 0) { int B1 = lower_bound(b + 1, b + 1 + num, q[i][1]) - b; int ans = n - find1(B1 - 1) - (n - 1 - find2(B1 - 1)); printf("%d\n", ans); } else { c = q[i][0]; d = lower_bound(b + 1, b + num + 1, q[i][1]) - b; add1(a[c], -1); if (c > 1) add2(min(a[c], a[c - 1]), -1), add2(min(d, a[c - 1]), 1); if (c < n) add2(min(a[c], a[c + 1]), -1), add2(min(d, a[c + 1]), 1); a[c] = d; add1(a[c], 1); } } return 0; }
Problem C 建造
Solution :
显然不能直接在数轴上直接$DP$,任意的限制只要按照排列顺序就可以转化为相邻的限制。
设$d_1 = pos_1 - 1, d_i = pos_i - pos_{i-1} (i\geq 2)$
于是限制可以转化为$d_i \geq max(h_i , h_{i-1})$且$\sum\limits_{i=1}^{n} d_i + tmp = X$,
且$d_1,...,d_n , tmp$都是非负整数。
如果知道$S = \sum\limits_{i=2}^{n} max(h_{i-1},h_{i})$, 那么容易使用插板法求出合法答案数目$\binom{X-1-s+n}{n}$
现在只要求出排列$n$个元素,获得$S$的方案数即可,答案就是方案数乘以组合数即可。
设$f[i][j][k]$表示从低到高考虑到第$i$个建筑,当前已知的$S$的值至少是$j$,还有$k$个空位来插入更大的值。
考虑定义$f[1][0][0] = 1$,刷表法求出所有答案。
考虑将$i+1$放在已知序列的首位,考虑两种可能,
- 首位和次位之间之间还要插入更大的数。
- 首位和次位之后不会插入数。
考虑将$i+1$放在已知序列的末尾,考虑两种可能。
- 次末尾和末尾之间还要插入更大的数。
- 次末尾和末尾值之间不会插入更大的数。
考虑将$i+1$放在一般位置,考虑三种可能。
- 前面和后面两个位置都会插入更大的数。
- 前面和后面都不会插入更大的数。
- 前面和后面其中只有1个位置会插入更大的数。
时间复杂度就是$O(n^4+X)$,需要使用滚动数组+卡常$AC$本题。
# pragma GCC optimize(3,"Ofast") # include <bits/stdc++.h> # define Rint register int # define int long long using namespace std; const int N=1e6+10; const int M=105; int n,m,mo; int fac[N],inv[N],f[2][M*M][M]; inline int Pow(int x,int n,int mo) { int ans = 1; while (n) { if (n&1) ans=ans*x%mo; x=x*x%mo; n>>=1; } return ans % mo; } inline int C(int n,int m) { return (m>n)?(0):(fac[n]*inv[m]%mo*inv[n-m]%mo); } inline void pls(int &a, int b) { a = (a + b >= mo ? a + b - mo : a + b); } signed main() { cin>>n>>m>>mo; fac[0]=1; for (Rint i=1;i<=n+m;i++) fac[i]=fac[i-1]*i%mo; inv[0]=1; for (Rint i=1;i<=n+m;i++) inv[i]=Pow(fac[i],mo-2,mo); int p; f[p=1][0][0]=1; for (Rint i=1;i<n;i++) { for (Rint j=0;j<=n*n;j++) for (Rint k=0;k<=i;k++) { pls(f[p^1][j][k+1],f[p][j][k]); pls(f[p^1][j+i+1][k],f[p][j][k]); pls(f[p^1][j][k+1],f[p][j][k]); pls(f[p^1][j+i+1][k],f[p][j][k]); if (k!=0) pls(f[p^1][j+2*(i+1)][k-1],f[p][j][k]*k%mo); pls(f[p^1][j][k+1],f[p][j][k]*k%mo); pls(f[p^1][j+i+1][k],2*k*f[p][j][k]%mo); } memset(f[p],0,sizeof(f[p])); p^=1; } int ans = 0; for (Rint i=0;i<=n*n;i++) { pls(ans,f[p][i][0]*C(m-1-i+n,n)%mo); } cout<<ans<<'\n'; return 0; }