• 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
    ",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

  • 相关阅读:
    初步掌握HDFS的架构及原理
    Hadoop 生态系统
    安装Hadoop,让word count飞起来
    十分钟看透MapReduce
    初识Hadoop
    线性回归-理论篇
    逻辑回归
    hdu 4848 Wow! Such Conquering! (floyd dfs)
    hdu 4856 Tunnels (记忆化搜索)
    poj 3237 Tree [LCA] (树链剖分)
  • 原文地址:https://www.cnblogs.com/joyouth/p/5513256.html
Copyright © 2020-2023  润新知