5.13 考试修改和总结
先放题解吧,考的很悲桑
第一题组合计数类DP
缩点之后易得对于每个固定形态的树有ans=(s1^d1*s2^d2……)
然后我们知道把一个树的普吕弗序列乘起来得s1^(d1-1)*s2^(d2-1)……
然后ans=普吕弗序列乘积*(s1*s2……)
设普吕弗序列长度为L(题解中的n-2并不标准)
因为sigma(s)=n
所以ans=simga(普吕弗序列乘积)*(s1*s2……)
ans=n^L*(s1*s2……)
我们只需要求出来sigma((s1*s2……)),dp就可以了
设f[i][j]表示i个元素分成j个联通块,我们考虑1的连通块大小则有
f[i][j]=simga(f[i-k][j-1]*C[i-1][k-1]*(m-1)!/2*m))
其中(m-1)!/2是环内方案数,m是环长
最后注意不存在二元环以及整个是一个环的情况
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; typedef long long LL; const int maxn=210; int n,mod; int C[maxn][maxn]; int sum[maxn]; int f[maxn][maxn]; void Get_C(){ C[0][0]=1; for(int i=1;i<=n;++i){ C[i][0]=C[i][i]=1; for(int j=1;j<i;++j){ C[i][j]=C[i-1][j-1]+C[i-1][j]; if(C[i][j]>=mod)C[i][j]-=mod; } }return; } void Get_DP(){ sum[1]=1;sum[3]=3; for(int i=4;i<=n;++i)sum[i]=sum[i-1]*i%mod; f[0][0]=1; for(int i=1;i<=n;++i){ for(int j=1;j<=i;++j){ for(int k=1;k<=i;++k){ f[i][j]=f[i][j]+1LL*f[i-k][j-1]*C[i-1][k-1]*sum[k]%mod; if(f[i][j]>=mod)f[i][j]-=mod; } } } int ans=1; for(int i=3;i<=n-1;++i)ans=ans*i%mod; int now=1; for(int i=2;i<=n;++i){ ans=ans+1LL*f[n][i]*now%mod; if(ans>=mod)ans-=mod; now=now*n%mod; }printf("%d\n",ans);return; } int main(){ freopen("land.in","r",stdin); freopen("land.out","w",stdout); scanf("%d%d",&n,&mod); Get_C();Get_DP(); return 0; }
至于第二题嘛,首先这是一道论文题,而且还是论文为了引入数位DP算法用的题目
所以这是一道很简单的题目
首先设n的长度为len,长度不为len的和很好计算,随便搞一搞就好了
之后我们考虑n的限制,分奇数偶数讨论
类似数位DP算贡献
如果len是偶数,那么每一位的符号是确定的,只需要知道出现的次数就可以了,扫一遍即可
如果len是奇数,相邻两个数互相抵消,只会剩下1,然后算有多少对就可以了
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; LL n; int Num[22],L; LL Solve(int n,int k){ if(k&1){ LL d=-1; for(int i=1;i<=n;++i)d=d*10; return d/2; }else{ if(!(n&1))return 0; LL d=-45; for(int i=1;i<n;++i)d=d*10; return d; } } LL Get_num(LL pre,int n){ int len=0,tmp=1; LL p=pre,sum=0; while(p){ sum=sum+(p%10)*tmp; tmp=-tmp;len++;p/=10; }sum=sum*(-tmp); for(int i=1;i<=n;++i)sum=sum*10; LL ans=Solve(n,n+len); if(!((n+len)&1))ans+=sum; return ans; } LL Get_ans(LL n){ if(n<10){ LL ans=0; for(int i=1;i<=n;++i){ if(i&1)ans+=i; else ans-=i; }return ans; } memset(Num,0,sizeof(Num));L=0; LL u=n; while(u)Num[++L]=u%10,u/=10; LL ans=5; for(int i=1;i<L;++i){ for(int j=1;j<=9;++j){ ans-=Get_num(j,i-1); } } LL pre=0; for(int i=L;i>=2;--i){ int end=Num[i]; for(int j=0;j<end;++j){ if(pre)ans-=Get_num(pre,i-1); pre++; }pre*=10; } int tmp=Num[1]; int t=-1; for(int i=0;i<=tmp;++i){ memset(Num,0,sizeof(Num));L=0; LL now=pre+i; while(now){ Num[++L]=now%10; now/=10; } for(int j=L;j>=1;--j){ ans+=Num[j]*t; t=-t; } }return ans; } int main(){ freopen("count.in","r",stdin); freopen("count.out","w",stdout); while(scanf("%lld",&n)==1){ if(!n)break; printf("%lld\n",Get_ans(n)); }return 0; }
第三题是个提交答案题,自己失误很大
首先第一个点爆搜写错了,第三个点没开long long,第十个点标程写错了
QAQ 一共炸掉了30分,真是悲桑
但是为什么失误这么大呢,显然是因为自己给题答只留了一个小时多一点的时间,其他时间用来check第二题和磕第一题了
如果按照CTSC我给题答留个两个多小时的话,应该能搞出50-60分
而且吐槽一句:这个题答太不标准了,没checker,没评分参数,不然我怎么会挂掉?
挂掉还是自己弱
第一个点爆搜就可以了,自己犯蠢挂了
#include<cstdio> #include<cstring> #include<iostream> #include<cstdlib> #include<algorithm> using namespace std; const int maxn=200010; int n,m; int ans=0; int val[maxn]; struct OP{ char type; int u,v,val; }c[maxn]; int Get_ans(int S){ int ans=0; for(int i=0;i<n;++i)if(S>>i&1)ans+=val[i]; for(int i=1;i<=m;++i){ int u=c[i].u,v=c[i].v; if(c[i].type=='A'){ if(S>>u&1){ if(S>>v&1)ans+=c[i].val; } }else if(c[i].type=='B'){ if((S>>u&1)||(S>>v&1))ans+=c[i].val; }else if(c[i].type=='C'){ if(S>>u&1){ if(!(S>>v&1))ans+=c[i].val; } }else if(c[i].type=='D'){ if(S>>v&1){ if(!(S>>u&1))ans+=c[i].val; } }else{ if(!(S>>u&1)){ if(!(S>>v&1))ans+=c[i].val; } } }return ans; } int main(){ freopen("shell1.in","r",stdin); freopen("shell1.out","w",stdout); scanf("%d%d",&n,&m); for(int i=0;i<n;++i)scanf("%d",&val[i]); for(int i=1;i<=m;++i){ c[i].type=getchar(); while(c[i].type<'!')c[i].type=getchar(); scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val); c[i].u--;c[i].v--; } for(int i=0;i<(1<<n);++i){ ans=max(ans,Get_ans(i)); }printf("%d\n",ans); return 0; }
第二个点注意到所有的权都很大,所以只是让你去写高精度
你贪心的去取所有正权点,会发现所有正权边也会被取到
然后答案就显然了
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; const int maxn=52; int n,m,u,v; char s[maxn]; struct big_num{ int a[52],len; void init(){ len=0;memset(a,0,sizeof(a)); len=strlen(s+1); for(int i=len;i>=1;--i)a[len-i+1]=s[i]-'0'; } void add(const big_num &A){ len=max(len,A.len)+1; for(int i=1;i<=len;++i){ a[i]=a[i]+A.a[i]; if(a[i]>=10)a[i]-=10,a[i+1]++; } while(len>0&&a[len]==0)len--; } void print(){ for(int i=len;i>=1;--i)printf("%d",a[i]); } }ans,A; int main(){ freopen("shell2.in","r",stdin); freopen("shell2.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ scanf("%s",s+1); if(s[1]=='-')continue; A.init();ans.add(A); } for(int i=1;i<=m;++i){ char ch=getchar(); while(ch<'!')ch=getchar(); scanf("%d%d",&u,&v); scanf("%s",s+1); if(s[1]=='-')continue; A.init();ans.add(A); }ans.print(); return 0; }
第三个点你会发现边权都特别大,然后点权是-1
然后限制都是B,所以我们一定能取得所有的边,问题就转化成了用最少的点
限制是u,v两个之中至少有一个被选中,二分图匹配即可
注意开long long
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> #include<queue> using namespace std; typedef long long LL; const int maxn=200010; int n,m,u,v,w; LL ans=0; int val[maxn]; int h[maxn],cnt=1; int vis[maxn],tim; int girl[maxn]; struct edge{ int to,next; }G[3000010]; void add(int x,int y){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];h[x]=cnt; } bool find(int u){ for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(vis[v]==tim)continue; vis[v]=tim; if(!girl[v]||find(girl[v])){ girl[v]=u;girl[u]=v; return true; } }return false; } int main(){ freopen("shell3.in","r",stdin); freopen("shell3.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)scanf("%d",&val[i]); for(int i=1;i<=m;++i){ char ch=getchar(); while(ch<'!')ch=getchar(); scanf("%d%d%d",&u,&v,&w); add(u,v);add(v,u); ans+=w; } for(int i=1;i<=n;++i){ if(girl[i])continue; tim++; if(find(i))ans--; }printf("%lld\n",ans); return 0; }
第四个点只有C限制,而且边权都很小且是负数
我们就发现我们一条边也不能选,限制变成了选u就必须选v,求最大点权和
最大权闭合子图即可
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #include<queue> using namespace std; const int maxn=200010; const int oo=0x7fffffff/3; int n,m,S,T; int u,v,w; int ans=0; int val[maxn]; int h[maxn],cnt=1; int cur[maxn]; struct edge{ int to,next,w; }G[3000010]; queue<int>Q; int vis[maxn]; void add(int x,int y,int z){ cnt++;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; ++cnt;G[cnt].to=x;G[cnt].next=h[y];G[cnt].w=0;h[y]=cnt; } bool BFS(){ for(int i=S;i<=T;++i)vis[i]=-1; Q.push(S);vis[S]=1; while(!Q.empty()){ int u=Q.front();Q.pop(); for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(G[i].w>0&&vis[v]==-1){ vis[v]=vis[u]+1; Q.push(v); } } }return vis[T]!=-1; } int DFS(int x,int f){ if(x==T||f==0)return f; int w,used=0; for(int i=cur[x];i;i=G[i].next){ if(vis[G[i].to]==vis[x]+1){ w=f-used; w=DFS(G[i].to,min(G[i].w,w)); G[i].w-=w;G[i^1].w+=w; if(G[i].w>0)cur[x]=i; used+=w;if(used==f)return used; } } if(!used)vis[x]=-1; return used; } void dinic(){ while(BFS()){ for(int i=S;i<=T;++i)cur[i]=h[i]; ans-=DFS(S,oo); }return; } int main(){ freopen("shell4.in","r",stdin); freopen("shell4.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)scanf("%d",&val[i]); for(int i=1;i<=m;++i){ char ch=getchar(); while(ch<'!')ch=getchar(); scanf("%d%d%d",&u,&v,&w); add(u,v,oo); } S=0;T=n+1; for(int i=1;i<=n;++i){ if(val[i]>0)ans+=val[i],add(S,i,val[i]); else add(i,T,-val[i]); } dinic(); printf("%d\n",ans); return 0; }
第五个点我们观察数据会发现有一个点点权很大,一个点点权很小,其他点都是0
之后看边权,发现边权和都是负的,证明负的点权一定不能取
且正点权一定能取,由于边权都是负数,所以能不选就不选
因为全是C操作
可以把正权点看成S,负权点看成T,从S到T求最小割即可
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #include<queue> using namespace std; const int maxn=200010; const int oo=0x7fffffff; int n,m,S,T; int u,v,w; int ans; int val[maxn]; int h[maxn],cnt=1; int cur[maxn]; struct edge{ int to,next,w; }G[3000010]; queue<int>Q; int vis[maxn]; void add(int x,int y,int z){ ++cnt;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;h[x]=cnt; ++cnt;G[cnt].to=x;G[cnt].next=h[y];G[cnt].w=0;h[y]=cnt; } bool BFS(){ for(int i=1;i<=n;++i)vis[i]=-1; Q.push(S);vis[S]=1; while(!Q.empty()){ int u=Q.front();Q.pop(); for(int i=h[u];i;i=G[i].next){ int v=G[i].to; if(G[i].w>0&&vis[v]==-1){ vis[v]=vis[u]+1; Q.push(v); } } }return vis[T]!=-1; } int DFS(int x,int f){ if(x==T||f==0)return f; int w,used=0; for(int i=cur[x];i;i=G[i].next){ if(vis[G[i].to]==vis[x]+1){ w=f-used; w=DFS(G[i].to,min(G[i].w,w)); G[i].w-=w;G[i^1].w+=w; if(G[i].w>0)cur[x]=i; used+=w;if(used==f)return used; } } if(!used)vis[x]=-1; return used; } void dinic(){ while(BFS()){ for(int i=1;i<=n;++i)cur[i]=h[i]; ans-=DFS(S,oo); }return; } int main(){ freopen("shell5.in","r",stdin); freopen("shell5.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ scanf("%d",&val[i]); if(val[i]>0)ans+=val[i]; } for(int i=1;i<=m;++i){ char ch=getchar(); while(ch<'!')ch=getchar(); scanf("%d%d%d",&u,&v,&w); add(u,v,-w); } S=12345;T=98765; dinic(); printf("%d\n",ans); return 0; }
UPD:第九个点和第五个点一样,所以程序差不多
第六个点我们发现数据时完全随机无规律,所以只能用近似算法了
模拟退火乱搞即可,搞的并没有答案优QAQ
#include<cstdio> #include<cstring> #include<iostream> #include<cstdlib> #include<algorithm> #include<cmath> #define eps 1e-5 using namespace std; const int maxn=200010; const int oo=0x7fffffff; int n,m,ans; int val[maxn]; bool vis[maxn]; struct OP{ char type; int u,v,val; }c[maxn]; double R(){return rand()%10000/10000.0;} int Get_ans(){ int sum=0; for(int i=1;i<=n;++i)if(vis[i])sum+=val[i]; for(int i=1;i<=m;++i){ int u=c[i].u,v=c[i].v; if(c[i].type=='A'){ if(vis[u]&&vis[v])sum+=c[i].val; }else if(c[i].type=='B'){ if(vis[u]||vis[v])sum+=c[i].val; }else if(c[i].type=='C'){ if(vis[u]&&!vis[v])sum+=c[i].val; }else if(c[i].type=='D'){ if(!vis[u]&&vis[v])sum+=c[i].val; }else if(!vis[u]&&!vis[v])sum+=c[i].val; }return sum; } void SA(double T){ int tmp=0,cur=0; for(int i=1;i<=n;++i)vis[i]=rand()%2; cur=Get_ans(); while(T>eps){ int now=rand()%n+1; vis[now]^=1; tmp=Get_ans(); ans=max(ans,tmp); int d=tmp-cur; if(d>0||exp(d/T)>R())cur=tmp; else vis[now]^=1; T*=0.998; }return; } int main(){ freopen("shell6.in","r",stdin); freopen("shell6.out","w",stdout); srand(5211314); scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)scanf("%d",&val[i]); for(int i=1;i<=m;++i){ c[i].type=getchar(); while(c[i].type<'!')c[i].type=getchar(); scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val); } int T=1000;ans=-oo; while(T--)SA(10000); printf("%d\n",ans); return 0; }
第七个点和第八个点数据都做错了,不过我还是写了标程的
很容易发现他想要做的数据是分块的,然后分块模拟退火即可
#include<cstdio> #include<cstring> #include<iostream> #include<cstdlib> #include<algorithm> #include<cmath> #define eps 1e-5 using namespace std; const int maxn=200010; const int oo=0x7fffffff; int n,m,ans,Ans; int val[maxn]; bool vis[maxn]; struct OP{ char type; int u,v,val; }c[maxn]; double R(){return rand()%10000/10000.0;} int Get_ans(){ int sum=0; for(int i=1;i<=100;++i)if(vis[i])sum+=val[i]; for(int i=1;i<=m;++i){ int u=c[i].u,v=c[i].v; if(c[i].type=='A'){ if(vis[u]&&vis[v])sum+=c[i].val; }else if(c[i].type=='B'){ if(vis[u]||vis[v])sum+=c[i].val; }else if(c[i].type=='C'){ if(vis[u]&&!vis[v])sum+=c[i].val; }else if(c[i].type=='D'){ if(!vis[u]&&vis[v])sum+=c[i].val; }else if(!vis[u]&&!vis[v])sum+=c[i].val; }return sum; } void SA(double T){ int tmp=0,cur=0; for(int i=1;i<=100;++i)vis[i]=rand()%2; cur=Get_ans(); while(T>eps){ int now=rand()%100+1; vis[now]^=1; tmp=Get_ans(); ans=max(ans,tmp); int d=tmp-cur; if(d>0)cur=tmp; else vis[now]^=1; T*=0.9; }return; } int main(){ freopen("shell7.in","r",stdin); freopen("shell7.out","w",stdout); srand(5211314); scanf("%d%d",&n,&m); m/=1000; for(int i=1;i<=n;++i)scanf("%d",&val[i]); for(int k=1;k<=1000;++k){ for(int i=1;i<=m;++i){ c[i].type=getchar(); while(c[i].type<'!')c[i].type=getchar(); scanf("%d%d%d",&c[i].u,&c[i].v,&c[i].val); c[i].u=c[i].u%100+1;c[i].v=c[i].v%100+1; } ans=-oo;int T=300; while(T--)SA(10000); Ans+=ans; for(int i=1;i+100<=n;++i)val[i]=val[i+100]; } printf("%d\n",Ans); return 0; }
第十个点m=n-1,观察边很容易发现这是棵树
设f[i][0/1]表示选不选,然后做一遍树形DP就可以了
讨论的时候注意细节,连std都讨论错了
#include<cstdio> #include<cstring> #include<cstdlib> #include<iostream> #include<algorithm> using namespace std; typedef long long LL; const int maxn=200010; int n,m; int u,v,w; int h[maxn],cnt=0; int val[maxn]; LL f[maxn][2]; struct edge{ char type; int to,next,w; }G[maxn]; void add(int x,int y,int z,char c){ cnt++;G[cnt].to=y;G[cnt].next=h[x];G[cnt].w=z;G[cnt].type=c;h[x]=cnt; } void DP(int u){ f[u][0]=0;f[u][1]=val[u]; for(int i=h[u];i;i=G[i].next){ int v=G[i].to; DP(v); if(G[i].type=='A'){ f[u][0]=f[u][0]+max(f[v][0],f[v][1]); f[u][1]=f[u][1]+max(f[v][0],f[v][1]+G[i].w); }else if(G[i].type=='B'){ f[u][0]=f[u][0]+max(f[v][0],f[v][1]+G[i].w); f[u][1]=f[u][1]+G[i].w+max(f[v][0],f[v][1]); }else if(G[i].type=='C'){ f[u][0]=f[u][0]+max(f[v][0],f[v][1]); f[u][1]=f[u][1]+max(f[v][0]+G[i].w,f[v][1]); }else if(G[i].type=='D'){ f[u][0]=f[u][0]+max(f[v][0],f[v][1]+G[i].w); f[u][1]=f[u][1]+max(f[v][0],f[v][1]); }else{ f[u][0]=f[u][0]+max(f[v][0]+G[i].w,f[v][1]); f[u][1]=f[u][1]+max(f[v][1],f[v][0]); } }return; } int main(){ freopen("shell10.in","r",stdin); freopen("shell10.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=n;++i)scanf("%d",&val[i]); for(int i=1;i<=m;++i){ char ch=getchar(); while(ch<'!')ch=getchar(); scanf("%d%d%d",&u,&v,&w); if(u>v){ swap(u,v); if(ch=='B')ch='C'; else if(ch=='C')ch='B'; } add(u,v,w,ch); } DP(1); printf("%lld\n",max(f[1][0],f[1][1])); return 0; }