比赛链接:https://ac.nowcoder.com/acm/contest/11259
A,D,E,J,K,10,不错。开场E题签到,一小时左右做出构造题K。开始看D和A;A读了半天题,写了写,WA了;又改了改,两小时的时候过了。D过了一会也做出来了。讨论了一番J,发现是要递归,四小时多一点过了。然后F题有个感觉很对的做法,最后三十五分钟开始写,写完WA了,也不知哪里错了?似乎也没有别人用这个做法了。
交流有时候挺费劲,双方都有原因。我这方面的课题,是应对我所在的izhi。主要是关于kimochi和zhizilioku啦。适应或改变?kimochi或zhizilioku?
当然了,我全都要!
D
分析:
一开始摸不着头脑,只想到由于(c)序列,所以确定(a_1)就确定了整个(a)序列。然后利用(b)的部分信息,可以确定每个(a_i)的一个大致范围……
就是这题,三人一起想;交流得很不顺畅。后来G说想出来了,口胡了一堆什么,然后写了一会,过了。行吧。
现在补题,原来是有一个“关键观察”:( a_i + a_{i+1} = ( a_i | a_{i+1} ) + ( a_i & a_{i+1} ) ),妙啊!然后就可以分开每一位考虑了,而且确定( a_1 )整个(a)就确定了。
原来如此简单,呵。
代码如下:
#include<iostream> #define ll long long using namespace std; int const N=1e5+5; int n,b[N],c[N]; int main() { scanf("%d",&n); for(int i=2;i<=n;i++)scanf("%d",&b[i]); for(int i=2;i<=n;i++)scanf("%d",&c[i]),c[i]-=b[i]; ll ans=1; for(int t=0;t<30;t++) { int f0=1,f1=1,n0=0,n1=1; for(int i=2;i<=n;i++) { if((b[i]&(1<<t))&&(c[i]&(1<<t))) { if(f0){if(!n0)f0=0; else n0=1;} if(f1){if(!n1)f1=0; else n1=1;} } if((b[i]&(1<<t))&&!(c[i]&(1<<t))) { if(f0)n0^=1; if(f1)n1^=1; } if(!(b[i]&(1<<t))&&(c[i]&(1<<t))) { puts("0"); return 0; } if(!(b[i]&(1<<t))&&!(c[i]&(1<<t))) { if(f0){if(n0)f0=0; else n0=0;} if(f1){if(n1)f1=0; else n1=0;} } if(!f0&&!f1)break; } //printf("t=%d f0=%d f1=%d ",t,f0,f1); ans*=(f0+f1); } printf("%lld ",ans); return 0; }
E
分析:
签到题。判断一个年份是否既是闰年又是质数。其实直接输出“no”就行,是闰年怎么可能是质数呢?哈哈。
F
分析:
比赛时想了各种连边、图之类的做法,但都没成功。这题其实可以暴力一点,因为没有特别精细的要求,走的方式也很有规律。
第一种和第二种机器人都很容易做,我们看第三种:
考虑对行进行分治。对于当前行的范围( l,r ),关注( mid=frac{l+r}{2} )这一行;用它来解决起点在( mid )上方、终点在( mid )下方的询问。
怎么解决呢?由于起点到终点的路线一定会经过这一行的某个点,我们可以枚举这一点,然后bfs求出这一点向上能走到的区域和向下能走到的区域;如果同时包含起点和终点,那么问题就解决了。这样暴力做,时间复杂度是( O(n^3) )的。
然后,起点终点都在( mid )上方的询问递归到上半部分解决,都在( mid )下方的询问递归到下半部分解决。
这样,解决询问的时间复杂度是( O(qlogn) ),因为每个询问均摊( O(logn) );分治的每一层时间复杂度( O(n^3) ),而分治一共有( logn )层。总复杂度是( O(qlogn+n^3logn) )。有点大。
但是,还有优化的余地。找( mid )这一行中每个点能走到的区域时,有很多重复的路线,因为这些点彼此可走到的区域有很多重复。如何加快这个过程呢?
实际上,我们也可以用DP来做这个过程;如果全图每个点( (i,j) )记录一个( dp[i][j] )表示能走到( mid )中的哪些点,转移是很容易的。然而,如果我们直接让两个点之间状态( O(n) )地转移,复杂度就是( O(n^3) )。和( mid )行上每个点来一遍bfs异曲同工。
但是,我们发现( dp[i][j] )记录的东西是能否走到( mid )行的每个点。换言之,是一系列的( 0 )和( 1 )。而转移就是把能走到当前格子的那些格子状态中的( 1 )直接拿过来。
所以我们可以用bitset优化,用bitset来存状态,转移就是( | )操作。这下,转移的时间复杂度就变成( O(frac{n}{32}) )了!
这样就能过了。时间复杂度( O(qlogn + frac{n^3logn}{32}) )。
写的时候注意分治边界和询问的特判。
代码如下:
#include<iostream> #include<vector> #include<bitset> #define mid ((l+r)>>1) #define pb push_back using namespace std; int const N=505,M=5e5+5; int n,m,q,ans[M]; char s[N][N]; bitset<N>f[N][N],g[N][N]; struct Nd{ int x1,y1,x2,y2,id; }; int work(int t,int x1,int y1,int x2,int y2) { if(t==1) { if(y1!=y2)return 0; if(x1>x2)return 0;/// for(int i=x1;i<=x2;i++) if(s[i][y1]=='1')return 0; return 1; } if(t==2) { if(x1!=x2)return 0; if(y1>y2)return 0;/// for(int j=y1;j<=y2;j++) if(s[x1][j]=='1')return 0; return 1; } } void solve(int l,int r,vector<Nd> v) { if(l>r)return;/// for(int j=m;j;j--) { f[mid][j].reset(); if(s[mid][j]=='1')continue; f[mid][j][j]=1; f[mid][j]|=f[mid][j+1]; } for(int j=1;j<=m;j++) { g[mid][j].reset(); if(s[mid][j]=='1')continue; g[mid][j][j]=1; g[mid][j]|=g[mid][j-1]; } for(int i=mid-1;i>=l;i--) for(int j=m;j;j--) { f[i][j].reset(); if(s[i][j]=='1')continue; f[i][j]|=f[i][j+1]; f[i][j]|=f[i+1][j]; } for(int i=mid+1;i<=r;i++) for(int j=1;j<=m;j++) { g[i][j].reset(); if(s[i][j]=='1')continue; g[i][j]|=g[i][j-1]; g[i][j]|=g[i-1][j]; } vector<Nd>vl,vr; for(Nd it:v) { if(it.x2<mid)vl.pb(it); else if(it.x1>mid)vr.pb(it); else ans[it.id]=(f[it.x1][it.y1]&g[it.x2][it.y2]).any(); //printf("mid=%d id=%d ans=%d ",mid,it.id,ans[it.id]); } v.clear(); if(vl.size())solve(l,mid-1,vl); if(vr.size())solve(mid+1,r,vr); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%s",s[i]+1); scanf("%d",&q); vector<Nd>v; for(int i=1,t,x1,y1,x2,y2;i<=q;i++) { scanf("%d%d%d%d%d",&t,&x1,&y1,&x2,&y2); if(t==1||t==2)ans[i]=work(t,x1,y1,x2,y2); else { if(x1<x2&&y1>y2)continue; v.pb((Nd){x1,y1,x2,y2,i}); } } solve(1,n,v); for(int i=1;i<=q;i++)puts((ans[i])?"yes":"no"); return 0; }
J
分析:
把问题简化一下。找出( s )到( t )的那条链,链上每个点延伸出去的部分只有最长链是需要考虑的。所以现在问题是两人分别从( s )和( t )出发,相互靠近,某一时刻可以从链上离开走那个点的最长链,剩下那个人就可以在剩下的节点中任意选择一个进入其最长链,这样一个博弈问题。
可以直接用递归模拟这个博弈过程,用ST表快速找到剩下那个人应该走哪个点的最长链。
递归时注意状态的含义!想当然地写最优决策而忽略当前状态的话,会在某一两个点一直WA。
代码如下:
#include<iostream> #include<cstring> #include<cmath> using namespace std; int const N=5e5+5,inf=1e9; int n,s,t,hd[N],cnt,nxt[N<<1],to[N<<1],fa[N],dep[N],p[N],pc; int st1[N][21],st2[N][21]; bool is[N]; void add(int x,int y){nxt[++cnt]=hd[x]; hd[x]=cnt; to[cnt]=y;} void dfs(int u) { dep[u]=0; for(int i=hd[u],v;i;i=nxt[i]) { if((v=to[i])==fa[u]||is[v])continue; fa[v]=u; dfs(v); dep[u]=max(dep[u],dep[v]+1); } } /* int get1(int l,int r) { int ret=0; for(int i=20;i>=0;i--) if(l+(1<<i)-1<=r)ret=max(ret,st1[l][i]),l+=(1<<i); return ret; } int get2(int l,int r) { int ret=0; for(int i=20;i>=0;i--) if(l+(1<<i)-1<=r)ret=max(ret,st2[l][i]),l+=(1<<i); return ret; } */ int get1(int l,int r) { int j=(int)log2(r-l+1); return max(st1[l][j],st1[r-(1<<j)+1][j]); } int get2(int l,int r) { int j=(int)log2(r-l+1); return max(st2[l][j],st2[r-(1<<j)+1][j]); } int work2(int u,int v); int work1(int u,int v)//一人在u,一人在v时 { if(u==v-1)return st1[u][0]-st2[v][0];//st1[u][0]-get2(v,pc); return max(st1[u][0]-get2(u+1,v),work2(u+1,v));///not "get2(u+1,pc)" } int work2(int u,int v) { if(u==v-1)return st1[u][0]-st2[v][0];//get1(1,u)-st2[v][0]; return min(get1(u,v-1)-st2[v][0],work1(u,v-1));///not "get1(1,v-1)" } int main() { scanf("%d%d%d",&n,&s,&t); for(int i=1,u,v;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u); dfs(t); int nw=s; p[++pc]=s; is[s]=1; while(nw!=t)nw=fa[nw],p[++pc]=nw,is[nw]=1; for(int i=1;i<=pc;i++)dfs(p[i]); for(int i=1;i<=pc;i++) st1[i][0]=i-1+dep[p[i]],st2[i][0]=pc-i+dep[p[i]]; for(int j=1;j<=20;j++) for(int i=1;i+(1<<j)-1<=pc;i++) st1[i][j]=max(st1[i][j-1],st1[i+(1<<(j-1))][j-1]), st2[i][j]=max(st2[i][j-1],st2[i+(1<<(j-1))][j-1]); printf("%d ",work1(1,pc)); return 0; }