Educational Codeforces Round 84 (Rated for Div. 2)
A. Sum of Odd Integers
题意:给你n,k问n能否表示为k个不同的正奇数之和。
思路:k个奇数和的奇偶性与k相同,所以如果k和n奇偶性不同必然不行。相同时我们可以取最小的几个正奇数1,3,5...2k-1,如果他们之和小于n,我们可以调整2k-1为合适的值来使得他们的和为n,否则无论如何也凑不出n。
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; 18 int n,k; 19 int main(){ 20 // freopen("in.txt","r",stdin); 21 rd(T); 22 while(T--){ 23 rd(n);rd(k); 24 if(n%2 != k%2)printf("NO\n"); 25 else { 26 if(1ll*k*k <= n)printf("YES\n"); 27 else printf("NO\n"); 28 } 29 } 30 return 0; 31 } 32 /**/
B. Princesses and Princes
题意:两组点,每组有n(1e5)个,第一组点向第二组点连若干条边,第一组点依次进行匹配,按照第二组点的编号从小到大进行匹配,如果当前点已经被其他点匹配上则尝试匹配下一个点,如果都没办法匹配就不匹配。现在让你加一条边,使得匹配数比加边前更多。
思路:模拟这个过程,如果所有人都匹配上了,那么怎么加边都不会匹配得更多了,否则任选两个未匹配的点相连即可,并不会影响到其他的点匹配。
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=1e5+10; 17 using namespace std; 18 int T; 19 int n; 20 bool is1[N],is2[N]; 21 int main(){ 22 // freopen("in.txt","r",stdin); 23 rd(T); 24 while(T--){ 25 rd(n);for(int i=1;i<=n;i++)is1[i]=is2[i]=0; 26 bool flg=0; 27 for(int i=1;i<=n;i++){ 28 int x,y;rd(x); 29 for(int j=1;j<=x;j++){ 30 rd(y); 31 if(!is1[i] && !is2[y]){ 32 is1[i]=1;is2[y]=1; 33 } 34 } 35 if(!is1[i])flg=1; 36 } 37 if(!flg)printf("OPTIMAL\n"); 38 else { 39 printf("IMPROVE\n"); 40 for(int i=1;i<=n;i++)if(!is1[i]){printf("%d ",i);break;} 41 for(int i=1;i<=n;i++)if(!is2[i]){printf("%d\n",i);break;} 42 } 43 } 44 return 0; 45 } 46 /**/
C. Game with Chips
题意:n*m(200*200)的棋盘上有k(200)个棋子(可以在同一格),每次你可以控制使得所有的点上下左右移动,如果在边界,那么就不动,再给出每个棋子的目标位置,问能否在2*n*m步之内使所有的棋子经过他们的目标位置,输出方案,不存在输出-1。
思路:所有棋子移动到(1,1),然后遍历整个棋盘,需要的步数为n*m+n+m-3 < 2*n*m。
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=205; 17 using namespace std; 18 int n,m; 19 int main(){ 20 // freopen("in.txt","r",stdin); 21 rd(n);rd(m); 22 printf("%d\n",n*m+n+m-3); 23 for(int i=1;i<n;i++)printf("U"); 24 for(int i=1;i<m;i++)printf("L"); 25 for(int i=1;i<=n;i++){ 26 for(int j=1;j<m;j++){ 27 if(i&1)printf("R"); 28 else printf("L"); 29 } 30 if(i!=n)printf("D"); 31 } 32 return 0; 33 } 34 /**/
D. Infinite Path
题意:定义一种数组之间的运算c=a x b,表示c[i]=b[a[i]],pow(p,k)=p x p x ... x p(k个),给出排列p,再给出每个点的颜色,寻找最小的正数k使得pow(p,k)得到的序列q按照i向qi连边后存在一个环,环上的点颜色相同。
思路:容易发现对于初始排列按此方式连边得到若干个环之后,每次p x 操作对于一个点都是在其所在的环上操作,对应环上所有数字"左移一次",所以k<=n才有意义。群论中有结论(应该是),对于 一个环,操作k-1次可以得到gcd(k,len)个长度相等的环。还有结论,如果gcd(k1,len)==gcd(k2,len),那么二者得到的环上的点都是相同的,只是顺序不同。于是我们可以直接枚举初始环长的约数作为操作次数,是n^(1/3)级别的(并不会证明),知道了操作的次数之后便可以很容易的得到操作后的序列(左移k-1次),然后遍历一次便可知道是否存在颜色完全相同的环。总复杂度n^(4/3)。
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,n,p[N],c[N],k; 19 bool tag[N]; 20 int a[N],col[N],cnt,hs[N]; 21 void dfs(int x){ 22 a[cnt]=p[x];hs[x]=cnt;col[cnt]=c[x];cnt++; 23 tag[x]=1;if(!tag[p[x]])dfs(p[x]); 24 } 25 int b[N]; 26 bool tag2[N]; 27 bool check(int x){tag2[x]=1;if(tag2[b[x]])return 1;else return check(b[x])&(col[x] == col[b[x]]);} 28 void get_ans(int x){ 29 for(int i=0;i<cnt;i++)b[i]=a[(i+x-1)%cnt],tag2[i]=0; 30 for(int i=0;i<cnt;i++)if(!tag2[i] && check(i)){k=min(k,x);break;} 31 } 32 void work(int x){ 33 cnt=0;dfs(x); 34 for(int i=0;i<cnt;i++)a[i]=hs[a[i]]; 35 for(int i=1;i<=sqrt(cnt);i++){ 36 if(cnt % i)continue;get_ans(i); 37 if(i != cnt/i)get_ans(cnt/i); 38 } 39 } 40 int main(){ 41 // freopen("in.txt","r",stdin); 42 rd(T); 43 while(T--){ 44 rd(n);k=n; 45 for(int i=1;i<=n;i++)rd(p[i]),tag[i]=0; 46 for(int i=1;i<=n;i++)rd(c[i]); 47 for(int i=1;i<=n;i++)if(!tag[i])work(i); 48 printf("%d\n",k); 49 } 50 return 0; 51 } 52 /**/
反思:一个数的约数是n^(1/3)级别。对于一个环,“左移”这一操作k-1次,便得到gcd(k,len)个长度相等的环,并且只要gcd(k,len)相等,环上点的集合就相同,只是顺序不同。另外p[i]=p[p[i]]等同于在"环上左移"的操作。
E. Count The Blocks
题意:给定n(2e5),求在0~pow(10,n)-1中(这里的数字用前缀0补齐n位),各长度的"块"有多少个,一个块定义为一串连续的最长的相同的数字,即333只计算长度为3的块,答案对998244353取模。
思路:我们枚举块的长度,对于长度为l的块,如果l=n,那么就是10种方案,即0~9所有的块。对于其他长度的块要分两部分计算,贴边的和不贴边的,贴边的情况就是块本身的10种*相邻位置的9种,其他位置可以任意选择,不贴边的情况就是要考虑两个相邻块,情况类似。块与块之间计算完全独立,所以你在将其他位置任意取的时候并不会影响到其他块对答案计算的贡献。
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=2e5+10; 18 using namespace std; 19 int n; 20 int pw[N]; 21 int main(){ 22 freopen("a.out","w",stdout); 23 rd(n); 24 pw[0]=1;for(int i=1;i<=n;i++)pw[i]=1ll*pw[i-1]*10%mod; 25 for(int i=1;i<=n;i++){ 26 int ans=0; 27 if(i <= n-2)ans=1ll*10*(n-i-1)%mod*81%mod*pw[n-i-2]%mod; 28 if(i <= n-1)ans=(ans+2ll*10*9*pw[n-i-1]%mod)%mod; 29 if(i == n)ans=10; 30 printf("%d ",ans); 31 } 32 return 0; 33 } 34 /**/
F. AND Segments
题意:给出n(5e5),m(5e5),k(30),寻找合法序列的个数,对998244353取模,合法序列定义为:长度为n,且满足给出的m个条件l,r,x,表示al & ... & ar = x。所有0<=x<pow(2,k)。
思路:每一位独立,所以按位处理。问题就转换成了al & ... & ar =1或者0,如果为1,那么l~r内所有数都是1,如果为0,则表示l~r中至少有一个0。所以就需要找到一个01序列(有些地方已知全是1),满足li~ri至少有一个0,问这种序列的个数是多少。不妨考虑dp。f[i]表示截至目前为止,最后一个0在第i位的情况,方案数是多少。对于当前处理的位,如果其为0,那么就可以用前面所有的f[i]之和来更新这个答案,如果为1,那么没有任何影响。在处理完当前位后,需要将一些f[i]重置为0,具体就是那些使得某个l~r都没出现0的f[i],也就是说i+1~当前位j之间存在一个完整的l~r,而且一旦被重置为0之后就永远是0了。我们可以通过计算以当前位为r的l~r的l最右面到了哪里,将这个l左面所有的f重置即可。通过记录前缀和和当前重置到了第几位就可以轻松线性的更新答案。
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=5e5+10; 18 using namespace std; 19 int n,k,m; 20 struct constraint{ 21 int l,r,x; 22 bool operator < (const constraint &o)const{ 23 return r < o.r || (r == o.r && l < o.l); 24 } 25 }a[N]; 26 int ans; 27 int f[N],s[N]; 28 int work(int x){ 29 for(int i=1;i<=n;i++)s[i]=0,f[i]=0; 30 for(int i=1;i<=m;i++)if(a[i].x & (1<<x-1))s[a[i].l]++,s[a[i].r+1]--; 31 int pre=1,l=0;f[0]=1; 32 for(int i=1,j=1;i<=n;i++){ 33 s[i]+=s[i-1]; 34 if(!s[i])f[i]=pre; 35 int tmp=0; 36 while(j <= m && a[j].r <= i){ 37 if((a[j].x&(1<<x-1)) == 0) 38 tmp=max(tmp,a[j].l); 39 j++; 40 } 41 while(l < tmp)pre=(pre-f[l]+mod)%mod,f[l++]=0; 42 pre=(pre+f[i])%mod; 43 } 44 return pre; 45 } 46 int main(){ 47 // freopen("in.txt","r",stdin); 48 rd(n);rd(k);rd(m);ans=1; 49 for(int i=1;i<=m;i++)rd(a[i].l),rd(a[i].r),rd(a[i].x); 50 sort(a+1,a+m+1); 51 for(int i=1;i<=k;i++)ans=1ll*ans*work(i)%mod; 52 printf("%d\n",ans); 53 return 0; 54 } 55 /**/
反思:这种dp应该是一种比较具有普遍意义的dp,感觉会很常见。另外也利用了滚动数组的思想,用一维数组不断覆盖来代替二维数组。