Codeforces Educational Round 57

这场出题人好像特别喜欢998244353,每个题里都放一个

A.Find Divisible

考察选手对输入输出的掌握

 1 #include<cstdio>
 2 int n,l,r;
 3 int main()
 4 {
 5     scanf("%d",&n);
 6     for(int i=1;i<=n;i++)
 7     {
 8         scanf("%d%d",&l,&r);
 9         printf("%d %d
",l,2*l);
10     }
11     return 0;
12 }
View Code

B.Substring Removal

一道讨论题

首先,特别的,如果整个串由一种字母组成,答案为非空子串数目(即$len^2-1$)

首先我们找出来左右两边相同的最长连续的段,记左边段长为l,右边段长为r。

然后显然我们可以有l+r种删法,即左/右剩下1,2,3......l/r个字母

如果左右两段的字母相同,那么删掉中间+两边延伸又有l*r种删法

还有删整个串也是一种

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=200005,mod=998244353;
 6 char a[N]; int len,lp,rp;
 7 int main()
 8 {
 9     scanf("%d%s",&len,a+1);
10     for(int i=1;i<=len;i++) 
11         if(a[i]!=a[i+1]) {lp=i; break;}
12     for(int i=len;i;i--) 
13         if(a[i]!=a[i-1]) {rp=i; break;}
14     if(lp==len) printf("%lld",(1ll*len*len-1)%mod);
15     else
16     {
17         long long ans=1;
18         ans+=len-rp+1,ans+=lp;
19         if(a[lp]==a[rp]) ans+=1ll*(lp)*(len-rp+1);
20         printf("%lld",ans%mod);
21     }
22     return 0;
23 }
View Code

C.Polygon for the Angle

一道小学数学题,题解居然还用了圆周角+圆心角,太浪费了

发现360的约数总共也没几个,于是预先筛一波内角为整数的正多边形。每次从小到大枚举,如果内角可以被$n-2$等分且内角大于询问的角就讨论一下。

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 int rec[360],n,rd,cnt,ans;
 6 void Prework()
 7 {
 8     for(int i=3;i*i<=360;i++)
 9         if(360%i==0) rec[++cnt]=i,rec[++cnt]=360/i;
10     rec[++cnt]=180,rec[++cnt]=360,sort(rec+1,rec+1+cnt);
11 }
12 int main()
13 {
14     Prework();
15     scanf("%d",&n);
16     for(int i=1;i<=n;i++)
17     {
18         scanf("%d",&rd),ans=-1;
19         for(int j=1;j<=cnt;j++)
20         {
21             int ra=180-360/rec[j];
22             if(ra==rd) {ans=rec[j]; break;}
23             else if(ra%(rec[j]-2)==0&&rd<=ra)
24             {
25                 int ori=ra/(rec[j]-2);
26                 if(rd%ori==0) {ans=rec[j]; break;}
27             }
28         }
29         printf("%d
",ans);
30     }
31     return 0;
32 }
View Code

D.Easy Problem

一道智商题

$dp[i][j]$表示到$i$位置已经拼出来了"hard"的前j个字符,讨论每个h,a,r,d的位置扔不扔即可,滚动数组可以滚成四个变量

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=100005;
 6 long long n,cst,dp[4]; char str[N];
 7 int main()
 8 {
 9     scanf("%lld%s",&n,str+1);
10     for(int i=1;i<=n;i++)
11     {
12         scanf("%lld",&cst);
13         if(str[i]=='h') dp[0]+=cst;
14         if(str[i]=='a') dp[1]=min(dp[0],dp[1]+cst);
15         if(str[i]=='r') dp[2]=min(dp[1],dp[2]+cst);
16         if(str[i]=='d') dp[3]=min(dp[2],dp[3]+cst);
17     }
18     for(int i=1;i<=3;i++) dp[0]=min(dp[0],dp[i]);
19     printf("%lld",dp[0]);
20     return 0;
21 }
View Code

E.The Top Scorer

大力计数题,本场最难一题

整体思路是规定Hasan的得分(不严格)最高的情况下,枚举Hasan的得分,然后枚举得这个分的人数来计算 

具体实现是这样的

$huge ans=frac{sumlimits_{i=r}^ssumlimits_{j=1}^pfrac{1}{j}C_{p-1}^{j-1}Calc(p-j,s-i*j,i)}{C_{s-r+p-1}^{p-1}}$

解释一下这是在干啥:底下的是总方案数,插板法;上面是先枚举Hasan的得分$i$,然后枚举得这个分的人数$j$,这样Hasan有$frac{1}{j}$的概率获胜,然后从除了Hasan的$p-1$个人里选这$j$个人有$C_{p-1}{j-1}$种方案。后面那个$Calc(x,y,z)$表示计算有$x$个人总分为$y$最高分小于$z$的方案数

那么有

$huge Calc(x,y,z)=C_{y+x-1}^{x-1}-sumlimits_{i=1}^{x}(-1)^iC_{x}^{i}C_{y-i*z+x-1}^{x-1}$

再解释一下:这是在容斥,容斥系数就不说了,$C_{x}^{i}$是规定选出$i$个人去不满足方案的方案数,$C_{y-i*z+x-1}^{x-1}$是先留出$i$个$z$让这些人不合法,剩下的插板

注意可能会出现组合数是负数但是也是有实际意义的情况,判掉(淦

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=5500,mod=998244353;
 6 int p,s,r,ans,fac[N],inv[N];
 7 void exGCD(int a,int b,int &x,int &y)
 8 {
 9     if(!b) {x=1,y=0; return;}
10     exGCD(b,a%b,y,x),y-=a/b*x;
11 }
12 int Inv(int x)
13 {
14     int xx,yy;
15     exGCD(x,mod,xx,yy);
16     return (xx%mod+mod)%mod;
17 }
18 void Prework()
19 {
20     fac[0]=inv[0]=1;
21     for(int i=1;i<=5200;i++) fac[i]=1ll*fac[i-1]*i%mod;
22     inv[5200]=Inv(fac[5200]);
23     for(int i=5199;i;i--) inv[i]=1ll*inv[i+1]*(i+1)%mod;
24 }
25 int C(int n,int m)
26 {
27     if(n==m) return 1; if(n<m) return 0;
28     return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;
29 }
30 int Calc(int cnt,int lim,int tot)//计算:cnt个人,总分为tot,最高分小于lim的方案数 
31 {
32     int ret=C(tot+cnt-1,cnt-1);//插板法 
33     for(int i=1,j;i<=cnt;i++,ret%=mod)//容斥:减掉1个的,加上2个的,减掉3个的...... 
34         j=i%2?mod-1:1,ret+=1ll*j*C(cnt,i)%mod*C(tot-i*lim+cnt-1,cnt-1)%mod;//把i个lim分出去,强行让i个人超过限制
35     return (ret+mod)%mod; 
36 }
37 int main()
38 {
39     scanf("%d%d%d",&p,&s,&r),Prework();
40     //思路:在规定Hasan得分(不严格)最高的情况下,枚举Hasan的得分,然后枚举得这个分的人数来计算 
41     for(int i=r;i<=s;i++)//枚举Hasan的得分 
42         for(int j=1;j<=p&&i*j<=s;j++,ans%=mod)//枚举得这个分的人数 
43             ans+=1ll*C(p-1,j-1)*Inv(j)%mod*Calc(p-j,i,s-i*j)%mod;//依次是选那j-1个人的方案数,在这些人里Hasan获胜的几率,还有上面写的那个东西
44     printf("%lld",1ll*ans*Inv(C(s-r+p-1,p-1))%mod);//总方案数 
45     return 0;
46 }
View Code

F.Inversion Expectation

又是一道分类讨论题(算吗?

设有$unk$个位置未知

已知-已知 求逆序对 :树状数组入门题

未知-未知 求逆序对:总共$frac{unk*(unk-1)}{2}$个有序对,其中$frac{1}{2}$是逆序对,乘起来就好了

已知-未知 求逆序对:首先对每个数$i$预处理出$un[i]$有多少个小于$i$的数没出现。从右往左扫一遍,这样如果到一个数它后面有$unk'$个未知位置,那么每个位置都有$frac{un[i]}{unk}$的几率形成一个逆序对,于是可以统计。反过来同理

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=200005,mod=998244353;
 6 int num[N],pos[N],bit[N],unk[N];
 7 int n,rd,cnt,ict,ans,luk,ruk;
 8 void exGCD(int a,int b,int &x,int &y)
 9 {
10     if(!b) {x=1,y=0; return;}
11     exGCD(b,a%b,y,x),y-=a/b*x;
12 }
13 int Qpow(int x,int k)
14 {
15     if(!k) return 1;
16     if(k==1) return x;
17     int tmp=Qpow(x,k/2);
18     return k%2?1ll*tmp*tmp%mod*x%mod:1ll*tmp*tmp%mod;
19 }
20 int Inv(int x)
21 {
22     int xx,yy;
23     exGCD(x,mod,xx,yy);
24     return (xx%mod+mod)%mod;
25 }
26 void Add(int x)
27 {
28     while(x<=n)
29         bit[x]++,x+=x&-x;
30 }
31 int Ask(int x)
32 {
33     int ret=0;
34     while(x)
35         ret+=bit[x],x-=x&-x;
36     return ret;
37 }
38 int main()
39 {
40     scanf("%d",&n);
41     for(int i=1;i<=n;i++)
42     {
43         scanf("%d",&rd),num[i]=rd;
44         if(~rd) 
45         {
46             ans+=Ask(n-rd+1),ans%=mod;
47             pos[rd]=i,Add(n-rd+1);
48         }
49     }
50     for(int i=1;i<=n;i++)
51         unk[i]=cnt,cnt+=!pos[i]; ict=Inv(cnt);
52     ans+=1ll*cnt*(cnt-1)%mod*Inv(4)%mod,ans%=mod;
53     for(int i=1;i<=n;i++)
54         ~num[i]?ans+=1ll*luk*(cnt-unk[num[i]])%mod*ict%mod,ans%=mod:luk++;
55     for(int i=n;i;i--)
56         ~num[i]?ans+=1ll*ruk*unk[num[i]]%mod*ict%mod,ans%=mod:ruk++;
57     printf("%d",ans);
58     return 0;
59 }
View Code

G.Lucky Tickets

这其实是前后都选$n$个数,问你相等的方案数$n$,用多项式乘法优化这个背包的过程即可。具体来说就是DFT之后自乘$frac{n}{2}$次然后卷回去,最后每个位置平方就是和为这个的方案。注意因为要自乘好多次需要开够多项式的空间

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 using namespace std;
 5 const int N=1000005,mod=998244353;
 6 int n,ni,G,Gi,k,m,rd,ans;
 7 int a[4*N],rev[4*N];
 8 void exGCD(int a,int b,int &x,int &y)
 9 {
10     if(!b) {x=1,y=0; return;}
11     exGCD(b,a%b,y,x),y-=a/b*x;
12 }
13 int Qpow(int x,int k)
14 {
15     if(!k) return 1;
16     if(k==1) return x;
17     int tmp=Qpow(x,k/2);
18     return k%2?1ll*tmp*tmp%mod*x%mod:1ll*tmp*tmp%mod;
19 }
20 int Inv(int x)
21 {
22     int xx,yy;
23     exGCD(x,mod,xx,yy);
24     return (xx%mod+mod)%mod;
25 }
26 void NTT(int *arr,int len,int typ)
27 {
28     for(int i=0;i<=len;i++)
29         if(rev[i]>i) swap(arr[rev[i]],arr[i]);
30     for(int i=2;i<=len;i<<=1)
31     {
32         int lth=i>>1,ort=Qpow(~typ?G:Gi,(mod-1)/i);
33         for(int j=0;j<len;j+=i)
34         {
35             int ori=1,tmp;
36             for(int k=j;k<j+lth;k++,ori=1ll*ori*ort%mod)
37             {
38                 tmp=1ll*ori*arr[k+lth]%mod;
39                 arr[k+lth]=(arr[k]-tmp+mod)%mod;
40                 arr[k]=(arr[k]+tmp)%mod;
41             }
42         }
43     }
44     if(typ==-1)
45     {
46         int ni=Inv(len);
47         for(int i=0;i<=len;i++)
48             arr[i]=1ll*arr[i]*ni%mod;
49     }
50 }
51 void Init()
52 {
53     scanf("%d%d",&n,&k);
54     G=3,Gi=Inv(G),m=1;
55     for(int i=1;i<=k;i++)
56         scanf("%d",&rd),a[rd]++;
57     while(m<=n*5) m<<=1;
58     for(int i=1;i<=m;i++)
59         rev[i]=(rev[i>>1]>>1)+(i&1)*(m>>1);
60 }
61 int main()
62 {
63     Init();
64     NTT(a,m,1);
65     for(int i=0;i<=m;i++) a[i]=Qpow(a[i],n/2);
66     NTT(a,m,-1);
67     for(int i=0;i<=m;i++) ans+=1ll*a[i]*a[i]%mod,ans%=mod;
68     printf("%d",ans);
69     return 0;
70 }
View Code
原文地址:https://www.cnblogs.com/ydnhaha/p/10222559.html