8.9 考试总结
集训的时间已经过去了一大半,昨晚化奥的同学的突然出现,对我们而言不仅仅是一种约束,更是结束散漫的生活方式,提高紧张度的一种不那么令人满意的途径。的确,处于集训状态的我们,感觉并没有之前想象的那样紧张高效,虽然适当的放松是有益于更敏捷的思考,但过度的舒适带来的也是精神的松懈。所以在接下来的集训生活里,我们要提高紧张度,让HZOI在2019年焕发出新的光彩。
今天是八月九日,上午的考试让人喜忧参半。
考试过程:
依旧还是先大体观察一下题目,发现T1是一道数论,dp有部分分,最多能达到70,正解应该是组合数,可能还有容斥。于是先放在一边,暂且不谈。T2是一道图论,对于图论而言,还是较为擅长,仔细读题,感觉思路异常清晰,决定先切掉,(然而此时并不知道我理解错了题意),欢天喜地地打好了最长链,判断奇偶,分类讨论,瞬间过掉两组样例,觉得考试稳了,然后自己画了一个环,环上有五个点,想验证思路是否正确,于是。。。我把代码删了之后,去上了个厕所,回来疯狂读题,找到正确的思路,在命悬一线之时挽救了自己。T3省略(要不是老师延长了20分钟,可能T3题都没读。。)
考试结果:60+100+0=160 rank 6 / 56
考试题解:
T1:写T1题解前,奉劝大家几句:(扔了40分的血泪教训)
1、特判记得return 0。
2、特盘一定要打全。
3、开数组不要手抖,少打一个0。
T1是一道数论题,可以视作求m个物品放进n个篮子的方案数,可以想到插板法,即在m个物品中插入n-1个板子,即C(n+m-1,n-1),但由于题目限制,每个篮子中至少有1个,因此可简化为一共只有m-n个物品,此时也出现了第一个特判,如果m<n,则答案为0。那么式子可化简为C(m-1,n-1)。然后考虑另外一个限制条件,每个篮子最多有k个,此时出现第二个特判,如果m>n×k,答案为0。那么我们不妨设有i个篮子,已经达到了k个的限制,即C(m-i×k-1,n-1)。然后我们发现,每个篮子,我们会算重C(n,i)次,可以利用容斥原理处理,于是我们就愉快的拿到了100分的好成绩。
1 #include<bits/stdc++.h> 2 #define re register 3 #define ll long long 4 #define mod 998244353 5 using namespace std; 6 ll n,m,k,ans; 7 ll f[2][10000005],sum[2][10000005]; 8 inline ll read(){ 9 re ll a=0,b=1; re char ch=getchar(); 10 while(ch<'0'||ch>'9') 11 b=(ch=='-')?-1:1,ch=getchar(); 12 while(ch>='0'&&ch<='9') 13 a=(a<<3)+(a<<1)+(ch^48),ch=getchar(); 14 return a*b; 15 } 16 signed main(){ 17 n=read(),m=read(),k=read(); 18 if(m<n||m>n*k){puts("0");return 0;} 19 for(re int i=m-n-k+1;i<=m-n;++i) f[1][i]=1; 20 for(re int i=m-n;~i;--i) 21 sum[1][i]=sum[1][i+1]+f[1][i]; 22 for(re ll i=2;i<=n;++i){ 23 sum[i&1][m-n+1]=0; 24 for(re int j=m-n;~j;--j) 25 f[i&1][j]=(sum[i&1^1][j]-sum[i&1^1][min(m-n+1,j+k)]+mod)%mod, 26 sum[i&1][j]=(sum[i&1][j+1]+f[i&1][j])%mod; 27 } 28 ans=f[n&1][0]; 29 printf("%lld\n",ans%mod); 30 }
1 #include<bits/stdc++.h> 2 #define re register 3 #define mod 998244353 4 #define int long long 5 using namespace std; 6 int n,m,k,fac[10000001],inv[10000001],ans; 7 inline int read(){ 8 re int a=0,b=1; re char ch=getchar(); 9 while(ch<'0'||ch>'9') 10 b=(ch=='-')?-1:1,ch=getchar(); 11 while(ch>='0'&&ch<='9') 12 a=(a<<3)+(a<<1)+(ch^48),ch=getchar(); 13 return a*b; 14 } 15 inline int qpow(re int x,re int y,re int res=1){ 16 for(;y;y>>=1,x=x*x%mod) if(y&1) res=res*x%mod; 17 return res; 18 } 19 inline void init(){ 20 fac[0]=fac[1]=1; 21 for(re int i=1;i<=m;++i) 22 fac[i]=fac[i-1]*i%mod,inv[i]=qpow(fac[i],mod-2)%mod; 23 } 24 inline int C(re int x,re int y){ 25 if(x==y)return 1; 26 return fac[x]*inv[y]%mod*inv[x-y]%mod; 27 } 28 signed main(){ 29 n=read(),m=read(),k=read(); 30 if(m<n||m>n*k){puts("0");return 0;} 31 init(); 32 ans=C(m-1,n-1); 33 for(re int i=1;i<=n;++i){ 34 if(m<n+k*i)continue; 35 re int res=C(n,i); 36 re int tmp=C(m-i*k-1,n-1); 37 ans+=(qpow(-1,i)*res%mod*tmp%mod)%mod; 38 ans=(ans+mod)%mod; 39 } 40 printf("%lld\n",ans); 41 }
T2:算法流程:tarjan缩点+拓扑排序。
观察题面,我们发现,对于一个环,我们需要操作的次数即为这个环上的点数,因为一个环是一个强联通子图,彼此可以互相到达,因此只能每次对一个节点操作,才能保证符合题意。所以我们可以对每一个强联通分量缩点,并记录点数,以点数为点权,重新建图,那么我们该如何处理联通的节点之间的操作方式呢?容易想到拓扑排序来处理点与点之间的关系,所以我们随着拓扑不断更新权值,得到的即为最终答案。
1 #include<bits/stdc++.h> 2 #define re register 3 using namespace std; 4 int n,m,tot=0,first[1000010],second[1000010],tot2=0,cnt=0,num=0; 5 int dfn[1000010],low[1000010],bel[1000010],scc[1000010],in[1000010]; 6 bool ins[1000010];int dis[1000010],ans; 7 struct node{int u,v,next;}edge[1000010],road[1000010]; 8 inline int read(){ 9 re int a=0,b=1; re char ch=getchar(); 10 while(ch<'0'||ch>'9') 11 b=(ch=='-')?-1:1,ch=getchar(); 12 while(ch>='0'&&ch<='9') 13 a=(a<<3)+(a<<1)+(ch^48),ch=getchar(); 14 return a*b; 15 } 16 inline void add(re int x,re int y){ 17 edge[++tot].v=y; 18 edge[tot].u=x; 19 edge[tot].next=first[x]; 20 first[x]=tot; 21 } 22 inline void add2(re int x,re int y){ 23 road[++tot2].v=y; 24 road[tot2].u=x; 25 road[tot2].next=second[x]; 26 second[x]=tot2; 27 } 28 stack<int> s; 29 inline void tarjan(re int x){ 30 dfn[x]=low[x]=++num; 31 ins[x]=1;s.push(x); 32 for(re int i=first[x];i;i=edge[i].next){ 33 re int y=edge[i].v; 34 if(!dfn[y]){ 35 tarjan(y); 36 low[x]=min(low[x],low[y]); 37 } 38 else if(ins[y]) 39 low[x]=min(low[x],dfn[y]); 40 } 41 if(low[x]==dfn[x]){ 42 ++cnt;re int res; 43 do{ 44 res=s.top();s.pop(); 45 ins[res]=0,bel[res]=cnt; 46 scc[cnt]++; 47 }while(res!=x); 48 } 49 } 50 signed main(){ 51 n=read(),m=read(); 52 for(re int i=1,a,b;i<=m;++i){ 53 a=read(),b=read(); 54 add(a,b); 55 } 56 for(re int i=1;i<=n;++i) if(!dfn[i]) tarjan(i); 57 for(re int i=1;i<=tot;++i) 58 if(bel[edge[i].u]!=bel[edge[i].v]){ 59 add2(bel[edge[i].u],bel[edge[i].v]); 60 in[bel[edge[i].v]]++; 61 } 62 queue<int> q; 63 for(re int i=1;i<=cnt;++i){ 64 dis[i]=scc[i]; 65 if(!in[i]) q.push(i); 66 } 67 while(q.size()){ 68 re int x=q.front();q.pop(); 69 for(re int i=second[x];i;i=road[i].next){ 70 re int y=road[i].v;--in[y]; 71 if(dis[y]<dis[x]+scc[y]) dis[y]=dis[x]+scc[y]; 72 if(!in[y]) q.push(y); 73 } 74 } 75 for(re int i=1;i<=cnt;++i) ans=max(ans,dis[i]); 76 printf("%d\n",ans); 77 }
T3:
用 g[i][j][k] 表示前 i+j+k 轮出了 i 个石头,j 个剪刀,k个布的概率,f[i][j][k][l]表示前 i+j+k 轮出了 i 个石头,j个剪刀,k 个布,下一轮出 l 的概率。
枚举i,j,k对 f[i][j][k][x]+f[i][j][k][x+1]×3 取个 max,然后累加起来就是答案。
我们考虑如何求出 g。前 x 轮情况总数为 300^x,我们只要求出 i 个石头,j 个剪刀,k 个布对应的方案数即可。
转移方程为 g[i][j][k]+=g[i-1][j][k]*r[t]+g[i][j-1][k]*s[t]+g[i][j][k-1]*p[t]。(t 是枚举每一个人,这一维被压掉了,最后统计答案时只需要使用 g[n][i][j][k])
f 的转移也是类似的。
注意输入的顺序为石头、布、剪刀。
1 #include<bits/stdc++.h> 2 #define re register 3 #define ff f[i][j][k] 4 using namespace std; 5 int n;double r[101],p[101],s[101],c[101][101]; 6 double f[101][101][101][4],g[101][4],ans; 7 inline int read(){ 8 re int a=0,b=1; re char ch=getchar(); 9 while(ch<'0'||ch>'9') 10 b=(ch=='-')?-1:1,ch=getchar(); 11 while(ch>='0'&&ch<='9') 12 a=(a<<3)+(a<<1)+(ch^48),ch=getchar(); 13 return a*b; 14 } 15 signed main(){ 16 n=read();f[0][0][0][0]=1; 17 for(re int i=0;i<=n;++i){ 18 c[i][0]=1; 19 for(re int j=1;j<=i;++j) 20 c[i][j]=c[i-1][j]+c[i-1][j-1]; 21 } 22 for(re int i=1;i<=n;++i) 23 r[i]=(double)read()/300.0,p[i]=(double)read()/300.0,s[i]=(double)read()/300.0; 24 for(re int i=1;i<=n;++i) g[i][1]=r[i],g[i][2]=s[i],g[i][3]=p[i]; 25 for(re int l=1;l<=n;++l) for(re int i=l;~i;--i) 26 for(re int j=l-i;~j;--j) for(re int k=l-i-j;~k;--k) 27 for(re int u=(i+j+k==l?0:3);u>=0;--u){ 28 if(i) ff[u]+=f[i-1][j][k][u]*g[l][1]; 29 if(j) ff[u]+=f[i][j-1][k][u]*g[l][2]; 30 if(k) ff[u]+=f[i][j][k-1][u]*g[l][3]; 31 if(u) ff[u]+=f[i] [j][k] [0]*g[l][u]; 32 } 33 for(re int i=0;i<n;++i) for(re int j=0;i+j<n;++j) for(re int k=0;i+j+k<n;++k) 34 ans+=max(ff[1]+3*ff[2],max(ff[2]+3*ff[3],ff[3]+3*ff[1]))/(c[n][i+j+k]*(n-i-j-k)); 35 printf("%.12lf",ans); 36 return 0; 37 }