您的位置:首页 > 其它

高二&高一&初三模拟赛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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: