今天跟之前的考试最大的区别是一道题都没有A 很是桑心
不过今天拿了好多的暴力分,rank1啦
先放题解吧
首先第一题我们考虑一个前缀覆盖对答案的影响
显然影响到了是子树中没有没覆盖过的部分,但是由于这一部分过于扭曲
我们不是很容易计算
(后来听说每次暴力BFS理论时间复杂度连暴力都比不上就能A,感觉辣鸡数据啊,也是无力吐槽)
我们可以考虑打标记,每次将标记下传,遇到被覆盖过的部分就永久停止下传并计算贡献
但是考试的时候想到了标记这一步,但是总是想着用单点来表示答案,所以就无法处理标记的合并和分离
我们不妨用一条链的和来表示答案,那么我们考虑暴力做法就是在前缀位置挂上+1,在子树中每个被覆盖的子树挂上-1
之后直接在trie树上跑一跑就可以啦
这样我们就可以轻松的解决标记下传的问题啦,每次在前缀位置挂上+1,挂上延迟标记-1
之后下传的时候遇到被覆盖部分就停止下传就可以啦
由于这道题还有一维是时间,但是可以离线,我们离线把时间排序就可以消掉这一维啦
如果要求在线的话,可以参考题解的做法,利用可持久化trie或者线段树就可以啦
#include<cstdio> #include<cstring> #include<iostream> #include<cstdlib> #include<algorithm> using namespace std; const int maxn=100010; int n,m,cnt,L,R,len; int tot=1; char op[4]; char s[maxn][35]; int nxt[5000010][2]; int sum[5000010]; int vis[5000010]; int mark[5000010]; struct OP{ int type; char s[35]; }c[maxn]; struct ASK{ int type; int pos,id; }Q[maxn<<1]; int ans[maxn]; bool cmp(const ASK &A,const ASK &B){return A.pos<B.pos;} void down(int u){ if(mark[u]){ int L=nxt[u][0],R=nxt[u][1]; if(L){ if(vis[L])sum[L]+=mark[u]; else mark[L]+=mark[u]; } if(R){ if(vis[R])sum[R]+=mark[u]; else mark[R]+=mark[u]; }mark[u]=0; }return; } void insert(char *s,int v){ len=strlen(s+1); int now=1; for(int i=1;i<=len;++i){ int idx=s[i]-'0'; if(!nxt[now][idx])nxt[now][idx]=++tot; now=nxt[now][idx];down(now); }vis[now]+=v;sum[now]++;mark[now]--; } int Get_ans(char *s){ len=strlen(s+1); int ans=0,now=1; for(int i=1;i<=len;++i){ int idx=s[i]-'0'; if(!nxt[now][idx])break; now=nxt[now][idx];down(now); ans+=sum[now]; }return ans; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;++i){ scanf("%s",op+1); scanf("%s",c[i].s+1); if(op[1]=='A')c[i].type=1; else c[i].type=-1; } for(int i=1;i<=m;++i){ scanf("%s",s[i]+1); scanf("%d%d",&L,&R); ++cnt;Q[cnt].type=-1;Q[cnt].pos=L;Q[cnt].id=i; ++cnt;Q[cnt].type=1;Q[cnt].pos=R;Q[cnt].id=i; } sort(Q+1,Q+cnt+1,cmp); int now=1; for(int i=1;i<=n;++i){ insert(c[i].s,c[i].type); while(now<=cnt&&Q[now].pos==i){ ans[Q[now].id]+=Q[now].type*Get_ans(s[Q[now].id]); now++; } } for(int i=1;i<=m;++i)printf("%d ",ans[i]); return 0; }
第二题如果m<=500就是一个很简单的省选难度的DP题啦
我们建出AC自动机,由于要求最坏情况
所以我们只能逆推,设f(i,j,k)表示i->m步当前在自动机的j节点之后最多按错k次的答案
我们会发现对于一个决策u,我们的最坏情况是Min(f(i+1,nxt(j,c),k-1),f(i+1,nxt(j,u),k))(c!=u)
对于所有的决策我们只需要取Max就可以啦,这样DP方程就出来了
至于k=0的情况,我们发现没有k的限制,我们求的就是在AC自动机上走m步的答案
采用倍增floyd即可拿到这部分分
之后我们考虑到AC自动机的节点数很小,m很大
很容易想到如果AC自动机上有一条路径成为最优决策,那么m步就会不停的走这条路径
这样这条路径我们就可以把他看成循环节,循环节不会很大,易于证明它<cnt*(K+1)
这样我们用上述的DP就可以啦,判断是否循环节只需要判断前后两个dp数组的增量是否完全一样即可
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> #include<queue> using namespace std; typedef long long LL; const int maxn=521; int n,m,K,cnt,v,len; char s[maxn]; int fail[maxn]; int vis[maxn]; int nxt[maxn][26]; LL f[maxn][6]; LL g[maxn][6]; LL dp[maxn][maxn][6]; LL pre[32],suf[32]; LL ans=0; queue<int>Q; void insert(){ len=strlen(s+1); int now=1; for(int i=1;i<=len;++i){ int idx=s[i]-'a'; if(!nxt[now][idx])nxt[now][idx]=++cnt; now=nxt[now][idx]; }vis[now]+=v; } void build_fail(){ Q.push(1); while(!Q.empty()){ int u=Q.front();Q.pop(); vis[u]+=vis[fail[u]]; for(int i=0;i<26;++i){ int k=fail[u]; while(k&&!nxt[k][i])k=fail[k]; if(nxt[u][i]){ fail[nxt[u][i]]=k?nxt[k][i]:1; Q.push(nxt[u][i]); }else nxt[u][i]=k?nxt[k][i]:1; } }return; } int main(){ freopen("typewriter.in","r",stdin); freopen("typewriter.out","w",stdout); scanf("%d%d%d",&n,&m,&K);cnt=1; for(int i=1;i<=n;++i){ scanf("%s",s+1); scanf("%d",&v); insert(); }build_fail(); for(int i=1;i<=cnt;++i)for(int j=0;j<=K;++j)f[i][j]=vis[i]; for(int t=0;t<m;++t){ int nowt=t%(cnt+1); for(int i=1;i<=cnt;++i)for(int j=0;j<=K;++j)g[i][j]=f[i][j],f[i][j]=-1; for(int i=1;i<=cnt;++i){ for(int u=0;u<26;++u)f[i][0]=max(f[i][0],g[nxt[i][u]][0]); f[i][0]+=vis[i]; for(int k=1;k<=K;++k){ pre[0]=g[nxt[i][0]][k-1]; for(int u=1;u<26;++u)pre[u]=min(pre[u-1],g[nxt[i][u]][k-1]); suf[25]=g[nxt[i][25]][k-1]; for(int u=24;u>=0;--u)suf[u]=min(suf[u+1],g[nxt[i][u]][k-1]); for(int u=0;u<26;++u){ LL now=g[nxt[i][u]][k]; if(u>0)now=min(now,pre[u-1]); if(u<25)now=min(now,suf[u+1]); f[i][k]=max(f[i][k],now); }f[i][k]+=vis[i]; } } for(int i=1;i<=cnt;++i){ for(int j=0;j<=K;++j){ dp[nowt][i][j]=f[i][j]; } } if(nowt==cnt){ for(int L=1;L<=cnt;++L){ LL tmp=dp[cnt][cnt][K]-dp[cnt-L][cnt][K]; bool flag=true; for(int i=1;i<=cnt;++i){ for(int j=0;j<=K;++j){ if(dp[cnt][i][j]-dp[cnt-L][i][j]!=tmp){ flag=false;break; } } } if(flag){ LL now=(m-(t+1))/L; ans=ans+now*tmp; m=m-now*L; break; } } } } printf("%lld ",ans+f[1][K]); return 0; }
第三题考试的时候想出来了正解,但是实在是不想写高精,其实现在也不想写
所以就默默等待zcgA掉这道题目啦,目前还是没有高精度的40分
我们知道两个数的GCD的唯一分解式的指数取得是两个数的min
这样根据题目的条件,我们可以得到答案的唯一分解式的指数的限制
限制分为两类,一类是>=k,我们直接算倍数就可以了
另一类是=k,我们可以考虑容斥
即全集-至少一个不满足的+至少两个不满足的……
注意到10^30最多有20个质因子,所以我们的容斥是2^20的
至于求和我们化一化公式就可以O(1)的算了(但是要写高精度)
得到指数限制我分情况讨论的,写的有些繁琐
实际上对a/b,c/d分解质因子就可以了
先放上没有高精度的代码
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cstdlib> using namespace std; typedef long long LL; const int maxn=10000010; int T; bool flag; LL L,R,a,b,c,d; LL now,ans; LL st[maxn],top=0; bool vis[maxn]; int p[maxn],cnt=0; void Get_Prime(){ for(int i=2;i<=10000000;++i){ if(!vis[i])p[++cnt]=i; for(int j=1;j<=cnt;++j){ if(1LL*i*p[j]>10000000)break; vis[i*p[j]]=true; if(i%p[j]==0)break; } }return; } void Get_FJ(LL a,LL b,LL c,LL d){ for(int i=1;i<=cnt;++i){ int div=p[i]; LL a1=-1,c1=-1; LL a2=-1,c2=-1; if(b%div==0){ int t1=0,t2=0;a2=1; while(b%div==0)b/=div,t1++,a2*=div; while(a%div==0)a/=div,t2++; if(t1!=t2)a1=a2; } if(d%div==0){ int t1=0,t2=0;c2=1; while(d%div==0)d/=div,t1++,c2*=div; while(c%div==0)c/=div,t2++; if(t1!=t2)c1=c2; } if(b%div!=0&&a%div==0){ if(c2!=-1){flag=true;return;} st[++top]=div; while(a%div==0)a/=div; while(c%div==0)c/=div; continue; } if(d%div!=0&&c%div==0){ if(a2!=-1){flag=true;return;} st[++top]=div; while(c%div==0)c/=div; continue; } if(a1!=-1&&c1!=-1){ if(a1!=c1){flag=true;return;} now*=a1;st[++top]=div; }else if(a1==-1&&c1!=-1){ if(a2>c1){flag=true;return;} now*=c1;st[++top]=div; }else if(a1!=-1&&c1==-1){ if(c2>a1){flag=true;return;} now*=a1;st[++top]=div; }else{ LL k=max(a2,c2); if(k!=-1)now*=k; } } if(b>1)now*=b; if(d>1&&d!=b)now*=d; if(a>1&&a!=b)st[++top]=a; if(c>1&&c!=d)st[++top]=c; } void DFS(int pos,LL mul,int flag){ if(pos>top){ LL div=mul*now; LL A=R/div,B=(L-1)/div; LL sum=((A+1)*A-(B+1)*B)/2*div; if(flag)ans+=sum; else ans-=sum; return; } DFS(pos+1,mul*st[pos],flag^1); DFS(pos+1,mul,flag); } int main(){ freopen("riddle.in","r",stdin); freopen("riddle.out","w",stdout); scanf("%d",&T); Get_Prime(); while(T--){ scanf("%lld%lld%lld%lld%lld%lld",&L,&R,&a,&b,&c,&d); top=0;now=1;flag=false; Get_FJ(a,b,c,d); if(flag){printf("0 ");continue;} ans=0;DFS(1,1,1); printf("%lld ",ans); }return 0; }
这场考试较好的方面:
1、第一题貌似除了我之外集体爆零,原因是都没有认真读题QAQ
2、第二题在正推无果的情况下转换了思路,使用逆推从而正确推出了方程
3、第三题将题目条件转化成限制之后发现了容斥原理的模型,从而得到了正解
较差的方面:
1、第一题一直考虑子树修改和单点查询,从而不能处理标记问题
但是如果转换思路变成单点修改和链查询就很容易想到思路了,对于数据结构和信息处理的问题掌握不是很熟练
2、第二题有20分的暴力分没有想到倍增floyd而丢掉了
3、第三题犯懒不写高精度,不然就200+了QAQ