[CSP-S模拟测试69]题解
kuku
A.chess
首先考虑$m=n$的情况,中间没有限制,所以直接设$dp[i][j]$为考虑前$i$列,共放$j$枚棋子的方案数转移即可。刷表控制一下边界。
不难发现$i$列和$i+pn$列的的情况是一样的,所以沿用上面那个转移,
改为 $dp[i][j+k]=\sum dp[i-1][j] \times C_(n,k)^{(m-i)/n+1}$ 。预处理组合数次幂。
#include<bits/stdc++.h> using namespace std; typedef long long ll; const ll mod=1e9+7; int n,K; ll m,dp[105][10005],C[105][105],g[105][105]; ll qpow(ll a,ll b) { ll res=1;a=a%mod; while(b) { if(b&1)res=res*a%mod; a=a*a%mod; b>>=1; } return res; } int main() { scanf("%d%lld%d",&n,&m,&K); C[0][0]=1; for(int i=1;i<=n;i++) { C[i][0]=1; for(int j=1;j<=i;j++) (C[i][j]=C[i-1][j]+C[i-1][j-1])%=mod; } for(int i=0;i<=n;i++) for(int j=0;j<=n;j++) g[i][j]=qpow(C[n][i],(m-j)/n+1); dp[0][0]=1; for(int i=1;i<=n;i++) { for(int j=0;j<=(i-1)*n;j++) for(int k=0;k<=n;k++) (dp[i][j+k]+=dp[i-1][j]*g[k][i]%mod)%=mod; } cout<<dp[n][K]<<endl; return 0; }
B.array
其实就是求某个位置到左侧第一个比它大的位置之间最小值的位置。单纯求左侧第一个大于该点的位置显然可以单调栈,那么对于这道题,只要多开一个数组$L[]$表示最小值位置,并在弹栈时用栈内元素更新该点的$L[]$就行了。
#include<cstdio> #include<iostream> #include<cstring> #include<stack> using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=1e7+2; int n,a[N],L[N],ans; stack<int> s; int main() { n=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<=n;i++) { L[i]=i; while(!s.empty()&&a[s.top()]<=a[i]) { if(a[L[s.top()]]<=a[L[i]]) L[i]=L[s.top()]; s.pop(); } s.push(i); ans=max(ans,i-L[i]+1); } printf("%d\n",ans); return 0; }
C.ants
题意:对于每次询问,求$l$和$r$之间最长连续值域段的长度。
信息难以直接用线段树维护和合并,考虑莫队。容易发现本题容易在左右指针移动时添加信息,而删除是比较困难的。所以就是回滚莫队的板子。
简单口胡一下(没准以后有时间会把根号算法放一起总结一下):
询问排序第一关键字左端点所在块,第二关键字右端点。
初始化:左指针位于该块右端点+1,右指针位于该块右端点。
如果询问的左右端点在一个块中,直接暴力。
否则,由于右端点升序,右侧进行的肯定都是添加操作。
那左侧呢?从原位置出发,只做添加操作,完毕后撤销影响回到该块右端点+1。
针对这道题,因为需要维护值域段长度,所以考虑并查集。显然路径压缩是无法撤销的,所以需要按秩合并。
一点细节:操作很多,并查集的合并次数更多,每次都要将之前的操作集合入栈,这样并不能保证每个元素只会在栈里出现一次,所以如果用数组模拟栈的话需要开很大。当然STL就不存在这个问题了。
#include<cstdio> #include<iostream> #include<cstring> #include<cmath> #include<stack> #include<algorithm> using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch))x=x*10+ch-'0',ch=getchar(); return x*f; } const int N=1e5+5; const int inf=0x3f3f3f3f; int n,m,a[N],bel[N],bl,ans[N]; int fa[N],size[N],mind[N],maxd[N],vis[N]; stack<int> s; int findf(int x){return fa[x]?findf(fa[x]):x;} struct query { int l,r,id; }q[N]; bool cmp(const query &x,const query &y) { return (bel[x.l]==bel[y.l])?(x.r<y.r):(bel[x.l]<bel[y.l]); } void merge(int x,int y) { x=findf(x),y=findf(y); if(x==y)return ; if(size[x]>size[y])swap(x,y); fa[x]=y;size[y]+=size[x]; s.push(x); } int main() { n=read();m=read(); bl=sqrt(n); for(int i=1;i<=n;i++) { a[i]=read(); bel[i]=(i-1)/bl+1; } for(int i=1;i<=m;i++) q[i].l=read(),q[i].r=read(),q[i].id=i; sort(q+1,q+m+1,cmp); for(int i=0;i<=n;i++) maxd[i]=0,mind[i]=inf; int l,r,res,old,last; for(int i=1;i<=m;i++) { if(bel[q[i].l]!=bel[q[i-1].l]) { last=bel[q[i].l]*bl+1; l=last;r=l-1; for(int i=1;i<=n;i++) fa[i]=vis[i]=0,size[i]=1; res=old=0; } if(bel[q[i].l]==bel[q[i].r]) { for(int j=q[i].l;j<=q[i].r;j++) { maxd[a[j]]=max(maxd[a[j]+1],a[j]); mind[a[j]]=min(mind[a[j]-1],a[j]); mind[maxd[a[j]]]=mind[a[j]]; maxd[mind[a[j]]]=maxd[a[j]]; ans[q[i].id]=max(ans[q[i].id],maxd[a[j]]-mind[a[j]]+1); } for(int j=q[i].l;j<=q[i].r;j++) mind[a[j]]=inf,maxd[a[j]]=0; continue; } while(r<q[i].r) { vis[a[++r]]=1; if(vis[a[r]+1])merge(a[r],a[r]+1); if(vis[a[r]-1])merge(a[r],a[r]-1); res=max(res,size[findf(a[r])]); } old=res; int form=s.size(); while(l>q[i].l) { vis[a[--l]]=1; if(vis[a[l]+1])merge(a[l],a[l]+1); if(vis[a[l]-1])merge(a[l],a[l]-1); res=max(res,size[findf(a[l])]); } ans[q[i].id]=res; res=old; while(l<last)vis[a[l++]]=0; while(s.size()>form) { int x=s.top();s.pop(); size[fa[x]]-=size[x]; fa[x]=0; } } for(int i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
兴许青竹早凋,碧梧已僵,人事本难防。