最近两场cf总结
2015-10-25 14:39
281 查看
最近做了两场cf,通过了当中所有的题目,稍稍做下总结
Codeforces Round #325 (Div. 1)
这场是我排名最靠前的一次(23),虽然04写的比较慢,但是幸运的猜出了03,05和06都是短代码的可做题,然而并没有时间去看,这说明代码能力还是很重要的
A:有n个小孩依次去看牙齿,每个小孩看的时候会对后面的小孩造成等差递减的伤害,假如小孩不能承受这个伤害就会逃跑,问最后有几个小孩去看了牙齿,n<=4000
分析:比较简单的题目,暴力模拟每个小孩的看牙情况,用一个数组记录小孩是否还在队列当中
B:给出一张3*n的地图,有些位置有列车,列车每次向左行驶两格,行人最初在第一列,每次向右走一格,之后可以决定向上或者向下走一格(不超出地图),问行人最后是否有可能到达最后一列。
分析:可以转换坐标系,变成行人每次向右走三格之后决定向上或向下,之后就是一个简单的dp
C:A最初只有一个苹果,B最初只有一个橘子,现在要你构造一个01串操作序列,假如操作是0,则B拿A当前所具有苹果和橘子,否则A对B做相同的事,要求最后A和B的苹果数之和为x,橘子数之和为y.(x,y<=1018x,y<=10^{18})
分析:是一道比较巧妙的数学题,比赛的时候用欧几里德算法带进去算了一下,正好满足!于是就通过了这题
题解给了一个不为人知的数据结构:https://en.wikipedia.org/wiki/Stern%E2%80%93Brocot_tree
D:D是一个挺无聊的折半暴搜,虽然很简单,但是还是码了我一个小时之久,主要是一开始题目看错了。。。。
E:n个数,从中选出一个非空集合,在另外选出一个数,要求集合的gcd不为1且这个gcd和另外选择的数互质
分析:比较常用的筛法,假如把题目变成选择两个不同的数,要求gcd为1,那么我们可以先求cnt[x]代表是x的倍数的数有多少个cnt[x]代表是x的倍数的数有多少个,筛一遍之后,我们可以先忽略不同这个要求,那么gcd是x的倍数的选法就是cnt[x]2cnt[x]^2,之后再套用莫比乌斯函数暴力容斥即可得到答案,最后再减去1的数目即可。
对于这道题,也类似搞搞,不同之处在于他要求所选集合gcd不为1,因此我们最后再剪掉选择集合gcd==1形成的方案数,而后面也可以用莫比乌斯暴力容斥
代码极短
F:给一个长<1000的数字串,给两个长度均为d且不含前导0的L和R,要求L~R之间有多少个串满足至少有一个长度>=d/2的子串是原数字串的子串。d<=50
这道题比赛的时候并没有人通过这道题,当时也是被吓的题目都没读过。然而今天读了一下,发现难度比我想象的要低很多。L~R之间有多少个满足的,看这样的数据范围很容易想到数位dp,关键是状态,由于要求是原串的子串,那么我们只需要知道当前匹配的最长原串子串是怎么样的即可。很容易想到后缀自动机,由于长度只有1000,那么总结点数也只有2000,放在状态里面一点也不过分。当然,后缀自动机只知道在那个节点还并不能知道当前串长是多少,由于d只有50,我们可以在一维代表长度,转移的时候暴力枚举填充的数就可以通过这道题
Codeforces Round #326 (Div. 1)
这场就没有上一场那么幸运,可能和比赛时间有一定关系,当时就感觉眼睛都睁不开了,很难进行思考,02题目看错,在wa中度过了余生,03写了树链剖分和暴力合并,结果被卡掉了,仔细想想这两题任何一道题通过了名次都还不至于这么差。。。。
A:给出一个数列,每次可以删除一个∑2a[i]=2x\sum 2^{a[i]}=2^x的数列,问最少需要删几次。
分析:显然的贪心即可,因为只有某个数的个数>=2才可以向后删,因此就for一遍,当某个数出现次数>=2,就暴力去删一次,仔细考虑清楚细节即可
B:给出一个长为l周期为n的数列,从中选出长度<=k的数列,要求每个数出自连续的不同周期,问方案数。n∗k<=106n*k<=10^6
分析:比较明显的前缀和dp,主要比赛的时候没看到连续,可怕的是还能过样例,所以说下次比较简单的题目一直wa就该去重新读一下题目!
C:给出一棵树,询问q次,每次询问树上的前K大(K<=10)
分析:显然用主席树求树上第K大毫无疑问可以通过这题,不过代码量有些大,不在考虑范围;因为只有10个,显然可以用线段树暴力合并,不用思考的做法就是外面套一层树链剖分就可以维护链上查询了,然而这个方法tle了;考虑到合并的复杂度显得有些大,我们可以只维护最小值,然后修改完之后查询,这样虽然复杂度没有变,但是常数变小了,有人这样通过了这题;正解是倍增,因为只有查询没有修改,用倍增维护10个值然后暴力合并,就可以少掉1个log,足以通过这题。
D:给出一张图,每条边有一个颜色和费用,要求删去一个没有公共点的边集,使得剩下的相同颜色的边无公共点,且最大的费用最小。
分析:最大费用最小,就是在提醒我们二分答案,之后就转化成某些边一定要保留,能否1.每个点至多删去一条边,使得2.剩下的相同颜色的边无公共点。根据这两个限制进行建图,由于第一个条件可能会导致边数爆炸,因此我们可以将每个点排序,对每个前缀新建一个节点,对每个后缀新建一个节点,之后再连边边数就变成O(n)了,之后再暴力跑2sat,由于有些边一定要保留,就先对那些边对应的变量跑一遍dfs,剩下的仍然是一个对称图,可以使用O(n)的强联通分量算法,当然,我还是写了暴力dfs,实际效果跑得也非常快,注意2-sat的连边方式:A->B则连A到B和~B到~A两条边
E:给一个序列,每次可以给L~R的数^K,或者询问L~R的数高斯消元后基的数量。
分析:一开始死脑筋想线段树维护区间的基,然而对于区间修改线段树并没办法维护一个集合^K之后的基。这里就有一个常用技巧,维护差分序列!由于要求基,那么相邻两个异或一遍再求并不影响结果,因此本题就变成单点修改的线段树暴力。类似的技巧还出现在上次维护区间gcd,然而这次碰到了还是没想到这个科技
F:给出n个串,q次询问,每次询问l,r,k,输出l~r中的串在第k个串中出现次数和,n,q<=10W,保证串长之和<=10w;
分析:快速询问A串在B串中出现的次数,很常规的做法就是建出fail树,然后把B所经过的节点标为1,然后查询A串终结节点所在子树的权值和即可。
对于这道题,套用类似的方法发现复杂度并不正确,看这种数据规模可以按照串长分块:
对于比较短的串,最多只有O(q)个询问,每次询问一个点到根的路径上有多少个权值位于L,R之间,显然这样的询问最多只有O(Q∗n−−√)O(Q*\sqrt n)个,根据询问的性质,一个点上的权值只会对子树造成影响,因此我们可以考虑dfs的同时动态修改、询问当前每种权值的个数,考虑到询问的次数比较多,可以对权值也进行分块,就能使询问O(1)O(1),修改O(n−−√)O(\sqrt n),因此这一部分的复杂度就是O(n−−√)O(\sqrt n)
对于比较长的串,显然这样的串不会多于O(n−−√)O(\sqrt n)个,因此只要将询问离线,暴力询问每个串每个节点的子树有多少个目标串的节点即可
Codeforces Round #325 (Div. 1)
这场是我排名最靠前的一次(23),虽然04写的比较慢,但是幸运的猜出了03,05和06都是短代码的可做题,然而并没有时间去看,这说明代码能力还是很重要的
A:有n个小孩依次去看牙齿,每个小孩看的时候会对后面的小孩造成等差递减的伤害,假如小孩不能承受这个伤害就会逃跑,问最后有几个小孩去看了牙齿,n<=4000
分析:比较简单的题目,暴力模拟每个小孩的看牙情况,用一个数组记录小孩是否还在队列当中
B:给出一张3*n的地图,有些位置有列车,列车每次向左行驶两格,行人最初在第一列,每次向右走一格,之后可以决定向上或者向下走一格(不超出地图),问行人最后是否有可能到达最后一列。
分析:可以转换坐标系,变成行人每次向右走三格之后决定向上或向下,之后就是一个简单的dp
C:A最初只有一个苹果,B最初只有一个橘子,现在要你构造一个01串操作序列,假如操作是0,则B拿A当前所具有苹果和橘子,否则A对B做相同的事,要求最后A和B的苹果数之和为x,橘子数之和为y.(x,y<=1018x,y<=10^{18})
分析:是一道比较巧妙的数学题,比赛的时候用欧几里德算法带进去算了一下,正好满足!于是就通过了这题
题解给了一个不为人知的数据结构:https://en.wikipedia.org/wiki/Stern%E2%80%93Brocot_tree
D:D是一个挺无聊的折半暴搜,虽然很简单,但是还是码了我一个小时之久,主要是一开始题目看错了。。。。
E:n个数,从中选出一个非空集合,在另外选出一个数,要求集合的gcd不为1且这个gcd和另外选择的数互质
分析:比较常用的筛法,假如把题目变成选择两个不同的数,要求gcd为1,那么我们可以先求cnt[x]代表是x的倍数的数有多少个cnt[x]代表是x的倍数的数有多少个,筛一遍之后,我们可以先忽略不同这个要求,那么gcd是x的倍数的选法就是cnt[x]2cnt[x]^2,之后再套用莫比乌斯函数暴力容斥即可得到答案,最后再减去1的数目即可。
对于这道题,也类似搞搞,不同之处在于他要求所选集合gcd不为1,因此我们最后再剪掉选择集合gcd==1形成的方案数,而后面也可以用莫比乌斯暴力容斥
代码极短
#include<bits/stdc++.h> using namespace std; const int Maxn=10000002,M=1e9+7; typedef long long Int; int miu[Maxn],cnt[Maxn]; int n; int xp[500020]; int main() { xp[0]=1; scanf("%d",&n); for(int i=1;i<=n;i++)xp[i]=(xp[i-1]+xp[i-1])%M; for(int i=1;i<=n;i++) { int x;scanf("%d",&x); cnt[x]++; } int ans=0; miu[1]=1; for(int i=1;i<Maxn;i++) { for(int j=i+i;j<Maxn;j+=i) { miu[j]-=miu[i]; cnt[i]+=cnt[j]; } if(!cnt[i]||!miu[i])continue; ans+=miu[i]*(xp[cnt[i]]-1)*(Int)(cnt[i]-n)%M; if(ans>=M)ans-=M; if(ans<0)ans+=M; } printf("%d\n",ans); }
F:给一个长<1000的数字串,给两个长度均为d且不含前导0的L和R,要求L~R之间有多少个串满足至少有一个长度>=d/2的子串是原数字串的子串。d<=50
这道题比赛的时候并没有人通过这道题,当时也是被吓的题目都没读过。然而今天读了一下,发现难度比我想象的要低很多。L~R之间有多少个满足的,看这样的数据范围很容易想到数位dp,关键是状态,由于要求是原串的子串,那么我们只需要知道当前匹配的最长原串子串是怎么样的即可。很容易想到后缀自动机,由于长度只有1000,那么总结点数也只有2000,放在状态里面一点也不过分。当然,后缀自动机只知道在那个节点还并不能知道当前串长是多少,由于d只有50,我们可以在一维代表长度,转移的时候暴力枚举填充的数就可以通过这道题
#include<bits/stdc++.h> using namespace std; const int Maxn=2020,M=1e9+7; string s; int last,sz; int ml[Maxn],f[Maxn],ch[Maxn][10]; void add(int c) { int p=last; int np=++sz; ml[np]=ml[p]+1; last=np; for(;p&&!ch[p][c];p=f[p])ch[p][c]=np; if(!p){f[np]=1;return;} int q=ch[p][c]; if(ml[q]==ml[p]+1){f[np]=q;return;} int nq=++sz; memcpy(ch[nq],ch[q],sizeof(ch[q])); ml[nq]=ml[p]+1; f[nq]=f[q]; f[q]=f[np]=nq; for(;p&&ch[p][c]==q;p=f[p])ch[p][c]=nq; } inline void up(int &x,int y){x+=y;if(x>=M)x-=M;} int dp[52][26][2020][2][2]; int dfs(int cur,int curlen,int curst,bool can,bool has) { int &t=dp[cur][curlen][curst][can][has]; if(cur>=s.size()){return t=has;} if(t>=0)return t; t=0; for(int i=0;i<10;i++) { if(!can&&i+'0'>s[cur])continue; bool ncan=(i+'0'==s[cur])?can:1; bool nhas=has; int nst,nlen; if(has) { nhas=1;nlen=0; nst=1; up(t,dfs(cur+1,nlen,nst,ncan,nhas)); } else { if(ch[curst][i]){nst=ch[curst][i];nlen=curlen+1;} else { nst=curst; while(nst&&!ch[nst][i])nst=f[nst]; if(!nst){nst=1;nlen=0;} else {nlen=ml[nst]+1;nst=ch[nst][i];} } if(nlen>=s.size()/2){nhas=1;nst=1;nlen=0;} //if(cur==1&&curlen==1&&i==2)printf("nlen=%d nst=%d ncan=%d nhas=%d\n",nlen,nst,ncan,nhas); up(t,dfs(cur+1,nlen,nst,ncan,nhas)); } } return t; } int solve() { memset(dp,-1,sizeof(dp)); return dfs(0,0,1,0,0); } int main() { cin>>s; last=sz=1; for(int i=0;i<s.size();i++)add(s[i]-'0'); //printf("ch=%d\n",ch[2][2]); cin>>s; reverse(s.begin(),s.end()); for(int i=0;i<s.size();i++) { if(s[i]=='0')s[i]='9'; else {s[i]--;break;} } reverse(s.begin(),s.end()); solve(); int ans=(M-solve())%M; cin>>s; ans+=solve();if(ans>=M)ans-=M; printf("%d\n",ans); }
Codeforces Round #326 (Div. 1)
这场就没有上一场那么幸运,可能和比赛时间有一定关系,当时就感觉眼睛都睁不开了,很难进行思考,02题目看错,在wa中度过了余生,03写了树链剖分和暴力合并,结果被卡掉了,仔细想想这两题任何一道题通过了名次都还不至于这么差。。。。
A:给出一个数列,每次可以删除一个∑2a[i]=2x\sum 2^{a[i]}=2^x的数列,问最少需要删几次。
分析:显然的贪心即可,因为只有某个数的个数>=2才可以向后删,因此就for一遍,当某个数出现次数>=2,就暴力去删一次,仔细考虑清楚细节即可
B:给出一个长为l周期为n的数列,从中选出长度<=k的数列,要求每个数出自连续的不同周期,问方案数。n∗k<=106n*k<=10^6
分析:比较明显的前缀和dp,主要比赛的时候没看到连续,可怕的是还能过样例,所以说下次比较简单的题目一直wa就该去重新读一下题目!
C:给出一棵树,询问q次,每次询问树上的前K大(K<=10)
分析:显然用主席树求树上第K大毫无疑问可以通过这题,不过代码量有些大,不在考虑范围;因为只有10个,显然可以用线段树暴力合并,不用思考的做法就是外面套一层树链剖分就可以维护链上查询了,然而这个方法tle了;考虑到合并的复杂度显得有些大,我们可以只维护最小值,然后修改完之后查询,这样虽然复杂度没有变,但是常数变小了,有人这样通过了这题;正解是倍增,因为只有查询没有修改,用倍增维护10个值然后暴力合并,就可以少掉1个log,足以通过这题。
D:给出一张图,每条边有一个颜色和费用,要求删去一个没有公共点的边集,使得剩下的相同颜色的边无公共点,且最大的费用最小。
分析:最大费用最小,就是在提醒我们二分答案,之后就转化成某些边一定要保留,能否1.每个点至多删去一条边,使得2.剩下的相同颜色的边无公共点。根据这两个限制进行建图,由于第一个条件可能会导致边数爆炸,因此我们可以将每个点排序,对每个前缀新建一个节点,对每个后缀新建一个节点,之后再连边边数就变成O(n)了,之后再暴力跑2sat,由于有些边一定要保留,就先对那些边对应的变量跑一遍dfs,剩下的仍然是一个对称图,可以使用O(n)的强联通分量算法,当然,我还是写了暴力dfs,实际效果跑得也非常快,注意2-sat的连边方式:A->B则连A到B和~B到~A两条边
#include<bits/stdc++.h> using namespace std; const int Maxn=50020,Inf=1e9+7; int n,m,tot; int tl[Maxn],col[Maxn]; int uu[Maxn],vv[Maxn]; bool cmp(int a,int b){return col[a]<col[b];} vector<int>G[Maxn];//edge in vertix vector<int>G2[Maxn<<4];//edge in two-sat,x<<1:del,x<<1|1:not del vector<int>rep; bool mark[Maxn<<4]; int top,S[Maxn<<4]; bool dfs(int u) { if(mark[u^1])return 0; if(mark[u])return 1; mark[u]=1; S[top++]=u; for(int i=0;i<G2[u].size();i++) { if(!dfs(G2[u][i]))return 0; } return 1; } bool check(int Tl,int ptmod=0) { top=0; for(int i=0;i<tot<<1;i++)mark[i]=0; //for(int i=0;i<G2[0].size();i++)printf("ok%d ",G2[0][i]);puts(""); for(int i=0;i<m;i++) { if(tl[i]>Tl)//bixubaoliu { if(mark[i<<1])return 0; if(mark[i<<1|1])continue; if(!dfs(i<<1|1))return 0; } } for(int i=0;i<m;i++) { if(!mark[i<<1]&&!mark[i<<1|1]) { if(!dfs(i<<1|1)) { while(top)mark[S[--top]]=0; if(!dfs(i<<1))return 0; if(ptmod)rep.push_back(i); } } else if(mark[i<<1]&&ptmod)rep.push_back(i); } if(ptmod) { puts("Yes"); printf("%d %d\n",Tl,(int)rep.size()); for(int i=0;i<rep.size();i++)printf("%d%c",rep[i]+1,i==rep.size()?'\n':' '); } return 1; } int main() { scanf("%d%d",&n,&m); for(int i=0;i<m;i++) { scanf("%d%d%d%d",uu+i,vv+i,col+i,tl+i); G[uu[i]].push_back(i); G[vv[i]].push_back(i); } bool flag=1; tot=m; for(int i=1;i<=n&&flag;i++) { vector<int>&V=G[i]; sort(V.begin(),V.end(),cmp); for(int j=0,k;j<V.size();j=k) { vector<int>tp; for(k=j;k<V.size()&&col[V[k]]==col[V[j]];k++)tp.push_back(V[k]); if(tp.size()>2){flag=false;break;} if(tp.size()==2) { G2[tp[0]<<1|1].push_back(tp[1]<<1); G2[tp[1]<<1|1].push_back(tp[0]<<1); } } vector<int>pre,suf; for(int j=0;j<V.size();j++) { int t=tot++; pre.push_back(t); G2[t<<1].push_back(V[j]<<1);G2[t<<1|1].push_back(V[j]<<1|1); if(j)G2[t<<1].push_back((t-1)<<1),G2[t<<1|1].push_back((t-1)<<1|1); } for(int j=V.size()-1;j>=0;j--) { int t=tot++; suf.push_back(t); G2[t<<1].push_back(V[j]<<1);G2[t<<1|1].push_back(V[j]<<1|1); if(j<V.size()-1)G2[t<<1].push_back((t-1)<<1),G2[t<<1|1].push_back((t-1)<<1|1); } reverse(suf.begin(),suf.end()); for(int j=0;j<V.size();j++) { int t=V[j]; if(j)G2[t<<1].push_back(pre[j-1]<<1|1); if(j<V.size()-1)G2[t<<1].push_back(suf[j+1]<<1|1); } } //if(tot>=(Maxn<<4))while(1); //if(m>40000)return 0; if(!flag||!check(Inf)){puts("No");return 0;} if(check(0)){puts("Yes");puts("0 0");return 0;} int L=0,R=Inf; while(L+1<R) { int mid=(L+R)>>1; if(check(mid))R=mid; else L=mid; } check(R,1); }
E:给一个序列,每次可以给L~R的数^K,或者询问L~R的数高斯消元后基的数量。
分析:一开始死脑筋想线段树维护区间的基,然而对于区间修改线段树并没办法维护一个集合^K之后的基。这里就有一个常用技巧,维护差分序列!由于要求基,那么相邻两个异或一遍再求并不影响结果,因此本题就变成单点修改的线段树暴力。类似的技巧还出现在上次维护区间gcd,然而这次碰到了还是没想到这个科技
#include<bits/stdc++.h> using namespace std; #define ls l,mid,x<<1 #define rs mid+1,r,x<<1|1 const int Maxn=200020; vector<int>tree[Maxn<<2]; int n,q; int a[Maxn],b[Maxn],c[Maxn]; inline int low(int x){return x&-x;} int ask(int loc){int ret=0;for(int i=loc;i>=1;i-=low(i))ret^=c[i];return ret;} int add(int loc,int w){for(int i=loc;i<=n;i+=low(i))c[i]^=w;} void gauss(vector<int>&V) { for(int i=0,j=0;i<32&&j<V.size();i++) { int cs=-1; for(int k=j;k<V.size();k++) { if(V[k]>>i&1){cs=k;break;} } if(cs<0)continue; if(cs!=j){swap(V[cs],V[j]);} for(int k=0;k<V.size();k++) { if(k!=j&&(V[k]>>i&1))V[k]^=V[j]; } j++; } while(V.size()&&!V.back())V.pop_back(); } vector<int> operator+(const vector<int>&V1,const vector<int>&V2) { vector<int>ret=V1; for(int i=0;i<V2.size();i++)ret.push_back(V2[i]); gauss(ret); return ret; } void build(int l,int r,int x) { if(l==r) { scanf("%d",a+l); b[l]=a[l]^a[l-1]; add(l,b[l]); if(b[l])tree[x].push_back(b[l]); return; } int mid=(l+r)>>1; build(ls);build(rs); tree[x]=tree[x<<1]+tree[x<<1|1]; } void modify(int tar,int w,int l,int r,int x) { if(l==r){b[l]^=w;add(l,w);if(tree[x].size())tree[x].pop_back();if(b[l])tree[x].push_back(b[l]);return;} int mid=(l+r)>>1; if(tar<=mid)modify(tar,w,ls); else modify(tar,w,rs); tree[x]=tree[x<<1]+tree[x<<1|1]; } vector<int>query(int L,int R,int l,int r,int x) { if(L>R){vector<int>tp;return tp;} if(L<=l&&R>=r)return tree[x]; int mid=(l+r)>>1; vector<int>ret; if(L<=mid)ret=ret+query(L,R,ls); if(R>mid)ret=ret+query(L,R,rs); return ret; } int main() { scanf("%d%d",&n,&q); build(1,n,1); while(q--) { int ty,l,r,k; scanf("%d%d%d",&ty,&l,&r); if(ty==1) { scanf("%d",&k); modify(l,k,1,n,1); if(r<n)modify(r+1,k,1,n,1); } else { vector<int>rep=query(l+1,r,1,n,1); rep.push_back(ask(l)); gauss(rep); printf("%d\n",1<<rep.size()); } } }
F:给出n个串,q次询问,每次询问l,r,k,输出l~r中的串在第k个串中出现次数和,n,q<=10W,保证串长之和<=10w;
分析:快速询问A串在B串中出现的次数,很常规的做法就是建出fail树,然后把B所经过的节点标为1,然后查询A串终结节点所在子树的权值和即可。
对于这道题,套用类似的方法发现复杂度并不正确,看这种数据规模可以按照串长分块:
对于比较短的串,最多只有O(q)个询问,每次询问一个点到根的路径上有多少个权值位于L,R之间,显然这样的询问最多只有O(Q∗n−−√)O(Q*\sqrt n)个,根据询问的性质,一个点上的权值只会对子树造成影响,因此我们可以考虑dfs的同时动态修改、询问当前每种权值的个数,考虑到询问的次数比较多,可以对权值也进行分块,就能使询问O(1)O(1),修改O(n−−√)O(\sqrt n),因此这一部分的复杂度就是O(n−−√)O(\sqrt n)
对于比较长的串,显然这样的串不会多于O(n−−√)O(\sqrt n)个,因此只要将询问离线,暴力询问每个串每个节点的子树有多少个目标串的节点即可
#include<bits/stdc++.h> using namespace std; typedef long long Int; const int Maxn=200020; int n,q,sz,last,len; int ml[Maxn],f[Maxn],ch[Maxn][26]; vector<int>G[Maxn]; string ss[Maxn]; void add(int c) { int p=last; if(ch[p][c]) { int q=ch[p][c]; if(ml[q]==ml[p]+1)last=q; else { int nq=++sz; memcpy(ch[nq],ch[q],sizeof(ch[q])); ml[nq]=ml[p]+1; last=nq; f[nq]=f[q]; f[q]=nq; for(;p&&ch[p][c]==q;p=f[p])ch[p][c]=nq; } } else { int np=++sz;last=np; for(;p&&!ch[p][c];p=f[p])ch[p][c]=np; if(!p){f[np]=1;return;} int q=ch[p][c]; if(ml[q]==ml[p]+1){f[np]=q;return;} int nq=++sz; memcpy(ch[nq],ch[q],sizeof(ch[q])); ml[nq]=ml[p]+1; f[nq]=f[q]; f[q]=f[np]=nq; for(;p&&ch[p][c]==q;p=f[p])ch[p][c]=nq; } } void insert(string &s) { last=1; for(int i=0;i<s.size();i++) { add(s[i]-'a'); } } int ql[Maxn],qr[Maxn],qk[Maxn],block; Int rep[Maxn],tp[Maxn],tpele[Maxn]; vector<int>Q1; vector<int>Q2[Maxn]; vector<int>End[Maxn]; int pre[Maxn],fin[Maxn],dfs_t; int cntblock[500],cntele[Maxn]; bool cmp(int a,int b){return qk[a]<qk[b];} void process(int u) { for(int i=0;i<=n;i++)tp[i]=0; for(int i=1;i<=sz;i++)tpele[i]=0; for(int i=0,j=1;i<ss[u].size();i++) { j=ch[j][ss[u][i]-'a']; tpele[pre[j]]++; } for(int i=1;i<=sz;i++)tpele[i]+=tpele[i-1]; for(int i=1;i<=n;i++) { tp[i]=tp[i-1]; int k=1; for(int j=0;j<ss[i].size();j++) k=ch[k][ss[i][j]-'a']; tp[i]+=tpele[fin[k]]-tpele[pre[k]-1]; } } void solve1() { sort(Q1.begin(),Q1.end(),cmp); for(int i=0,j;i<Q1.size();i=j) { int u=qk[Q1[i]]; process(u); for(j=i;j<Q1.size()&&qk[Q1[j]]==u;j++) { int id=Q1[j]; rep[id]+=tp[qr[id]]-tp[ql[id]-1]; } } } void add(int loc,int w) { loc--; int t=loc/block; for(int i=0;i<t;i++)cntblock[i]+=w; for(int i=t*block;i<=loc;i++)cntele[i]+=w; } int ask(int loc) { loc--; if(loc>=n)return 0; return cntele[loc]+cntblock[loc/block]; } void dfs(int u) { pre[u]=++dfs_t; for(int i=0;i<End[u].size();i++) { add(End[u][i],1); } for(int i=0;i<Q2[u].size();i++) { int id=Q2[u][i]; rep[id]+=ask(ql[id])-ask(qr[id]+1); } for(int i=0;i<G[u].size();i++)dfs(G[u][i]); for(int i=0;i<End[u].size();i++)add(End[u][i],-1); fin[u]=dfs_t; } int main() { scanf("%d%d",&n,&q); last=sz=1; for(int i=1;i<=n;i++)cin>>ss[i],insert(ss[i]),len+=ss[i].size(); len=sqrt(len+0.5); for(int i=1;i<=q;i++) { scanf("%d%d%d",ql+i,qr+i,qk+i); if(ss[qk[i]].size()>=len) { Q1.push_back(i); } else { for(int k=0,j=1;k<ss[qk[i]].size();k++) { j=ch[j][ss[qk[i]][k]-'a']; Q2[j].push_back(i); } } } block=sqrt(n+0.5); for(int i=1;i<=n;i++) { int k=1; for(int j=0;j<ss[i].size();j++) { k=ch[k][ss[i][j]-'a']; } End[k].push_back(i); } for(int i=1;i<=sz;i++)G[f[i]].push_back(i); dfs(1); solve1(); for(int i=1;i<=q;i++)printf("%lld\n",rep[i]); }
相关文章推荐
- ZOJ-3869 Ace of Aces
- NYOJ 138 找球号(二)(哈希)
- 堆叠顺序 和 推断是否成对括号
- TypeScript学习笔记(一):介绍及环境搭建
- 可行性研究报告
- MySQL 存储过程检测表是否存在
- 【codevs 1329】东风谷早苗
- hdu(5441)——Travel
- Java 笔试:常见题目总结
- HashMap的一般用法以及遍历方法
- Linux 安装花生壳解析动态域名
- Java算法题:求素数
- R语言笔记003——set.seed()函数
- 棋盘游戏
- dispatch_sync dispatch_async有什么区别?通熟易懂的解释
- 写的一个音乐播放器界面
- Android中Broadcast Receiver组件详解
- 一个由proguard与fastJson引起的血案
- web前端之HTML简介
- Oracle 的数据类型