• 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;
    }
    
  • 相关阅读:
    学习java随笔第五篇:流程控制
    学习java随笔第四篇:运算符
    学习java随笔第三篇:java的基本数据类型
    知识精简
    性能优化(详细)
    2,8,10,16进制转换
    前端优化35例
    性能优化
    字面量自定义事件

  • 原文地址:https://www.cnblogs.com/rainair/p/14305853.html
Copyright © 2020-2023  润新知