您的位置:首页 > 其它

最近两场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形成的方案数,而后面也可以用莫比乌斯暴力容斥

代码极短

#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]);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: