高二&高一&初三模拟赛21 总结
2017-09-27 09:14
405 查看
T1:纸牌游戏(SMOJ1979)
题目分析:水题一道。先对p分解质因数,将质因数存在cnt数组里,将其对应的幂存在num数组里。然后看一下a数组的第i个元素分别含有多少个cnt[j],存在val[i][j]里。由于题目的每一对(x,y)都对应a数组的一个闭区间,我们只需要知道区间的左端点为L的时候,其右端点R的最小值,这样答案就可以多n-R+1(因为R更大的时候区间的乘积显然也会是p的倍数)。而L变大的时候,R不会变小,于是用一个类似尺取法的方法单调向右扫即可。只要存储区间里每一个val[x][j](L<=x<=R)的和,与num[j]比较,即可判断区间乘积是否为p的倍数。时间复杂度O(nlog(p))。CODE:
#include<iostream> #include<string> #include<cstring> #include<cmath> #include<cstdio> #include<cstdlib> #include<stdio.h> #include<algorithm> using namespace std; const int maxn=100100; const int maxm=40; typedef long long LL; int prime[maxm]; int num[maxm]; int cur=0; int val[maxn][maxm]; int a[maxn]; int now[maxm]; int n,k; LL ans=0; bool Judge() { for (int i=1; i<=cur; i++) if (now[i]<num[i]) return false; return true; } int main() { freopen("1979.in","r",stdin); freopen("1979.out","w",stdout); scanf("%d%d",&n,&k); for (int i=1; i<=n; i++) scanf("%d",&a[i]); if (k==1) { ans=(long long)n*(long long)(n+1)/2LL; printf("%lld\n",ans); return 0; } int sk=(int)floor( sqrt( (double)k )+1e-3 ); for (int i=2; i<=sk; i++) if (!(k%i)) { prime[++cur]=i; while (!(k%i)) num[cur]++,k/=i; } if (k>1) prime[++cur]=k,num[cur]=1; for (int i=1; i<=n; i++) for (int j=1; j<=cur; j++) while (!(a[i]%prime[j])) a[i]/=prime[j],val[i][j]++; int tail=0; while ( !Judge() && tail<=n ) { tail++; for (int i=1; i<=cur; i++) now[i]+=val[tail][i]; } for (int i=1; i<=n; i++) { if (tail>n) break; ans+=(long long)(n-tail+1); for (int j=1; j<=cur; j++) now[j]-=val[i][j]; while ( !Judge() && tail<=n ) { tail++; for (int j=1; j<=cur; j++) now[j]+=val[tail][j]; } } printf("%lld\n",ans); return 0; }
T2:XOR(SMOJ1980)
题目分析:由于在GDKOI2016的时候见到过一题类似的线段树+异或前缀和,所以这题作为前者的弱化版很快就切了。首先异或是没有分配律的,所以不能直接存sum来异或。又因为它是位运算,我们考虑开20棵线段树,分别存20~219的信息。如果a[i]第2j位是1,它在第j+1棵线段树的对应位置权值就是1,线段树的非叶子节点存区间的和。要对[L,R]异或x的时候,逐个查看x的第2j位是不是1,是的话对第j+1棵线段树的[L,R]区间进行取反操作。查询答案的时候将第j棵线段树里区间的权值和乘以2j−1,加起来即可。CODE:
#include<iostream> #include<string> #include<cstring> #include<cmath> #include<cstdio> #include<cstdlib> #include<stdio.h> #include<algorithm> using namespace std; const int maxn=100100; const int maxm=20; typedef long long LL; struct Tnode { bool flip; int num; } tree[maxm][maxn<<2]; int a[maxn]; int Two[maxm]; int n,m; void Build(int root,int L,int R) { if (L==R) { for (int i=0; i<maxm; i++) tree[i][root].num=(bool)(a[L]&Two[i]); return; } int mid=(L+R)>>1; int Left=root<<1; int Right=Left|1; Build(Left,L,mid); Build(Right,mid+1,R); for (int i=0; i<maxm; i++) tree[i][root].num=tree[i][Left].num+tree[i][Right].num; } void Down(int id,int root,int L,int R) { if (tree[id][root].flip) { int mid=(L+R)>>1; int Left=root<<1; int Right=Left|1; tree[id][Left].flip^=1; tree[id][Right].flip^=1; tree[id][Left].num=(mid-L+1)-tree[id][Left].num; tree[id][Right].num=(R-mid)-tree[id][Right].num; tree[id][root].flip=false; } } LL Query(int root,int L,int R,int x,int y) { if ( y<L || R<x ) return 0LL; if ( x<=L && R<=y ) { LL sum=0; for (int i=0; i<maxm; i++) sum+=( (long long)tree[i][root].num*(long long)Two[i] ); return sum; } int mid=(L+R)>>1; int Left=root<<1; int Right=Left|1; for (int i=0; i<maxm; i++) Down(i,root,L,R); LL vl=Query(Left,L,mid,x,y); LL vr=Query(Right,mid+1,R,x,y); return (vl+vr); } void Update(int id,int root,int L,int R,int x,int y) { if ( y<L || R<x ) return; if ( x<=L && R<=y ) { tree[id][root].flip^=1; tree[id][root].num=(R-L+1)-tree[id][root].num; return; } int mid=(L+R)>>1; int Left=root<<1; int Right=Left|1; Down(id,root,L,R); Update(id,Left,L,mid,x,y); Update(id,Right,mid+1,R,x,y); tree[id][root].num=tree[id][Left].num+tree[id][Right].num; } int main() { freopen("1980.in","r",stdin); freopen("1980.out","w",stdout); Two[0]=1; for (int i=1; i<maxm; i++) Two[i]=Two[i-1]<<1; scanf("%d",&n); for (int i=1; i<=n; i++) scanf("%d",&a[i]); Build(1,1,n); scanf("%d",&m); for (int i=1; i<=m; i++) { int t,L,R; scanf("%d%d%d",&t,&L,&R); if (t==1) { LL ans=Query(1,1,n,L,R); printf("%lld\n",ans); } else { int x; scanf("%d",&x); for (int i=0; i<maxm; i++) if (x&Two[i]) Update(i,1,1,n,L,R); } } return 0; }
T3:豆腐(SMOJ1981)
题目分析:做了这题才发现自己的数位DP真是菜。还记得省选前模拟赛的时候曾经见过一道AC自动机+数位DP,那个时候因为AC自动机还没学熟就没有做;后来暑假参加百度之星,复赛的时候又见到一道数位DP,那时我和tututu码了60分钟还是没码出来,导致没了纪念衫,心情非常沮丧(tututu直接申请回家了)。现在又见到一题AC机+数位DP,明明离考试结束还有75min的时候就会做了,以为能AC的,结果还是没码出,比赛结束的时候连编译都没通过,结果这题就未提交……其实这题不难,我们对数字串建一个AC机,每个节点用一个域val存它在fail树到根的路径上的麻辣值之和(一开始每个数字串的尾节点存该数字串的麻辣值),代表走到P节点的时候豆腐的麻辣值加了P->val。然后再在每个节点开一个数组f[i][j],代表从这个节点无限制地往下走i步,获得的麻辣值刚好为j的方案数是多少,很明显:
P−>f[i][j]=∑k=0m−1P−>son[k]−>f[i−1][j−P−>val]
那如何统计答案呢?我们做一次差分,将题目变为求0~L-1与0~R中满足条件的数字个数。假设我们要求0~x中满足条件的数字个数,x=259,则先统计一位数与两位数的答案,此时对数字的限制只有最高位不为0这一条件,暴力枚举最高位即可。
当数字为三位数的时候,它不仅受最高位不为0这一限制,还可能受到x每一位数字的限制,于是我们暴力枚举三位数的每一位是多少。假设最高位为1,剩下的就无限制,调用Root->son[1]的f数组更新答案。再考虑第一位已经为2,而第二位为0~4的情况,剩下的位置同样不受限制,调用Root->son[2]->son[0~4]的f数组更新答案(注意要将k减去Root->son[2]->val,因为固定了最高位为2)……依此类推即可。
CODE:
#include<iostream> #include<string> #include<cstring> #include<cmath> #include<cstdio> #include<cstdlib> #include<stdio.h> #include<algorithm> using namespace std; const int maxn=210; const int maxk=510; const int maxm=23; const int M=1000000007; struct Tnode { int f[maxn][maxk]; int val; Tnode *son[maxm],*fsto; } tree[maxn]; Tnode *Root; int cur=-1; Tnode *que[maxn]; int head=0,tail=1; int L[maxn]; int R[maxn]; int a[maxn][maxn]; int v[maxn]; int n,m,k; void Read(int *r) { scanf("%d",&r[0]); for (int i=1; i<=r[0]; i++) scanf("%d",&r[i]); } Tnode *New_node() { cur++; tree[cur].fsto=NULL; for (int i=0; i<m; i++) tree[cur].son[i]=NULL; return tree+cur; } void Insert(int *r,int x) { int len=r[0]; Tnode *P=Root; for (int i=1; i<=len; i++) { int to=r[i]; if (!P->son[to]) P->son[to]=New_node(); P=P->son[to]; } P->val+=x; } void Bfs() { que[1]=Root; Root->f[0][0]=1; while (head<tail) { Tnode *F=que[++head]; for (int i=0; i<m; i++) if (F->son[i]) { Tnode *P=F->son[i],*Node=F->fsto; while ( Node && !Node->son[i] ) Node=Node->fsto; if (!Node) P->fsto=Root; else P->fsto=Node->son[i]; P->val+=P->fsto->val; if (P->val<=k) P->f[0][ P->val ]=1; if (P->val>=M) P->val-=M; que[++tail]=P; } } for (int i=0; i<m; i++) if (!Root->son[i]) Root->son[i]=Root; for (int i=2; i<=tail; i++) for (int j=0; j<m; j++) if (!que[i]->son[j]) que[i]->son[j]=que[i]->fsto->son[j]; } int Work(int *r) { int temp=0,s=k; for (int i=1; i<r[0]; i++) { for (int j=1; j<m; j++) for (int w=0; w<=k; w++) { temp+=Root->son[j]->f[i-1][w]; if (temp>=M) temp-=M; } } for (int i=1; i<r[1]; i++) for (int w=0; w<=k; w++) { temp+=Root->son[i]->f[ r[0]-1 ][w]; if (temp>=M) temp-=M; } Tnode *P=Root->son[ r[1] ]; k-=Root->son[ r[1] ]->val; for (int i=2; i<=r[0]; i++) if (k>=0) { for (int j=0; j<r[i]; j++) for (int w=0; w<=k; w++) { temp+=P->son[j]->f[ r[0]-i ][w]; if (temp>=M) temp-=M; } P=P->son[ r[i] ]; k-=P->val; } if (k>=0) for (int w=0; w<=k; w++) { temp+=P->f[0][w]; if (temp>=M) temp-=M; } k=s; return temp; } void Dec() { L[ L[0] ]--; int x=L[0]; while (L[x]<0) L[x]+=m,x--,L[x]--; if (!L[1]) { L[0]--; for (int i=L[0]; i>=1; i--) L[i]=L[i+1]; } } int main() { freopen("1981.in","r",stdin); freopen("1981.out","w",stdout); scanf("%d%d%d",&n,&m,&k); Read(L); Read(R); for (int i=1; i<=n; i++) Read(a[i]),scanf("%d",&v[i]); Root=New_node(); for (int i=1; i<=n; i++) Insert(a[i],v[i]); Bfs(); int len=max(L[0],R[0]); for (int i=1; i<=len; i++) for (int j=1; j<=tail; j++) { Tnode *P=que[j]; int z=P->val; for (int w=0; w<m; w++) for (int x=z; x<=k; x++) { int &y=P->f[i][x]; y+=P->son[w]->f[i-1][x-z]; if (y>=M) y-=M; } } int ans=Work(R); Dec(); ans-=Work(L); if (ans<0) ans+=M; printf("%d\n",ans); return 0; }
总结:这次比赛前两题都做得还可以,就是T3的数位DP细节很多,程序错漏百出,赛后又写了30min才写出来。以后要加强数位DP。
相关文章推荐
- 高二&高一&初三模拟赛25 总结
- 高二&高一&初三模拟赛17 总结
- 高二&高一&初三模拟赛24 总结
- 高二&高一&初三模拟赛20 总结
- 高二&高一&初三模拟赛27 总结
- 高二&高一&初三模拟赛14 总结
- 高二&高一&初三模拟赛16 总结
- 高二&高一&初三模拟赛17 总结
- 高二&高一&初三模拟赛19 总结
- 高二&高一&初三模拟赛16 总结
- 高二&高一&初三模拟赛23 总结
- 高二&高一&初三模拟赛26 总结
- 高二&高一&初三模拟赛22 总结
- 高二&高一模拟赛13 总结
- 高二&高一&初三模拟赛27 总结
- 高二&高一&初三模拟赛25 总结
- 高二&高一&初三模拟赛24 总结
- 高二&高一&初三模拟赛22 总结
- 高二&高一&初三模拟赛15 总结
- NOIP 2015模拟赛 题解&总结