5.20 考试修改和总结
今天跟之前的考试最大的区别是一道题都没有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\n",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\n",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\n");continue;}
ans=0;DFS(1,1,1);
printf("%lld\n",ans);
}return 0;
}
这场考试较好的方面:
1、第一题貌似除了我之外集体爆零,原因是都没有认真读题QAQ
2、第二题在正推无果的情况下转换了思路,使用逆推从而正确推出了方程
3、第三题将题目条件转化成限制之后发现了容斥原理的模型,从而得到了正解
较差的方面:
1、第一题一直考虑子树修改和单点查询,从而不能处理标记问题
但是如果转换思路变成单点修改和链查询就很容易想到思路了,对于数据结构和信息处理的问题掌握不是很熟练
2、第二题有20分的暴力分没有想到倍增floyd而丢掉了
3、第三题犯懒不写高精度,不然就200+了QAQ

浙公网安备 33010602011771号