十二省联考2019题解
day1t1
考场上唯一会做的题,还因为习惯性写for(int i=1;i<=n;i++)而挂掉40分,本次考试最大的败笔。
说说我的做法:对每一个a[i]建立一棵字典树,然后维护一个堆,记录对于每个a[i]第x大的异或值,然后每次弹出时记录下一个。不需要可持久化,因为可以给字典树记录一个维护子树大小的sz数组,查询方式类似于treap。
考场上只改了一个字符的code
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #include<queue> using namespace std; typedef long long ll; const int N=5e5+7; struct node{int u,id;ll v;}; int n,k,tot=1,ch[N*33][2],sz[N*33]; ll ans,a[N]; bool operator<(node a,node b){return a.v<b.v;} priority_queue<node>q; void build(ll x) { int u=1; for(int i=31;i>=0;i--) { int idx; if(x&(1ll<<i))idx=1;else idx=0; if(!ch[u][idx])ch[u][idx]=++tot; u=ch[u][idx]; sz[u]++; } } ll query(ll x,int rk) { int u=1; ll ret=0; for(int i=31;i>=0;i--) { int idx; if(x&(1ll<<i))idx=1;else idx=0; if(sz[ch[u][idx^1]]>=rk)ret+=1ll<<i,u=ch[u][idx^1]; else rk-=sz[ch[u][idx^1]],u=ch[u][idx]; } return ret; } int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++)scanf("%lld",&a[i]),a[i]^=a[i-1]; for(int i=0;i<=n;i++)build(a[i]); for(int i=0;i<=n;i++)q.push((node){i,1,query(a[i],1)}); for(int tim=1;tim<2*k;tim++) { node u=q.top();q.pop(); if(tim&1)ans+=u.v; if(u.id==n)continue; u.id++,u.v=query(a[u.u],u.id); q.push(u); } printf("%lld",ans); return 0; }
day1t2
考场上只会40pts的O(nm)哈希暴力,80pts写挂弃疗,还是太水了。
做法:其实不用SA,可以用一种代码较短的写法,但是思维难度比较高,要用到SAM+树上倍增+字典树优化建图+拓扑排序。
首先建立反串的SAM,然后记录反串每个位置在SAM上的位置,再倍增。对SAM的每个点开vector,然后按照子串长度和是否为A类串为1和2关键字排序即可,再用字典树优化建边,就可以跑拓扑排序了(考场上我还写tarjan判环)。
code
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=4e5+7; int n,m,na,nb,sz,tot,a[N],b[N],is[N<<1],in[N<<1],hd[N<<1],v[N<<2],nxt[N<<2]; int last,cnt,id[N],lst[N],fa[N],f[N][20],ch[N][26],len[N<<1]; ll dis[N<<1]; char s[N]; vector<int>G[N<<1]; queue<int>q; bool cmp(int x,int y){return len[x]>len[y]||len[x]==len[y]&&is[x]>is[y];} void adde(int x,int y){v[++tot]=y,nxt[tot]=hd[x],hd[x]=tot,in[y]++;} void build(int c) { int p=last,np=++cnt; last=np,len[np]=len[p]+1; while(p&&!ch[p][c])ch[p][c]=np,p=fa[p]; if(!p)fa[np]=1; else{ int q=ch[p][c]; if(len[p]+1==len[q])fa[np]=q; else{ int nq=++cnt;len[nq]=len[p]+1; memcpy(ch[nq],ch[q],sizeof ch[q]); fa[nq]=fa[q],fa[q]=fa[np]=nq; while(p&&ch[p][c]==q)ch[p][c]=nq,p=fa[p]; } } } void judge(int b) { int l,r;scanf("%d%d",&l,&r); r=r-l+1,l=id[l]; for(int i=19;~i;i--)if(f[l][i]&&len[f[l][i]]>=r)l=f[l][i]; is[++sz]=b,len[sz]=r; G[l].push_back(sz); } int main() { int T;scanf("%d",&T); while(T--) { scanf("%s",s+1),n=strlen(s+1); last=cnt=1; for(int i=n;i;i--)build(s[i]-'a'),id[i]=last; for(int i=1;i<=cnt;i++)f[i][0]=fa[i]; for(int j=1;j<=19;j++)for(int i=1;i<=cnt;i++)f[i][j]=f[f[i][j-1]][j-1]; scanf("%d",&na),sz=cnt; for(int i=1;i<=na;i++)judge(1),a[i]=sz; scanf("%d",&nb); for(int i=1;i<=nb;i++)judge(0),b[i]=sz; for(int i=1;i<=cnt;i++)sort(G[i].begin(),G[i].end(),cmp); for(int i=1;i<=cnt;i++) { int last=i; for(int j=G[i].size()-1;j>=0;j--) { adde(last,G[i][j]); if(!is[G[i][j]])last=G[i][j]; } lst[i]=last; } for(int i=2;i<=cnt;i++)adde(lst[fa[i]],i); for(int i=1;i<=sz;i++)if(!is[i])len[i]=0; scanf("%d",&m); int flag=0;ll ans=0; for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),adde(a[x],b[y]); for(int i=1;i<=sz;i++)if(!in[i])q.push(i); while(!q.empty()) { int x=q.front();q.pop(),ans=max(ans,dis[x]+len[x]); for(int i=hd[x];i;i=nxt[i]) { dis[v[i]]=max(dis[v[i]],dis[x]+len[x]); if(!--in[v[i]])q.push(v[i]); } } for(int i=1;i<=sz;i++)if(in[i]){flag=1;break;} if(flag)puts("-1");else printf("%lld\n",ans); while(!q.empty())q.pop(); for(int i=1;i<=cnt;i++)fa[i]=0,memset(ch[i],0,sizeof ch[i]); for(int i=1;i<=sz;i++)G[i].clear(),is[i]=len[i]=hd[i]=dis[i]=in[i]=0; last=cnt=sz=tot=0; } }
day1t3
坑。
考场上杠了很长时间T3,获得39分的“好”成绩
做法:这题目太垃圾,做个鬼,而且也做不了,不用gedit也打不开最后一个点,真实防AK的题。
说说39分的吧:subtask1~3:直接写,注意指数模998244352即可;subtask4:找到最大的结果,然后找2个结果相同的,就能求出循环节,但这里要找2组,这样就可以愉快的求gcd了,很容易找到模数;subtask6:快速幂不乘long long,做法就是直接写ans=ans*19%mod,开int就行了;subtask8:线性筛质数(实际上我傻了没想起来用1e6以内的质数筛出第9个点,可能是考场上太紧张了);subtask11:线性筛mu;subtask14:求原根。原根的定义:对于一个整数p,若p^0,p^1,p^2,……,p^(mod-2)在%mod的意义下互不相同。然后由于p^(Mod-1)≡1(mod Mod),所以p^i的循环节必然是mod-1的因数,枚举1e5个数判断循环节,是否中途回到1即可,复杂度是O(1e5*31*步长个数),但是mod-1有94个不为1或mod-1的因数,显然会TLE(实测11s),那如何优化呢?发现一些步长是另一些的因数,显然这些是可以筛掉的,只保留真正有用的就行,发现1s左右就跑完了。
考场39pts code
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> using namespace std; const int N=1e6+7; int mod=998244353,cnt,pri[N],vis[N],mu[N]; char typ[101],str[1001]; int qpow(int a,int b) { int ret=1; while(b) { if(b&1)ret=1ll*ret*a%mod; a=1ll*a*a%mod,b>>=1; } return ret; } void so_easy() { int Q,n,len;scanf("%d",&Q); while(Q--) { scanf("%s",str); len=strlen(str),n=0; for(int i=0;i<len;i++)n=(10ll*n+str[i]-'0')%(mod-1); printf("%d\n",qpow(19,n)); } } void prepare() { mu[1]=1; for(int i=2;i<=1e6;i++) { if(!vis[i])pri[++cnt]=i,mu[i]=-1; for(int j=1;j<=cnt&&i*pri[j]<=1e6;j++) { vis[i*pri[j]]=1; if(i%pri[j]==0){mu[i*pri[j]]=0;break;} mu[i*pri[j]]=-mu[i]; } } } int main() { scanf("%s",typ); int lentyp=strlen(typ); if(typ[0]=='1'&&typ[1]=='_'){so_easy();return 0;} if(lentyp==2&&typ[1]=='?') { mod=1145141; int Q,n,len;scanf("%d",&Q); while(Q--) { scanf("%s",str); len=strlen(str),n=0; for(int i=0;i<len;i++)n=(10ll*n+str[i]-'0')%572570; printf("%d\n",qpow(19,n)); } return 0; } if(typ[0]=='1'&&typ[1]=='w') { int Q,n=1,len;scanf("%d",&Q); while(Q--) { scanf("%*s"); printf("%d\n",n); n=n*19%mod; } return 0; } prepare(); if(typ[0]=='2'&&typ[1]=='p') { int Q,a,b; scanf("%d",&Q); while(Q--) { scanf("%d%d",&a,&b); if(a==5&&b==10)puts("p.p..."); else if(a==2) { for(int i=2;i<=b;i++)if(!vis[i])printf("p");else printf("."); puts(""); } } return 0; } if(typ[0]=='2'&&typ[1]=='u') { int Q,a,b; scanf("%d",&Q); while(Q--) { scanf("%d%d",&a,&b); for(int i=a;i<=b;i++) if(mu[i]>0)printf("+");else if(mu[i]<0)printf("-");else printf("0"); puts(""); } return 0; } if(typ[0]=='2'&&typ[1]=='g') { cnt=0; for(int i=2;i*i<mod;i++)if((mod-1)%i==0)pri[++cnt]=i,pri[++cnt]=(mod-1)/i; sort(pri+1,pri+cnt+1); for(int i=cnt-1;i>=1;i--) for(int j=i+1;j<=cnt;j++) if(pri[j]&&pri[j]%pri[i]==0){pri[i]=0;break;} sort(pri+1,pri+cnt+1); reverse(pri+1,pri+cnt+1); while(!pri[cnt])cnt--; int Q,a,b;scanf("%d",&Q); while(Q--) { scanf("%d%d%*d",&a,&b); for(int i=a;i<=b;i++) { char as='g'; for(int j=1;j<=cnt;j++)if(qpow(i,pri[j])==1){as='.';break;} printf("%c",as); } puts(""); } return 0; } return 0; }
day2t1
考场上SB只会40,出来发现50是个人都会
做法:50分做法:f[i][j][k]表示到第i个人,蓝阵营j人,鸭派k人的方案数,按城市排序暴力DP即可,滚动一维最好。
k=0做法:实际上就是把阵营和派系2个分开DP,然后方案数相乘就行了。
然鹅我两个考场上都没想到(或许也是心态问题)。
100分做法:发现k<=30,可以考虑在k^2*m的做法,本质是前两种做法的结合。发现一些学校不能在选某派的同时加入某阵营,但是其他学校选阵营和选派系似乎还是可以分开算。所以把未强制的学校按照k=0做法去计算,没有学校被强制的城市也如此做。对于有被强制的城市,注意在选择一个阵营以后要把这个城市所有学校全部加入该阵营。
时间复杂度:O(Tm(c+n+k^2))
code
#include<bits/stdc++.h> #define mem(a) memset(a,0,sizeof a) using namespace std; typedef pair<int,int> pii; typedef long long ll; const int mod=998244353,N=2555; int n,m,c,C0,C1,D0,D1,tot,all,ans,f[N][400],g[N][400],b[N],s[N],sz[N],del[N],fx[N],fy[N]; vector<pii>G[N]; void add(int&x,int y){x=x+y>=mod?x+y-mod:x+y;} int ask(int*f,int l,int r){return r<0?0:(l<=0?f[r]:(f[r]-f[l-1]+mod)%mod);} void clear() { for(int i=1;i<=n;i++)G[i].clear(); mem(f),mem(g),mem(b),mem(s),mem(sz),mem(del),mem(fx),mem(fy); ans=all=tot=0; } int main() { int T;scanf("%d",&T); while(T--) { scanf("%d%d%d%d%d%d",&n,&c,&C0,&C1,&D0,&D1); for(int i=1;i<=n;i++)scanf("%d%d",&b[i],&s[i]),sz[b[i]]+=s[i],all+=s[i]; scanf("%d",&m); for(int i=1,x;i<=m;i++)scanf("%d",&x),scanf("%d",&del[x]),++del[x]; if(C0+C1<all||D0+D1<all){puts("0"),clear();continue;} C0=all-C0,D0=all-D0; fx[0]=fy[0]=1; for(int i=1;i<=n;i++) if(!del[i])for(int j=D1;j>=s[i];j--)add(fy[j],fy[j-s[i]]); else G[b[i]].push_back(pii(s[i],del[i])); f[0][0]=1; for(int i=1;i<=c;i++) { if(G[i].empty()&&sz[i])for(int j=C1;j>=sz[i];j--)add(fx[j],fx[j-sz[i]]); if(G[i].empty())continue; memset(g,0,sizeof g); for(int j=sz[i];j<=C1;j++)for(int k=0;k<=tot;k++)g[j][k]=f[j-sz[i]][k]; int sz1=tot,sz2=tot; for(int j=0;j<G[i].size();j++) { int tp=G[i][j].second-1; if(tp/2) { sz1+=G[i][j].first; for(int l=0;l<=C1;l++) for(int k=sz1;k>=G[i][j].first;k--) add(f[l][k],f[l][k-G[i][j].first]); if(tp%2==0) { sz2+=G[i][j].first; for(int l=0;l<=C1;l++) for(int k=sz2;k>=G[i][j].first;k--) g[l][k]=g[l][k-G[i][j].first]; for(int l=0;l<=C1;l++)for(int k=G[i][j].first-1;k>=0;k--)g[l][k]=0; } } else{ sz2+=G[i][j].first; for(int l=0;l<=C1;l++) for(int k=sz2;k>=G[i][j].first;k--) add(g[l][k],g[l][k-G[i][j].first]); if(tp%2==0) { sz1+=G[i][j].first; for(int l=0;l<=C1;l++) for(int k=sz1;k>=G[i][j].first;k--) f[l][k]=f[l][k-G[i][j].first]; for(int l=0;l<=C1;l++) for(int k=G[i][j].first-1;k>=0;k--) f[l][k]=0; } } } tot=max(sz1,sz2); for(int i=0;i<=C1;i++)for(int j=0;j<=tot;j++)add(f[i][j],g[i][j]); } for(int i=1;i<=D1;i++)add(fy[i],fy[i-1]); for(int i=1;i<=C1;i++)add(fx[i],fx[i-1]); for(int i=0;i<=C1;i++) for(int j=0;j<=tot;j++) ans=(ans+1ll*f[i][j]*ask(fx,C0-i,C1-i)%mod*ask(fy,D0-j,D1-j))%mod; printf("%d\n",ans); clear(); } }
day2t2
考场上再一次因为心态爆炸,想问题复杂错失一条链的15pts
做法:其实是一个神奇的贪心。先考虑一条链,就是将两侧从大到小排序,取最大的加起来即可。这个做法实际上可以拓展成一棵树,就是对每棵树维护一个优先队列,然后树形DP的时候,将两个队列启发式合并,然后dfs完成后把根节点的优先队列中所有值加起来就行了
code
#include<bits/stdc++.h> using namespace std; const int N=2e5+7; priority_queue<int>q[N]; int n,cnt,w[N],deg[N],id[N],a[N]; vector<int>G[N]; long long ans; void dfs(int u) { id[u]=++cnt; for(int i=0;i<G[u].size();i++) { dfs(G[u][i]); if(q[id[u]].size()<q[id[G[u][i]]].size())swap(id[u],id[G[u][i]]); int k=q[id[G[u][i]]].size(); for(int j=1;j<=k;j++) { a[j]=max(q[id[u]].top(),q[id[G[u][i]]].top()); q[id[u]].pop(),q[id[G[u][i]]].pop(); } for(int j=1;j<=k;j++)q[id[u]].push(a[j]); } q[id[u]].push(w[u]); } int main() { scanf("%d",&n); for(int i=1;i<=n;i++)scanf("%d",&w[i]); for(int i=2,x;i<=n;i++)scanf("%d",&x),G[x].push_back(i); dfs(1); while(!q[id[1]].empty())ans+=q[id[1]].top(),q[id[1]].pop(); printf("%lld",ans); }
day2t3
坑。
说一下考场想到的36pts和后来想到的52pts:
36pts做法:
1、指数级别的复杂度暴力16分我也不想说了。
2、对于一条链的部分分,可以这样考虑:考虑k条线段所共有的保护区域中,左端点为i的方案数,然后可以扫描一遍,统计一下保护区域包含i的线段数s1和保护区域左端点为i的线段数s2,然后对于i点答案就是qpow(s1,k)-qpow(s1-s2,k),s1和s2可以O(1)数学方法求解,边界条件有点繁琐还容易错,然后n个点累加起来即可。考场上拍了过了,实际上也过了。
3、也许是一条链给我的启发,可以把一条链左端点为i扩展为一棵树以1为根i为深度最浅的点。然后由于时间不够,只想到了L=n的做法:由于连通块没有要求,所以不需考虑距离等繁琐问题,然后想到对于每个点维护s1(过i点的连通块数),s2(以1为根i为深度最浅的点的连通块数)。s2很显然可以一发树形DP求解,DP方程:f[u]=1ll*f[u]*(f[son]+1)%mod。然后s1可以换根DP求解,撤回操作只需一个逆元即可,复杂度O(nlogn)
52pts:代码先咕了,只口胡写不出来,思路好像是对的。就是O(nL)的树形DP,正着DP一遍,再反过来换根DP,记录f[i][j]表示以i为根长度为j的连通块,然后只能用一维,id函数转换一下。结合一条链和n=L即可获得52pts。
52pts code(LOJ又莫名RE,在luogu得的52)
#include<cstdio> #include<algorithm> #include<cstring> #include<vector> using namespace std; const int N=11e6,mod=998244353; int n,m,L,k,ans,sz[N],a[N],f[N],g[N],pre[N],suf[N]; vector<int>G[N]; int qpow(int a,int b) { int ret=1; while(b) { if(b&1)ret=1ll*ret*a%mod; a=1ll*a*a%mod,b>>=1; } return ret; } void dp(int u,int fa) { f[u]=1; for(int i=0;i<G[u].size();i++) if(G[u][i]!=fa)dp(G[u][i],u),f[u]=1ll*f[u]*(f[G[u][i]]+1)%mod; } void dp2(int u,int fa) { for(int i=0;i<G[u].size();i++) if(G[u][i]!=fa) g[G[u][i]]=(1ll*g[u]*qpow(f[G[u][i]]+1,mod-2)%mod+1)*f[G[u][i]]%mod,dp2(G[u][i],u); } bool chain() { for(int i=1;i<=n;i++)if(G[i].size()>2)return 0; return 1; } int id(int x,int y){return (x-1)*(L+2)+y;} void dfs(int u,int fa) { for(int i=0;i<G[u].size();i++)if(G[u][i]!=fa)dfs(G[u][i],u); for(int i=0;i<=L;i++)f[id(u,i)]=1; for(int i=0;i<G[u].size();i++) if(G[u][i]!=fa)for(int j=1;j<=L;j++) f[id(u,j)]=1ll*f[id(u,j)]*(f[id(G[u][i],j-1)]+1)%mod; } void dfs2(int u,int fa) { int tot=0; for(int i=0;i<G[u].size();i++)if(G[u][i]!=fa)a[++tot]=G[u][i]; pre[id(1,0)]=1;for(int j=1;j<=L;j++)pre[id(1,j)]=f[id(a[1],j-1)]+1; for(int i=2;i<=tot;i++) { pre[id(i,0)]=1; for(int j=1;j<=L;j++)pre[id(i,j)]=1ll*pre[id(i-1,j)]*(f[id(a[i],j-1)]+1)%mod; } suf[id(tot,0)]=1;for(int j=1;j<=L;j++)suf[id(tot,j)]=f[id(a[tot],j-1)]+1; for(int i=tot-1;i>0;i--) { suf[id(i,0)]=1; for(int j=1;j<=L;j++)suf[id(i,j)]=1ll*suf[id(i+1,j)]*(f[id(a[i],j-1)]+1)%mod; } for(int i=1;i<=tot;i++) { g[id(a[i],0)]=1; for(int j=1;j<=L;j++) { int x=i>1?pre[id(i-1,j-1)]:1,y=i<tot?suf[id(i+1,j-1)]:1; g[id(a[i],j)]=1ll*x*y%mod*g[id(u,j-1)]%mod+1; } } for(int i=0;i<G[u].size();i++)if(G[u][i]!=fa)dfs2(G[u][i],u); } int main() { scanf("%d%d%d",&n,&L,&k); for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x); if(chain()) { for(int i=1;i<=n;i++) { int l=max(1,i-L),r=min(n,i+L),s1,s2; s1=1ll*(r-i+1)*(i-l+1)%mod; s2=r-i+1; if(r==i+L)s2+=i-l; s2=(s1-s2+mod)%mod; ans=(1ll*ans+qpow(s1,k)-qpow(s2,k)+mod)%mod; } printf("%d",ans);return 0; } if(L==n) { dp(1,0),g[1]=f[1],dp2(1,0); for(int i=1,s1,s2;i<=n;i++) s1=g[i],s2=(s1-f[i]+mod)%mod,ans=(1ll*ans+qpow(s1,k)-qpow(s2,k)+mod)%mod; printf("%d",ans); return 0; } dfs(1,0); for(int j=0;j<=L;j++)g[id(1,j)]=1; dfs2(1,0); for(int i=1;i<=n;i++)ans=(ans+qpow(1ll*f[id(i,L)]*g[id(i,L)]%mod,k))%mod; if(L)for(int i=2;i<=n;i++)ans=(ans-qpow(1ll*f[id(i,L-1)]*(g[id(i,L)]+mod-1)%mod,k)+mod)%mod; printf("%d",ans); }