zoukankan      html  css  js  c++  java
  • CF 582 题解

    A

    手玩一下样例,我们从大到小确定每个数:将确定的数挪到序列的最前面,设确定了 (x) 个数,那么 (x imes x) 的方格的数都没用了,并且剩下的是一个 L 形方格。剩下的 L 形方格内对角线一定是最大值,所以我们只需要每次取最大值,将这个数和已经确定的数的 (gcd) 删掉即可。注意要删两次。

    B

    可以猜测一下选择方案一定是前面可能有某些增加值的地方,后面值就一直不变了。所以我们先一直跑 dp,跑到快要 T 的时候就相当于要从剩下的块中选出一些数字。由于我们可以将这个块添加到任意位置,所以选最大出现次数的数字即可。

    可以证明其实只需要跑 (n) 块即可。。因为你最坏情况是每一块增加一次。这一块不增加后面完全没有必要增加。

    C

    一个经典结论是设长度为 (s),设 (g = gcd(s,n)),那么新的序列的第 (i) 位会对应原序列的 (i,i+g,i+2g,ldots i+kg) 位。所以我们复制一遍序列,枚举 (g|n),将下标按照 (mod g) 分类,一个点合法当前仅当它是和它一组中的最大值,一个方案合法当且仅当区间内所有点合法,倒着扫一遍维护一个指针,差分维护前缀加,求出所有长度的区间的方案数,判断 (gcd) 即可解决。

    注意这里需要预处理一下 (gcd(i,n)),要不然会 T。。时间还是很紧的。

    D

    (v_p(x)) 表示 (x) 的唯一分解中 (p) 上面的指数。

    首先我们有 (v_p(n!) = sum_{i geq 1} lfloor frac{n}{p^i} floor),并且还有 (v_p(ab) = v_p(a)+v_p(b)),如果考虑将 (n) 分解成 (p) 进制下的数,那么 (v_p(n!)) 的每一位贡献都是将比这一位低的位都去掉的数的和。

    那么我们知道 (v_p(inom n m) = v_p(n!)-v_p(m!)-v_p((n-m)!)),我们考虑 (m+(n-m) = n) 这个加法运算,如果在第 (i) 位它进位了,那么在前 (i) 位贡献不变,在第 (i+1) 位答案会多 (1)。所以 (v_p(inom n m)) 就是 (n-m)(m)(p) 进制下加法的进位次数。

    这个好像叫做库默尔定理?

    然后我们可以考虑设计一个数位dp:我们在第 (i) 位需要知道它后面那一位是否进位才可以确定限制等因素,设 (f_{i,j,0/1,0/1}) 从高到低考虑前 (i) 位,进了 (j) 次位,当前的和是否卡上界,下一位是否进位,转移细节可以看代码(我注释写的很详细了)。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 3500+5;
    const int ha = 1e9 + 7;
    const int inv2 = (ha+1)>>1;
    int p,_,n;
    char str[MAXN];
    int b[MAXN],a[MAXN];
    int f[2][MAXN][2][2],now;
    // 考虑前i位 当前进位了j次 现在是否卡上界 下一位是否进位
    
    inline void add(int &x,int y){
    	x += y;if(x >= ha) x -= ha;
    }
    
    inline int calc(int n){ // sum of [x <= b,y <= n,x+y <= n]
    	if(n < 0) return 0;
    	if(n == 0) return 1;
    	return (1ll*(n+1)*(n+1)%ha+ha-1ll*n*(n+1)%ha*inv2%ha)%ha;
    }
    
    inline int calc2(int n){ // sum of [x <= p-1,y <= p-1,x+y <= n]
    	// p <= n+1-i => i <= n+1-p
    	int res = 0;
    	if(n+1-p >= 0) add(res,1ll*(n+1-p+1)%ha*p%ha);
    	// p > n+1-i => i >= n+2-p
    	if(n+2-p <= p-1){
    		// p-1 +p-2 + ....
    		int len = (p-1)-(n+2-p)+1;
    		add(res,1ll*(p-1)*p%ha*inv2%ha);
    		len = (p-1)-len;
    		if(len > 0) add(res,ha-1ll*(len+1)*len%ha*inv2%ha);
    	}
    	return res;
    }
    
    int main(){
    	scanf("%d%d",&p,&_);// 进制转换
    	scanf("%s",str+1);int len = strlen(str+1);
    	FOR(i,1,len) a[i] = str[i]-'0';
    	std::reverse(a+1,a+len+1);
    	while(len){
    		int t = 0;
    		ROF(i,len,1) t = (10ll*t+a[i]) % p;
    		b[++n] = t;
    		a[1] -= t;
    		FOR(i,1,len){
    			if(a[i] < 0){
    				int tt = (-a[i])/10;
    				if(a[i]+10*tt < 0) ++tt;
    				a[i] += 10*tt;
    				a[i+1] -= tt;
    			}
    		}
    		while(len && !a[len]) --len;
    		LL r = 0; // up to O(p^2)
    		ROF(i,len,1){
    			r = 10*r+a[i];
    			a[i] = r/p;
    			r %= p;
    		}
    		while(len && !a[len]) --len;
    	}
    	std::reverse(b+1,b+n+1);
    	f[now=0][0][1][0] = 1;
    	// int tt = 0;FOR(x,0,p-1) FOR(y,0,p-1) tt += (x+y <= 3);
    	// DEBUG(tt);DEBUG(calc2(3));
    	// DEBUG(calc2(3)-calc2(2));
    	// exit(0);
    	FOR(i,1,n){
    		// 打破限制 <=> (x+y)%p < b[i]
    		// 有进位  <=>  x+y>=p
    		// 无进位 <=> x+y <= p-1
    		// 卡上界 <=> (x+y)%p = b[i]
    		// +1 <=> 这一位的值 +1=
    		// 这一位有限制 上一位必然有限制
    		// (x+1)^2-x*(x+1)/2
    		CLR(f[now^1],0);
    		FOR(j,0,i){// 设两个数分别为x,y
    			int t = 0;
    			int c1 = calc(p-1);
    			int c2 = calc(p-2);
    			int c3 = calc(b[i]-1);
    			int c4 = (calc2(p+b[i]-1)+ha-calc2(p-1))%ha;
    
    			add(f[now^1][j][0][0],1ll*f[now][j][0][0]*(c1/*不进位*/)%ha);
    			add(f[now^1][j][0][0],1ll*f[now][j][0][1]*(c2/*有进位*/)%ha);
    			add(f[now^1][j][0][0],1ll*f[now][j][1][0]*(c3/*打破限制 不进位*/)%ha);
    			add(f[now^1][j][0][0],1ll*f[now][j][1][1]*(c4/*打破限制 有进位*/)%ha);
    			
    			c1 = calc(p-2);
    			c2 = calc(p-2);add(c2,p);
    			c3 = calc(b[i]-2);
    			c4 = (calc2(std::min(2*p-2,b[i]+p-2))+ha-calc2(p-2))%ha;
    			
    			add(f[now^1][j+1][0][1],1ll*f[now][j][0][0]*(c1/*不进位 +1*/)%ha);
    			add(f[now^1][j+1][0][1],1ll*f[now][j][0][1]*(c2/*有进位 +1*/)%ha);
    			add(f[now^1][j+1][0][1],1ll*f[now][j][1][0]*(c3/*打破限制 不进位 +1*/)%ha);
    			add(f[now^1][j+1][0][1],1ll*f[now][j][1][1]*(c4/*打破限制 有进位 +1*/)%ha);
    		
    			c3 = b[i]+1;
    			c4 = std::min(b[i]+p,p-1)-std::max(1+b[i],0)+1;
    			c4 = std::max(c4,0);
    			// add(f[now^1][j][1][0],f[now][j][0][0]*(/*不存在*/));
    			// add(f[now^1][j][1][0],f[now][j][0][1]*(/*不存在*/));
    			add(f[now^1][j][1][0],1ll*f[now][j][1][0]*(c3/*卡上界 不进位*/)%ha);
    			add(f[now^1][j][1][0],1ll*f[now][j][1][1]*(c4/*卡上界 有进位*/)%ha);
    			
    			c3 = b[i]-1+1;
    			c4 = std::min(b[i]+p-1,p-1)-std::max(1-p+b[i]+p-1,0)+1;
    			// add(f[now^1][j+1][1][1],f[now][j][0][0]*(/*不存在*/));
    			// add(f[now^1][j+1][1][1],f[now][j][0][1]*(/*不存在*/));
    			add(f[now^1][j+1][1][1],1ll*f[now][j][1][0]*(c3/*卡上界 不进位 +1*/)%ha);
    			add(f[now^1][j+1][1][1],1ll*f[now][j][1][1]*(c4/*卡上界 有进位 +1*/)%ha);
    		}
    		now ^= 1;
    	}
    	int ans = 0;
    	FOR(i,_,n) add(ans,f[now][i][0][0]),add(ans,f[now][i][1][0]);
    	printf("%d
    ",ans);
    	return 0;
    }
    

    E

    首先表达式问题肯定不能做,要建表达式树。

    建数每次扫出一个完整的括号就在这个地方划分就好了(详情请看代码)。

    发现只有 (4) 个变量也就是 (16) 种取值,我们相当于限制了在多个变量取值情况的总取值,所以我们考虑将所有可能取值的总取值都压缩起来:设 (f_{i,S}) 表示 (i) 子树内,变量取值为 (1) 的集合 (T in S) 的时候总取值都为 (1),其余都为 (0) 的方案数。换言之 (S) 是字母取值集合的集合(所以大小 (2^{16}))。

    在底层的时候如果是确定的点就枚举所有的 (v in T),将这些 (T) 组成的集合 (S)(f_{v,S} = 1)。如果不确定的话就把所有可能枚举一遍。这里要预处理所有类型的点而不是每次暴力要不然时间可能不太行。

    然后合并的时候相当于是一个集合并卷积或者集合交卷积,FWT即可。

    这种将所有可行的取值的状态压起来的状压还是要多想。。好好复习一下。

    #include <bits/stdc++.h>
    
    #define fi first
    #define se second
    #define db double
    #define U unsigned
    #define P std::pair<int,int>
    #define LL long long
    #define pb push_back
    #define MP std::make_pair
    #define all(x) x.begin(),x.end()
    #define CLR(i,a) memset(i,a,sizeof(i))
    #define FOR(i,a,b) for(int i = a;i <= b;++i)
    #define ROF(i,a,b) for(int i = a;i >= b;--i)
    #define DEBUG(x) std::cerr << #x << '=' << x << std::endl
    
    const int MAXN = 500+5;
    const int MAXM = 16;
    const int ha = 1e9 + 7;
    
    inline void add(int &x,int y){
    	x += y;if(x >= ha) x -= ha;
    }
    
    int ch[MAXN][2],tot;
    char a[MAXN],str[MAXN];
    int rt;
    
    inline int work(int l,int r){
    	if(l > r) return 0;
    	if(l == r){
    		a[++tot] = str[l];
    		return tot;
    	}
    	int sm = 0,ps = 0;
    	FOR(i,l,r){
    		if(str[i] == '(') ++sm;
    		if(str[i] == ')') --sm;
    		if(sm == 0){
    			ps = i;break;
    		}
    	}
    	if(ps == r) return work(l+1,r-1);
    	int x = ++tot;
    	a[x] = str[ps+1];
    	ch[x][0] = work(l+1,ps-1);
    	ch[x][1] = work(ps+3,r-1);
    	return x;
    }
    
    int f[MAXN][(1<<16)+3];
    int g[9][(1<<16)+3];
    // 子树v内, 四个字母每种可能的取值 T 对应的表达式取值集合是S,方案数
    
    inline int ctoi(char c){
    	if(c >= 'A' && c <= 'D') return c-'A';
    	if(c >= 'a' && c <= 'd') return c-'a';
    	return -1;
    }
    
    int N = (1<<16);
    
    inline void OR(int f[]){for(int mid = 1;mid < N;mid <<= 1) for(int i = 0;i < N;i += (mid<<1)) FOR(j,0,mid-1) add(f[i+mid+j],f[i+j]);}
    inline void iOR(int f[]){for(int mid = N>>1;mid;mid >>= 1) for(int i = 0;i < N;i += (mid<<1)) FOR(j,0,mid-1) add(f[i+mid+j],ha-f[i+j]);}
    inline void AND(int f[]){for(int mid = 1;mid < N;mid <<= 1) for(int i = 0;i < N;i += (mid<<1)) FOR(j,0,mid-1) add(f[i+j],f[i+mid+j]);}
    inline void iAND(int f[]){for(int mid = N>>1;mid;mid >>= 1) for(int i = 0;i < N;i += (mid<<1)) FOR(j,0,mid-1) add(f[i+j],ha-f[i+mid+j]);}
    inline void COR(int A[],int B[],int C[]){OR(A);OR(B);FOR(i,0,N-1) C[i] = 1ll*A[i]*B[i]%ha;iOR(C);iOR(A);iOR(B);}
    inline void CAND(int A[],int B[],int C[]){AND(A);AND(B);FOR(i,0,N-1) C[i] = 1ll*A[i]*B[i]%ha;iAND(C);iAND(A);iAND(B);}
    
    int tmp[(1<<16)+5];
    
    inline void dfs(int v){
    	if(!ch[v][0] && !ch[v][1]){
    		int id = ctoi(a[v])+(4*(a[v] >= 'a' && a[v] <= 'd'));
    		if(id == -1) id = 8;
    		FOR(S,0,(1<<16)-1) f[v][S] = g[id][S];
    		return;
    	}
    	dfs(ch[v][0]);dfs(ch[v][1]);
    	if(a[v] == '&') return CAND(f[ch[v][0]],f[ch[v][1]],f[v]);
    	if(a[v] == '|') return COR(f[ch[v][0]],f[ch[v][1]],f[v]);
    	CAND(f[ch[v][0]],f[ch[v][1]],tmp);
    	COR(f[ch[v][0]],f[ch[v][1]],f[v]);
    	FOR(S,0,N-1) add(f[v][S],tmp[S]);
    }
    
    int main(){
    	FOR(i,0,3){
    		int t = 0;
    		FOR(S,0,(1<<4)-1) t |= (((S>>i)&1)<<S);
    		++g[i][t];++g[i+4][t^((1<<16)-1)];
    		++g[8][t];++g[8][t^((1<<16)-1)];
    	}
    	scanf("%s",str+1);int len = strlen(str+1);
    	rt = work(1,len);
    	dfs(rt);
    	int n;scanf("%d",&n);
    	int t0=0,t1=0;
    	FOR(i,1,n){
    		int a,b,c,d,e;scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
    		int t = a|(b<<1)|(c<<2)|(d<<3);
    		if(e) t1 |= (1<<t);
    		else t0 |= (1<<t);
    	}
    	int ans = 0;
    	FOR(S,0,(1<<16)-1) if(((S&t1) == t1) && ((S&t0) == 0)) add(ans,f[rt][S]);
    	printf("%d
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    面向对象的继承关系体现在数据结构上时,如何表示
    codeforces 584C Marina and Vasya
    codeforces 602A Two Bases
    LA 4329 PingPong
    codeforces 584B Kolya and Tanya
    codeforces 584A Olesya and Rodion
    codeforces 583B Robot's Task
    codeforces 583A Asphalting Roads
    codeforces 581C Developing Skills
    codeforces 581A Vasya the Hipster
  • 原文地址:https://www.cnblogs.com/rainair/p/14305853.html
Copyright © 2011-2022 走看看