A. Exercising Walk
题意:给一个矩阵和矩阵内的一个点,以该点为起点在矩阵内上下左右移动,问能否实现上行a步下行b步左行c步右行d步。
思路:左右和上下移动没有本质区别,以上下移动为例。如果矩阵退化为了一条线,并且a,b其中一个不为0,那么一定无法实现。我们不关心ab具体数值而只关心他们的差值,因为可以上一步下一步一直走。如果走对应差值的步数后出了边界就一定不合法,否则就是合法的。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 using namespace std; 17 int T,a,b,c,d; 18 int x,y,x1,y_1,x2,y2; 19 bool check_x(){ 20 return a-b <= x-x1 && b-a <= x2-x; 21 } 22 bool check_y(){ 23 return c-d <= y-y_1 && d-c <= y2-y; 24 } 25 int main(){ 26 // freopen("in.txt","r",stdin); 27 rd(T); 28 while(T--){ 29 rd(a);rd(b);rd(c);rd(d); 30 rd(x);rd(y);rd(x1);rd(y_1);rd(x2);rd(y2); 31 if(x1 == x2 && a+b != 0)printf("No "); 32 else if(y_1 == y2 && c+d != 0)printf("No "); 33 else if(check_x() && check_y())printf("Yes "); 34 else printf("No "); 35 } 36 return 0; 37 } 38 /**/
B. Composite Coloring
题意:n(1e4)个合数ai(1e3),每种数涂上一个颜色,使得同一颜色的两个数之间gcd > 1,总颜色数不能超过11。
思路:可以发现第11个素数是31。而1000以内的合数不可能不含有<sqrt(1000)=33的质因子,所以必定含2~31中的一个素数,我们枚举这11个素数作为gcd对原数组进行分组即可。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=1e3+10; 17 using namespace std; 18 int T; 19 int n,a[N],col[N]; 20 int p[11]={2,3,5,7,11,13,17,19,23,29,31}; 21 vector<int>S[11]; 22 bool tag[N]; 23 int main(){ 24 // freopen("in.txt","r",stdin); 25 rd(T); 26 while(T--){ 27 rd(n);for(int i=1;i<=n;i++)rd(a[i]),tag[i]=0; 28 for(int i=0;i<11;i++)S[i].clear(); 29 for(int i=0;i<11;i++){ 30 for(int j=1;j<=n;j++){ 31 if(tag[j] || a[j] % p[i])continue; 32 S[i].push_back(j);tag[j]=1; 33 } 34 } 35 int now=0; 36 for(int i=0;i<11;i++){ 37 if(!S[i].size())continue;now++; 38 for(int j=0;j<S[i].size();j++)col[S[i][j]]=now; 39 } 40 printf("%d ",now); 41 for(int i=1;i<=n;i++)printf("%d ",col[i]);puts(""); 42 } 43 return 0; 44 } 45 /**/
C. K-Complete Word
题意:n(2e5)长的字符串(小写字母)。给出k(n%k==0)。修改字符串的若干位使得其为回文串并且s[i]=s[i+k]。问最少修改多少位。
思路:可以看出就是将原字符串修改为若干长k的相同字符串首尾相连。可以发现无论什么情况,这个长度为k的字符串都必须是回文串。我们可以对最终这个长k的字符串的每一位进行考虑,分别统计将n/k个字符串的这一位修改为26个字母中的某一个需要的代价。位与位之间互不影响(除非是回文串对应位),分别取最小值统计答案即可。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int N=2e5+10; 17 using namespace std; 18 int T; 19 int n,k; 20 char s[N]; 21 int cnt[N][26]; 22 int main(){ 23 // freopen("in.txt","r",stdin); 24 rd(T); 25 while(T--){ 26 rd(n);rd(k);scanf("%s",s+1); 27 for(int i=1;i<=(k+1)/2;i++) 28 for(int j=0;j<26;j++) 29 cnt[i][j]=0; 30 for(int i=1;i<=n;i+=k){ 31 for(int l=i,r=i+k-1;l <= r;l++,r--){ 32 if(l == r){ 33 for(int o=0;o<26;o++)cnt[l-i+1][o]++; 34 cnt[l-i+1][s[l]-'a']--; 35 } 36 else { 37 for(int o=0;o<26;o++)cnt[l-i+1][o]+=2; 38 cnt[l-i+1][s[l]-'a']--; 39 cnt[l-i+1][s[r]-'a']--; 40 } 41 } 42 } 43 int ans=0; 44 for(int i=1;i<=(k+1)/2;i++){ 45 int tmp=0x7fffffff; 46 for(int o=0;o<26;o++)tmp=min(tmp,cnt[i][o]); 47 ans+=tmp; 48 } 49 printf("%d ",ans); 50 } 51 return 0; 52 } 53 /**/
D. Walk on Matrix
题意:假设给出一个n*m矩阵,每个点有一个权值,每次向右或者向下位移,从(1,1)到(n,m)的答案对应路径上所有点权值的并,现在有人用一种错误的dp,即每次选择上面和左面的f&当前位置权值取较大来更新得方法,算出了一个答案,而这个答案会比最终的答案小k,现在给出k(0~1e5),要求你构造出这样一个矩阵。1<=n,m<=500,0<=aij<=3e5
思路:拿到这种题一定要知道最终矩阵是一个很有规律的矩阵或者是一个很小的手玩的矩阵,而这个k又和矩阵大小很难产生联系,所以猜测是手玩矩阵。我先尝试了2*2,无果,于是尝试了2*3,事实证明可以。a11的位置取一个范围内二进制下全为1的数字,这样对最终结果造成影响的一定是其他位置了。a12这个位置填上一个>k且和k的并为0的数字,a13这个位置直接写0,保证a23不会从这里转移,a21这个位置直接填k本身,而a22这个位置填a12|a21,这样22这个位置取得就是二者之中的较大值也就是a12,然而从a22更新a23的时候,最终答案为k&a12=0,而最终答案显然是k,构造完毕。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 using namespace std; 17 int k; 18 int main(){ 19 rd(k); 20 printf("2 3 "); 21 printf("%d %d %d %d %d %d ",(1<<18)-1,(1<<17),0,k,(1<<17)+k,k); 22 return 0; 23 } 24 /**/
反思:事后学弟和我讨论spj的写法,我大概想了一种比较靠谱的写法,但未证实:按位处理,从高位到低位,每次bfs保留(1,1)到(n,m)所有合法路径上的点,合法路径定义为路径上的点在这一位都为1,如果能找到这样的路径,那么只保留这些路径上的点,最终答案当前位为1,否则不进行任何操作,最终答案当前位为0。
E. Height All the Same
题意:对于一个n*m的矩阵,每个点有一个权值aij(L<=aij<=R),每次操作可以将一个位置+2或者相邻的两个位置各加一,一个位置上下左右四个位置是他的相邻位置。如果最终可以使得所有位置的权值相等,那么这个初始的矩阵为一个合法矩阵。现在给出n,m(1~1e9且nm>=2)以及L,R(1~1e9),问可以找出多少个合法的矩阵,答案对998244353取模。需要注意操作后矩阵上的点的权值并不需要满足在LR之间。
思路:这种范围,dp是很困难的,一般可以推出来一个方便计算的式子,当然也可能是矩阵优化dp。先考虑一些性质。如果确定了操作方式,操作顺序是没有关系的,不妨先进行所有的+1+1操作,再进行+2操作。而如果在进行+2操作之前,所有点奇偶性相同了,那么一定合法,反之一定不合法。于是我们可以摆脱+2操作,只考虑+1+1操作对数的奇偶性的影响,我们将这一操作描述为改变奇偶性。事实上我们可以对矩阵上任意两个点同时改变奇偶性,只需要沿着矩阵上两点之间的路径不断进行+1+1操作即可。考虑n*m为奇数的时候,aij为奇数的点有偶数个或者aij为偶数的点有偶数个,我们可以对具有偶数个点的那种类型两两进行类型转换,一定可以使所有点奇偶性相同。n*m为偶数的时候,如果所有aij之和为偶数,那么一定存在偶数个奇数和偶数个偶数,我们仍可以将其中一种改变为另一种。最后只需要考虑aij之和为奇数,n*m为偶数的情况。下面给出多种解决方法。
1.二项式展开。我们用组合数考虑问题,假如L~R内有E个偶数,O个奇数。从n*m个位置中取出2i个位置填上奇数,可以表示为pow(O,2i)*pow(E,nm-2i)*C(nm,2i) (0<=i<=nm/2),可以发现这个式子可以通过pow(O+E,nm)和pow(O-E,nm)通过加减得到。
2.矩阵乘法。f[0][0]表示当前位为偶数,总和为偶数的方案数,f[1][1],f[0][1],f[1][0]同理,可以发现枚举当前位的奇偶性即可进行转移,只需要建造一个4*4的矩阵,就可以完成转移,矩阵快速幂优化即可。
3.其他解法。在1的末尾我们已经可以发现pow(O-E,nm)是一个非1即0的数字,这也算是与这种解法有一丝联系。题解说这是一种常见解题方法(可能是对于这种范围很大,应该有简单式子存在的题目),我们考虑将所有合法解与不合法解一一匹配,如果可以匹配上,那么合法方案数就是总数/2。我们可以将L~R区间改为0~R-L区间,因为一定可以通过两种操作使得所有数+L,设k=R-L。当k为奇数的时候,我们发现将所有合法解的第一位a11异或1,可以得到一个一定不合法的解,因为改变了a11的奇偶性进而改变了奇偶数的数目,而且一定是一一对应的。当k为偶数的时候,对于一个合法解,我们取第一个非k的数字,异或1,也一定得到一个不合法解,同时也会一一对应,因为异或后的值仍然<k,不可能出现(k,k,a,k)和(k,k,k,b)操作后得到同一个不合法解的情况,而唯一无法匹配的情况就是全为k的情况,他是合法的,于是可以虚构出一组解和他匹配,也就是总数目+1后再/2。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int mod=998244353; 17 using namespace std; 18 int n,m,L,R; 19 int ksm(int x,LL y){ 20 int ans=1; 21 while(y){ 22 if(y&1)ans=1ll*x*ans%mod; 23 x=1ll*x*x%mod;y>>=1; 24 } 25 return ans; 26 } 27 struct matrix{ 28 int A[4][4]; 29 void clear(){ 30 for(int i=0;i<4;i++) 31 for(int j=0;j<4;j++) 32 A[i][j]=0; 33 } 34 matrix operator * (const matrix B)const{ 35 matrix C;C.clear(); 36 for(int i=0;i<4;i++) 37 for(int j=0;j<4;j++) 38 for(int k=0;k<4;k++) 39 C.A[i][j]=(C.A[i][j]+1ll*A[i][k]*B.A[k][j]%mod)%mod; 40 return C; 41 } 42 }; 43 matrix ksm_matrix(matrix A,LL y){ 44 matrix ans;ans.clear(); 45 ans.A[0][0]=1; 46 while(y){ 47 if(y&1)ans=ans*A; 48 A=A*A;y>>=1; 49 } 50 return ans; 51 } 52 int main(){ 53 // freopen("in.txt","r",stdin); 54 rd(n);rd(m);rd(L);rd(R); 55 if(1ll*n*m % 2 == 1)printf("%d ",ksm(R-L+1,1ll*n*m)); 56 else { 57 int x1=(R-L+1)/2,x2=(R-L+2)/2; 58 if(L % 2 == 0)swap(x1,x2); 59 matrix A;A.clear(); 60 A.A[0][0]=A.A[1][0]=A.A[2][2]=A.A[3][2]=x1; 61 A.A[2][1]=A.A[3][1]=A.A[0][3]=A.A[1][3]=x2; 62 matrix B=ksm_matrix(A,1ll*n*m); 63 printf("%d ",(B.A[0][0]+B.A[0][1])%mod); 64 } 65 return 0; 66 } 67 /**/
反思:
1.二项式这个应该很常用,我在推到这一步的时候竟然没能想到...这种处理方法需要记住
2.矩阵乘法可以优化那种状态数不多,每次转移都一样的情况,比如斐波那契。
3.学会这种"合法与不合法"相匹配的思路,考试的时候我有猜测最终答案是总数/2的情况,但没有任何根据也没有敢写。
4.整道题都可以说很巧妙很规范了。
F. Independent Set
题意:n(3e5)个节点的树,对于边集E,定义G(E)为E相连的所有点的集合。w(G(E))表示G(E)中独立集的个数。求所有E'∈E,且E'非空,w(G(E'))之和。
思路:这种题直接想树形dp。树上的独立集个数是可以轻松dp出来的,记录f[i][0]和f[i][1]表示当前点选不选即可。这道题用类似的思想,首先记录f[i][0]和f[i][1]表示以i为根的子树里面,所有G(E')里独立集不包含i和包含i的的数目之和。可以发现计算当前点i的f时其的儿子所对应值之间可以直接相乘。我们考虑对于其中一个儿子计算贡献。如果不取当前点i,那么儿子点可以取或者不取,而这还需要考虑i连向儿子的这条边取还是不取,所以都要*2,但是我们发现一个问题,对于儿子选的情况,该点连向儿子的边取或者不取对应贡献是不同的,如果取了,儿子连向其儿子的所有边都可以不取,而如果没有取,则至少存在一条儿子连向其儿子的边。我们可以通过记录一个点连向其儿子的所有边都不选取的情况下对应的方案数为f[i][2],并且选取该点的方案数f[i][1]不考虑是否已经选取与该点相连的边(当然有很多种记录方法,你也可以记录当前点连向父亲节点的边选或者不选的方案数),那么在计算儿子的贡献时,就需要减去这个f[u][2],因为在当前点连向儿子的边不选的情况下f[u][1]会多计算一个f[u][2]。对于f[i][1]的更新,一个儿子的贡献为2*f[u][0]+f[u][1]-f[u][2],f[u][1]-f[u][2]即只能选择与儿子之间不连边的情况。f[i][2]的计算也就是f[u][0]+f[u][1]-f[u][2]。最终f[1][0]+f[1][1]-f[1][2]-1就是答案,因为这样算会算上E'为空集的情况,所以要-1。
1 #include<bits/stdc++.h> 2 #define LL long long 3 #define dl double 4 void rd(int &x){ 5 x=0;int f=1;char ch=getchar(); 6 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 7 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 8 } 9 void lrd(LL &x){ 10 x=0;int f=1;char ch=getchar(); 11 while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();} 12 while(ch<='9' && ch>='0')x=x*10+ch-'0',ch=getchar();x*=f; 13 } 14 const int INF=1e9; 15 const LL LINF=1e18; 16 const int mod=998244353; 17 const int N=3e5+10; 18 using namespace std; 19 int n; 20 int fi[N],to[N<<1],nxt[N<<1],tot; 21 void link(int x,int y){nxt[++tot]=fi[x];fi[x]=tot;to[tot]=y;} 22 int f[N][3]; 23 void dfs(int x,int fa){ 24 f[x][0]=1;f[x][1]=1;f[x][2]=1; 25 for(int i=fi[x];i;i=nxt[i]){ 26 if(to[i] == fa)continue; 27 dfs(to[i],x);int tmp; 28 tmp=(1ll*2*f[to[i]][0]+2*f[to[i]][1]-f[to[i]][2]+mod)%mod; 29 f[x][0]=1ll*f[x][0]*tmp%mod; 30 tmp=(1ll*2*f[to[i]][0]+f[to[i]][1]-f[to[i]][2]+mod)%mod; 31 f[x][1]=1ll*f[x][1]*tmp%mod; 32 tmp=(1ll*f[to[i]][0]+f[to[i]][1]-f[to[i]][2]+mod)%mod; 33 f[x][2]=1ll*f[x][2]*tmp%mod; 34 } 35 } 36 int main(){ 37 // freopen("in.txt","r",stdin); 38 rd(n); 39 for(int i=1;i<n;i++){ 40 int x,y;rd(x);rd(y); 41 link(x,y);link(y,x); 42 } 43 dfs(1,0);printf("%d ",(1ll*f[1][0]+f[1][1]-f[1][2]-1+mod)%mod); 44 return 0; 45 } 46 /**/