比赛链接:https://ac.nowcoder.com/acm/contest/11256
B,D,H,K,15,差点做出J。
H签到题。B是期望题,Y很快推出来了,但是写了写WA了,就先搁置;G在写K,但写不出来,后来发现做法有点问题。我在看D,想了一会,然后G说了个DP,我也觉得行,于是他去写了,写完又WA了,也先搁置了。又看了看别的题,还是回来看D,G换了种DP方式,就过了;又看看B,忽然发现没排序,排序后就过了;然后G又想到K的另种简单做法,也过了。这之后就做J,我想了个做法,写完却WA了,仔细想想有点不对,因为那些关于t的二次函数在原点右边也可以有交点。这题应该是个二分图匹配;G上去写网络流,但是T了;Y又写KM算法,复杂度应该没问题,但还是一直T。然后比赛结束。讲题说J的正解就是KM算法,不知是哪里写错了。
总之我心情还是挺轻松的。有句话说得好啊,比赛中只有队伍,没有个人!心思重会很疲惫。
B
分析:
( C )最多花一次,而且要花就在最开始花;
所以有两种策略,一种是全开,代价( sum w_i );
另一种是先花( C ),然后按( w_i )升序一个一个往后开,开到后缀全是同色为止,代价( C + sum w_i * (1-frac{1}{2^{n-i}} ) ),意思是如果后面( n-i )个都确定了,那第( i )个也不用开了。
C
分析:
利用(W,L)的前缀和;记录每个(W,L)的位置;记录分数相同时的终点位置;然后一段一段地判断即可。
小心(n=1或n=2)时,不要让for循环陷入死循环^_^
代码如下:
#include<iostream> #include<cstdio> #define ll long long using namespace std; int const N=1e6+5,md=998244353; int n,p,sumw[N],suml[N],posw[N],posl[N],tie[N]; int f[N]; char s[N]; void init() { tie[n]=n+1; tie[n-1]=n+1; //for(int i=n-2;i;i--)tie[i]=(s[i+1]==s[i+2])?i+2:tie[i+2]; for(int i=n-2;i>=1;i--)tie[i]=(s[i+1]==s[i+2])?i+2:tie[i+2];///!!! for(int i=1;i<=n;i++) { sumw[i]=sumw[i-1]+(s[i]=='W'); suml[i]=suml[i-1]+(s[i]=='L'); if(s[i]=='W')posw[sumw[i]]=i; if(s[i]=='L')posl[suml[i]]=i; } } void work(int k) { if(!p||p>n)return; int cw=sumw[p-1],cl=suml[p-1]; if(cw+k<=sumw[n])//W先达到k局 { int psw=posw[cw+k]; if(suml[psw]-cl<=k-2){f[k]++; p=psw+1; return;} if(suml[psw]-cl==k-1) { if(psw==n){p=psw+1; return;} else if(s[psw+1]=='W'){f[k]++; p=psw+2; return;} else { if(tie[psw+1]<=n)f[k]+=(s[tie[psw+1]]=='W'); p=tie[psw+1]+1; return; } } } if(cl+k<=suml[n])//L先达到k局 { int psl=posl[cl+k]; if(sumw[psl]-cw<=k-2){p=psl+1; return;} if(sumw[psl]-cw==k-1) { if(psl==n){p=psl+1; return;} else if(s[psl+1]=='L'){p=psl+2; return;} else { if(tie[psl+1]<=n)f[k]+=(s[tie[psl+1]]=='W'); p=tie[psl+1]+1; return; } } } p=n+1; return; } int main() { scanf("%d%s",&n,s+1); init(); for(int i=1;i<=n;i++) { p=1; while(p+i-1<=n)work(i); } int ans=0,mul=1; for(int i=1;i<=n;i++) ans=(ans+(ll)f[i]*mul%md)%md,mul=(ll)mul*(n+1)%md; printf("%d ",ans); return 0; }
D
分析:
想到枚举( a_i < b_j )的点了,然后前面一个DP是相同的子序列方案数,后面一个DP是两段字符串任意取相同长度子序列的方案数;但是这DP一时间没想清楚。其实这种DP很简单的。
代码如下:
#include<iostream> #include<cstring> #define ll long long using namespace std; int const N=5005,md=1e9+7; int n,m,f[N][N],g[N][N],ans; char a[N],b[N]; int main() { scanf("%s%s",a+1,b+1); n=strlen(a+1); m=strlen(b+1); for(int i=0;i<=n;i++)f[i][0]=g[i][0]=1;//i=0 for(int j=0;j<=m;j++)f[0][j]=g[0][j]=1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) g[i][j]=((ll)g[i-1][j]+g[i][j-1]-g[i-1][j-1]+g[i-1][j-1])%md; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { f[i][j]=((ll)f[i-1][j]+f[i][j-1]+md-f[i-1][j-1])%md;// if(a[i]==b[j])f[i][j]=((ll)f[i][j]+f[i-1][j-1])%md; if(a[i]<b[j])ans=((ll)ans+(ll)f[i-1][j-1]*g[n-i][m-j]%md)%md; } printf("%d ",ans); return 0; }
E
分析:
因为有( d )的存在,每次值都会变,再搞位运算很困难;
但是发现( d )的范围只有100。所以可以对每个( d )都做一次,这样就可以只关注位运算了。当然,这样的话每个( d )需要( O(n) )出结果。
但是没关系。像这种子树内所有路径如何如何的问题,容易想到树形DP。关键是如何转移三种运算的答案。
设( f[u][0/1/2] )分别表示以( u )为根的子树下所有路径的或/与/异或值。我们针对当前( u )的儿子( v ),考虑三个值如何转移。
如果( u —> v )这条边上的运算是“或”:
对于( f[u][0] ):子树内所有路径值 ( | w_u)后再全部( | )起来,同先( | )起来以后再 ( | w_u )。所以 ( f[u][0] |= w_u | f[v][0] )
对于( f[u][1] ):子树内所有路径值 ( | w_u )后再全部 ( & )起来,同先 ( & )起来以后再 ( | w_u )。所以 ( f[u][1] &= w_u | f[v][1] )
对于( f[u][2] ):子树内所有路径值( | w_u )后再全部 ( igoplus )起来。由于所有路径值在 ( w_u ) 为(1)的那几位上都是(1),所以这些位完全与异或的次数有关,也就是与(v)子树的大小有关。这些位异或奇数次全(1),异或偶数次全(0)。其他位与( w_u )就没有关系了,所以其他位就是( f[v][2] )。这里想要分开位做,可以通过( & w_u )和 ( & (sim w_u) )实现。
如果( u —> v )这条边上的运算是“与”:
( & )这个操作,不管放在内层还是外层,( & 0)就是(0),( & 1)就是原来的数。所以内层或外层、操作几次都是一样的。当然不放心的话就自己再稍微想一想。
对于( f[u][0] ):( f[u][0] |= w_u & f[v][0] )
对于( f[u][1] ):( f[u][1] &= w_u & f[v][1] )
对于( f[u][2] ):( f[u][2] igoplus= w_u & f[v][2] )
如果( u —> v )这条边上的运算是“异或”:
对于( f[u][0] ):分别考虑( w_u )是(0)的位和是(1)的位。是(0)的位对原来那些路径值没有影响,所以还是( f[v][0] )。是(1)的位会把路径值上对应位都取反;而取反以后再( | )起来的效果和取反以前( & )起来的效果是一样的。这挺奇妙,自己验证一下或者想一想就可知。所以是(1)的位要用的是( f[v][1] )。
对于( f[u][1] ):完全同上。但这里写的时候要注意呀!!因为是( & )操作,所以分开位的时候不能分别( & )呀!要 ( | )起来再整体 ( & )呀!TAT
对于( f[u][2] ):都是异或,具有结合律,括号随便拆。所以( f[u][2] igoplus= f[v][2] ),如果(v)子树大小是奇数,再( igoplus w_u )。
做完这些以后还要注意个小细节,就是每个点算答案的时候是不包含自己的,但是转移的时候又需要把自己转移上去。所以这里另开一个数组存答案吧。
代码如下:
#include<iostream> #include<algorithm> #include<cstring> #define ll long long using namespace std; int const N=1e5+5; int n,q,hd[N],cnt,nxt[N],to[N],s[N],d,siz[N]; ll a[N],f[N][3],ans[N][3]; struct Nd{ int d,u,id; ll a0,a1,a2; }qr[N]; bool cmp(Nd x,Nd y){return x.d<y.d;} bool cmp2(Nd x,Nd y){return x.id<y.id;} ll rd() { ll ret=0,f=1; char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();} while(ch>='0'&&ch<='9')ret=(ret<<3)+(ret<<1)+ch-'0',ch=getchar(); return ret*f; } void add(int x,int y,int t){nxt[++cnt]=hd[x]; hd[x]=cnt; to[cnt]=y; s[cnt]=t;} void dfs(int u) { ll w=a[u]+u*d; siz[u]=1; f[u][0]=0; f[u][1]=(1ll<<62)-1; f[u][2]=0; for(int i=hd[u],v;i;i=nxt[i]) { dfs(v=to[i]); siz[u]+=siz[v]; if(s[i]==0) { f[u][0]|=(w|f[v][0]); f[u][1]&=(w|f[v][1]); f[u][2]^=( (~w) & f[v][2] ); if(siz[v]&1)f[u][2]^=w; } if(s[i]==1) { f[u][0]|=(w&f[v][0]); f[u][1]&=(w&f[v][1]); f[u][2]^=(w&f[v][2]); } if(s[i]==2) { f[u][0]|=( (~w) & f[v][0] ); f[u][0]|=( w & (~f[v][1]) ); //f[u][1]&=( (~w) & f[v][1] ); //f[u][1]&=( w & (~f[v][0]) ); f[u][1]&=( ( (~w) & f[v][1] ) | ( w & (~f[v][0]) ) );//! // if(!d&&u==3&&v==2)printf("f[u][1]=%d w=%d (%d)&(%d)=%d (%d)&(%d)=%d ", // f[u][1],w,~w,f[v][1],(~w)&f[v][1],w,~f[v][0],w&(~f[v][0])); f[u][2]^=f[v][2]; if(siz[v]&1)f[u][2]^=w; } //if(d==0)printf("u=%d v=%d f[%d][1]=%d f[%d][1]=%d ",u,v,v,f[v][1],u,f[u][1]); } // if(siz[u]==1) // f[u][0]=w,f[u][1]=w,f[u][2]=w; for(int i=0;i<=2;i++)ans[u][i]=f[u][i]; f[u][0]|=w; f[u][1]&=w; f[u][2]^=w; } int main() { n=rd(); q=rd(); for(int i=1;i<=n;i++)a[i]=rd(); for(int i=2,f,t;i<=n;i++) f=rd(),t=rd(),add(f,i,t); for(int i=1;i<=q;i++)qr[i].d=rd(),qr[i].u=rd(),qr[i].id=i; sort(qr+1,qr+q+1,cmp); d=-1; for(int i=1;i<=q;i++) { if(d!=qr[i].d)d=qr[i].d,dfs(1); int u=qr[i].u; qr[i].a0=ans[u][0]; qr[i].a1=ans[u][1]; qr[i].a2=ans[u][2]; } sort(qr+1,qr+q+1,cmp2); for(int i=1;i<=q;i++) printf("%lld %lld %lld ",qr[i].a0,qr[i].a1,qr[i].a2); return 0; }
G
分析:
是……子集DP。看了G的博客。做法很直观,枚举子集的技巧挺不错的。
__int128 要自己写输出。
H
分析:
就00110011....和11001100...交替即可。
J
分析:
要给每个东西选一个时间拿;也就是( n*n )的矩阵,每行每列只能选一个,求最小总和。
行作为一边,列作为一边,用KM算法做最小权值二分图匹配即可。
板子不好背,总之积累下来了。这里放一下G的代码。
代码如下:
#include<bits/stdc++.h> #define ll long long using namespace std; const int MAXN=310; const ll Inf=1e18; int n; ll Ans,Edge[MAXN][MAXN]; namespace KM { int Pre[MAXN],Cp[MAXN]; ll Delta,Slack[MAXN],Val1[MAXN],Val2[MAXN]; bool Vis1[MAXN],Vis2[MAXN]; void Match(int St) { int Le,Ri=0,Nr=0; for(int i=0;i<=n;i++) Pre[i]=0,Slack[i]=Inf; for(Cp[Ri]=St;Cp[Ri]!=-1;Ri=Nr) { Le=Cp[Ri],Delta=Inf,Vis2[Ri]=1; for(int i=1;i<=n;i++) { if(Vis2[i]) continue ; if(Slack[i]>Val1[Le]+Val2[i]-Edge[Le][i]) Slack[i]=Val1[Le]+Val2[i]-Edge[Le][i],Pre[i]=Ri; if(Slack[i]<Delta) Delta=Slack[i],Nr=i; } for(int i=0;i<=n;i++) if(Vis2[i]) Val1[Cp[i]]-=Delta,Val2[i]+=Delta; else Slack[i]-=Delta; } while(Ri) Cp[Ri]=Cp[Pre[Ri]],Ri=Pre[Ri]; } void Solve() { for(int i=0;i<=n;i++) Val1[i]=Val2[i]=0,Cp[i]=-1; for(int i=1;i<=n;i++) fill(Vis2,Vis2+n+1,0),Match(i); for(int i=1;i<=n;i++) Ans+=Edge[Cp[i]][i]*(Cp[i]!=-1); } }using namespace KM; int main() { scanf("%d",&n); for(int i=1,X,Y,Z,V;i<=n;i++) { scanf("%d%d%d%d",&X,&Y,&Z,&V); for(int j=0;j<n;j++) Edge[i][j+1]=-(X*X+Y*Y+Z*Z+2ll*Z*j*V+1ll*j*j*V*V); } Solve(),printf("%lld ",-Ans); }
K
分析:
对于每个询问,枚举( r ),找到第一个不符合条件的( l );可以知道( l )是只会右移的;
用单调队列维护递减的最大值,递增的最小值,就可以判断当前( l )是否符合条件了。
代码如下:
#include<iostream> #define ll long long using namespace std; int const N=1e5+5; int n,m,a[N],mx[N],hdx,tlx,mn[N],hdn,tln; ll ans; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&a[i]); for(int i=1,k;i<=m;i++) { scanf("%d",&k); ans=0; hdx=hdn=1; tlx=tln=0; for(int r=1,l=1;r<=n;r++) { while(hdx<=tlx&&a[mx[tlx]]<a[r])tlx--; mx[++tlx]=r; while(hdn<=tln&&a[mn[tln]]>a[r])tln--; mn[++tln]=r; //for(int j=hdx;j<=tlx;j++)printf("mx[%d]=%d ",j,mx[j]); printf(" "); //for(int j=hdn;j<=tln;j++)printf("mn[%d]=%d ",j,mn[j]); printf(" "); while(l<r&&a[mx[hdx]]-a[mn[hdn]]>k) { if(mx[hdx]==l&&hdx<=tlx)hdx++; if(mn[hdn]==l&&hdn<=tln)hdn++; l++; } //printf("l-1=%d r=%d ",l-1,r); ans+=l-1; } printf("%lld ",ans); } return 0; }