[NOIP模拟测试11] 题解
A.string
和河北的一道省选题很像。考场上写的暴力桶排,正解其实就是优化一下这个思路。
开线段树维护字符串中每个字母出现的次数。对于每条询问,区间查询、区间赋值维护即可。
另外,本题卡常严重,正解能被卡到40到90不等。所以直接循环展开乱搞一波?
暴力40分代码:
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int N=100005; int a[N],bu[205],n,m; char str[N]; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } void busort(int op,int l,int r) { for(int i=l;i<=r;i++) bu[a[i]]++; int tot=0; if(op) { for(int i=97;i<=122;i++) while(bu[i])a[l+(tot++)]=i,bu[i]--; } else { for(int i=122;i>=97;i--) while(bu[i])a[l+(tot++)]=i,bu[i]--; } } int main() { n=read();m=read(); scanf("%s",str+1); for(int i=1;i<=n;i++) a[i]=(int)str[i]; while(m--) { int l=read(),r=read(),op=read(); busort(op,l,r); } for(int i=1;i<=n;i++) { char ch=(char)a[i]; printf("%c",ch); } return 0; }
被卡的90分代码:
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int N=100005; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } int n,m,len; char str[N],ans[N]; #define ls(k) k<<1 #define rs(k) k<<1|1 int num[N<<2][28],lz[N<<2],le[N<<2],re[N<<2]; int res[28]; void up(int k) { for(int i=1;i<=26;i++) num[k][i]=num[ls(k)][i]+num[rs(k)][i]; } void build(int k,int l,int r) { lz[k]=0;le[k]=l,re[k]=r; if(l==r) { num[k][str[l]-'a'+1]=1; return ; } int mid=l+r>>1; build(ls(k),l,mid); build(rs(k),mid+1,r); up(k); } void down(int k) { memset(num[ls(k)],0,sizeof(num[ls(k)])); memset(num[rs(k)],0,sizeof(num[rs(k)])); num[ls(k)][lz[k]]=re[ls(k)]-le[ls(k)]+1; num[rs(k)][lz[k]]=re[rs(k)]-le[rs(k)]+1; lz[ls(k)]=lz[rs(k)]=lz[k]; lz[k]=0; } void query(int k,int L,int R) { if(le[k]>=L&&R>=re[k]) { for(int i=1;i<=26;i++) res[i]+=num[k][i]; return ; } int mid=le[k]+re[k]>>1; if(lz[k])down(k); if(L<=mid)query(ls(k),L,R); if(R>mid)query(rs(k),L,R); } void change(int k,int L,int R,int val) { if(le[k]>=L&&R>=re[k]) { memset(num[k],0,sizeof(num[k])); num[k][val]=re[k]-le[k]+1; lz[k]=val; return ; } int mid=le[k]+re[k]>>1; if(lz[k])down(k); if(L<=mid)change(ls(k),L,R,val); if(R>mid)change(rs(k),L,R,val); up(k); } void cacl(int k) { if(le[k]==re[k]) { for(int i=1;i<=26;i++) if(num[k][i]) { ans[++len]=i-1+'a'; return ; } } if(lz[k])down(k); cacl(ls(k)); cacl(rs(k)); } int main() { n=read();m=read(); scanf("%s",str+1); build(1,1,n); for(int i=1;i<=m;i++) { int l=read(),r=read(),op=read(); memset(res,0,sizeof(res)); query(1,l,r); if(op) { for(int j=1;j<=26;j++) if(res[j]) change(1,l,l+res[j]-1,j),l+=res[j]; } else { for(int j=26;j>=1;j--) if(res[j]) change(1,l,l+res[j]-1,j),l+=res[j]; } } cacl(1); printf("%s\n",ans+1); return 0; }
丧心病狂科学打表的循环展开代码:
#include<cstdio> #include<iostream> #include<cstring> #define rint register int using namespace std; const int N=100005; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x*f; } int n,m,len; char str[N],ans[N]; #define ls(k) k<<1 #define rs(k) k<<1|1 int num[N<<2][28],lz[N<<2],le[N<<2],re[N<<2]; int res[28]; void up(int k) { num[k][1]=num[ls(k)][1]+num[rs(k)][1]; num[k][2]=num[ls(k)][2]+num[rs(k)][2]; num[k][3]=num[ls(k)][3]+num[rs(k)][3]; num[k][4]=num[ls(k)][4]+num[rs(k)][4]; num[k][5]=num[ls(k)][5]+num[rs(k)][5]; num[k][6]=num[ls(k)][6]+num[rs(k)][6]; num[k][7]=num[ls(k)][7]+num[rs(k)][7]; num[k][8]=num[ls(k)][8]+num[rs(k)][8]; num[k][9]=num[ls(k)][9]+num[rs(k)][9]; num[k][10]=num[ls(k)][10]+num[rs(k)][10]; num[k][11]=num[ls(k)][11]+num[rs(k)][11]; num[k][12]=num[ls(k)][12]+num[rs(k)][12]; num[k][13]=num[ls(k)][13]+num[rs(k)][13]; num[k][14]=num[ls(k)][14]+num[rs(k)][14]; num[k][15]=num[ls(k)][15]+num[rs(k)][15]; num[k][16]=num[ls(k)][16]+num[rs(k)][16]; num[k][17]=num[ls(k)][17]+num[rs(k)][17]; num[k][18]=num[ls(k)][18]+num[rs(k)][18]; num[k][19]=num[ls(k)][19]+num[rs(k)][19]; num[k][20]=num[ls(k)][20]+num[rs(k)][20]; num[k][21]=num[ls(k)][21]+num[rs(k)][21]; num[k][22]=num[ls(k)][22]+num[rs(k)][22]; num[k][23]=num[ls(k)][23]+num[rs(k)][23]; num[k][24]=num[ls(k)][24]+num[rs(k)][24]; num[k][25]=num[ls(k)][25]+num[rs(k)][25]; num[k][26]=num[ls(k)][26]+num[rs(k)][26]; } void build(int k,int l,int r) { lz[k]=0;le[k]=l,re[k]=r; if(l==r) { num[k][str[l]-'a'+1]=1; return ; } int mid=l+r>>1; build(ls(k),l,mid); build(rs(k),mid+1,r); up(k); } void down(int k) { memset(num[ls(k)],0,sizeof(num[ls(k)])); memset(num[rs(k)],0,sizeof(num[rs(k)])); num[ls(k)][lz[k]]=re[ls(k)]-le[ls(k)]+1; num[rs(k)][lz[k]]=re[rs(k)]-le[rs(k)]+1; lz[ls(k)]=lz[rs(k)]=lz[k]; lz[k]=0; } void query(int k,int L,int R) { if(le[k]>=L&&R>=re[k]) { res[1]+=num[k][1]; res[2]+=num[k][2]; res[3]+=num[k][3]; res[4]+=num[k][4]; res[5]+=num[k][5]; res[6]+=num[k][6]; res[7]+=num[k][7]; res[8]+=num[k][8]; res[9]+=num[k][9]; res[10]+=num[k][10]; res[11]+=num[k][11]; res[12]+=num[k][12]; res[13]+=num[k][13]; res[14]+=num[k][14]; res[15]+=num[k][15]; res[16]+=num[k][16]; res[17]+=num[k][17]; res[18]+=num[k][18]; res[19]+=num[k][19]; res[20]+=num[k][20]; res[21]+=num[k][21]; res[22]+=num[k][22]; res[23]+=num[k][23]; res[24]+=num[k][24]; res[25]+=num[k][25]; res[26]+=num[k][26]; return ; } int mid=le[k]+re[k]>>1; if(lz[k])down(k); if(L<=mid)query(ls(k),L,R); if(R>mid)query(rs(k),L,R); } void change(int k,int L,int R,int val) { if(le[k]>=L&&R>=re[k]) { memset(num[k],0,sizeof(num[k])); num[k][val]=re[k]-le[k]+1; lz[k]=val; return ; } int mid=le[k]+re[k]>>1; if(lz[k])down(k); if(L<=mid)change(ls(k),L,R,val); if(R>mid)change(rs(k),L,R,val); up(k); } void cacl(int k) { if(le[k]==re[k]) { if(num[k][1]){ans[++len]=1-1+'a';return ;} if(num[k][2]){ans[++len]=2-1+'a';return ;} if(num[k][3]){ans[++len]=3-1+'a';return ;} if(num[k][4]){ans[++len]=4-1+'a';return ;} if(num[k][5]){ans[++len]=5-1+'a';return ;} if(num[k][6]){ans[++len]=6-1+'a';return ;} if(num[k][7]){ans[++len]=7-1+'a';return ;} if(num[k][8]){ans[++len]=8-1+'a';return ;} if(num[k][9]){ans[++len]=9-1+'a';return ;} if(num[k][10]){ans[++len]=10-1+'a';return ;} if(num[k][11]){ans[++len]=11-1+'a';return ;} if(num[k][12]){ans[++len]=12-1+'a';return ;} if(num[k][13]){ans[++len]=13-1+'a';return ;} if(num[k][14]){ans[++len]=14-1+'a';return ;} if(num[k][15]){ans[++len]=15-1+'a';return ;} if(num[k][16]){ans[++len]=16-1+'a';return ;} if(num[k][17]){ans[++len]=17-1+'a';return ;} if(num[k][18]){ans[++len]=18-1+'a';return ;} if(num[k][19]){ans[++len]=19-1+'a';return ;} if(num[k][20]){ans[++len]=20-1+'a';return ;} if(num[k][21]){ans[++len]=21-1+'a';return ;} if(num[k][22]){ans[++len]=22-1+'a';return ;} if(num[k][23]){ans[++len]=23-1+'a';return ;} if(num[k][24]){ans[++len]=24-1+'a';return ;} if(num[k][25]){ans[++len]=25-1+'a';return ;} if(num[k][26]){ans[++len]=26-1+'a';return ;} } if(lz[k])down(k); cacl(ls(k)); cacl(rs(k)); } int main() { n=read();m=read(); scanf("%s",str+1); build(1,1,n); for(rint i=1;i<=m;i++) { int l=read(),r=read(),op=read(); memset(res,0,sizeof(res)); query(1,l,r); if(op) { if(res[1])change(1,l,l+res[1]-1,1),l+=res[1]; if(res[2])change(1,l,l+res[2]-1,2),l+=res[2]; if(res[3])change(1,l,l+res[3]-1,3),l+=res[3]; if(res[4])change(1,l,l+res[4]-1,4),l+=res[4]; if(res[5])change(1,l,l+res[5]-1,5),l+=res[5]; if(res[6])change(1,l,l+res[6]-1,6),l+=res[6]; if(res[7])change(1,l,l+res[7]-1,7),l+=res[7]; if(res[8])change(1,l,l+res[8]-1,8),l+=res[8]; if(res[9])change(1,l,l+res[9]-1,9),l+=res[9]; if(res[10])change(1,l,l+res[10]-1,10),l+=res[10]; if(res[11])change(1,l,l+res[11]-1,11),l+=res[11]; if(res[12])change(1,l,l+res[12]-1,12),l+=res[12]; if(res[13])change(1,l,l+res[13]-1,13),l+=res[13]; if(res[14])change(1,l,l+res[14]-1,14),l+=res[14]; if(res[15])change(1,l,l+res[15]-1,15),l+=res[15]; if(res[16])change(1,l,l+res[16]-1,16),l+=res[16]; if(res[17])change(1,l,l+res[17]-1,17),l+=res[17]; if(res[18])change(1,l,l+res[18]-1,18),l+=res[18]; if(res[19])change(1,l,l+res[19]-1,19),l+=res[19]; if(res[20])change(1,l,l+res[20]-1,20),l+=res[20]; if(res[21])change(1,l,l+res[21]-1,21),l+=res[21]; if(res[22])change(1,l,l+res[22]-1,22),l+=res[22]; if(res[23])change(1,l,l+res[23]-1,23),l+=res[23]; if(res[24])change(1,l,l+res[24]-1,24),l+=res[24]; if(res[25])change(1,l,l+res[25]-1,25),l+=res[25]; if(res[26])change(1,l,l+res[26]-1,26),l+=res[26]; } else { if(res[26])change(1,l,l+res[26]-1,26),l+=res[26]; if(res[25])change(1,l,l+res[25]-1,25),l+=res[25]; if(res[24])change(1,l,l+res[24]-1,24),l+=res[24]; if(res[23])change(1,l,l+res[23]-1,23),l+=res[23]; if(res[22])change(1,l,l+res[22]-1,22),l+=res[22]; if(res[21])change(1,l,l+res[21]-1,21),l+=res[21]; if(res[20])change(1,l,l+res[20]-1,20),l+=res[20]; if(res[19])change(1,l,l+res[19]-1,19),l+=res[19]; if(res[18])change(1,l,l+res[18]-1,18),l+=res[18]; if(res[17])change(1,l,l+res[17]-1,17),l+=res[17]; if(res[16])change(1,l,l+res[16]-1,16),l+=res[16]; if(res[15])change(1,l,l+res[15]-1,15),l+=res[15]; if(res[14])change(1,l,l+res[14]-1,14),l+=res[14]; if(res[13])change(1,l,l+res[13]-1,13),l+=res[13]; if(res[12])change(1,l,l+res[12]-1,12),l+=res[12]; if(res[11])change(1,l,l+res[11]-1,11),l+=res[11]; if(res[10])change(1,l,l+res[10]-1,10),l+=res[10]; if(res[9])change(1,l,l+res[9]-1,9),l+=res[9]; if(res[8])change(1,l,l+res[8]-1,8),l+=res[8]; if(res[7])change(1,l,l+res[7]-1,7),l+=res[7]; if(res[6])change(1,l,l+res[6]-1,6),l+=res[6]; if(res[5])change(1,l,l+res[5]-1,5),l+=res[5]; if(res[4])change(1,l,l+res[4]-1,4),l+=res[4]; if(res[3])change(1,l,l+res[3]-1,3),l+=res[3]; if(res[2])change(1,l,l+res[2]-1,2),l+=res[2]; if(res[1])change(1,l,l+res[1]-1,1),l+=res[1]; } } cacl(1); printf("%s\n",ans+1); return 0; }
B.matrix
神dp。状态数组我估计一辈子都想不出来。
(虽然题意不明 似乎默认限制区间外必填0?)
我们把构造矩阵的过程看作往矩阵里填1的过程,设$f[i][j]$为填到第i列,有j个1在右侧限制区间里的方案数。
设$lnum[i]$为到i列时已结束的左限制区间的个数,$rnum[i]$为到i列时已开始的右限制区间的个数。
转移时左右区间分别考虑:
对于右区间,考虑到i列放与不放即可。如果不放的话就直接继承状态,即$f[i][j]+=f[i-1][j]$.如果放,则有$f[i][j+1]+=f[i-1][j]*(rnum[i]-j)$,解释一下:放1肯定得在已开始的右区间里放,但是还要除去已经放了的j个右区间。
对于左区间,我们只在恰好经过它的结束点时对它进行考虑。既然有j个1放在右限制区间,已经转移了i列,那么一定有i-j个1放在左限制区间,毕竟每列只放一个。又因为有$lnum[i-1]$个左区间已经结束并处理过了,所以留给恰在第i列结束的左区间的1只有$i-j-lnum[i-1]$个(当然,这些1也可以放在之前的某一列里),那么对方案的贡献就要乘上$A_{i-j-lnum[i-1]}^{lnum[i]-lnum[i-1]}$了。
排列数打表或逆元求皆可。
//dp[i][j]:到第i列,j个1在各行的右区间 #include<cstdio> #include<iostream> #include<cstring> using namespace std; typedef long long ll; const int N=3005; const ll mod=998244353; int L[N],R[N],numl[N],numr[N],n,m; ll dp[N][N],A[N][N]; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d%d",&L[i],&R[i]); numl[L[i]]++;numr[R[i]]++; } for(int i=1;i<=m;i++) numl[i]+=numl[i-1],numr[i]+=numr[i-1]; for(int i=1;i<=m;i++) { A[i][0]=1; for(int j=1;j<=i;j++) A[i][j]=A[i][j-1]*1LL*(i-j+1)%mod; } dp[1][0]=1;A[0][0]=1; for(int i=1;i<=m;i++) { for(int j=0;j<=n;j++) { if(i-j-numl[i-1]<numl[i]-numl[i-1])//左:在这一列结束的左区间能放几个1 { dp[i][j]=0; continue; } (dp[i][j]*=A[i-j-numl[i-1]][numl[i]-numl[i-1]])%=mod; (dp[i+1][j]+=dp[i][j])%=mod; (dp[i+1][j+1]+=dp[i][j]*1LL*(numr[i+1]-j))%=mod; // cout<<dp[i][j]<<endl; } } cout<<dp[m][n]<<endl; return 0; }
C.
sb对手干的破事其实就是循环左移(最靠前的一位挪到后面,剩下全体左移一位)
异或满足交换律结合律,那么就可以先利用前后缀求出每次除了初值外的异或和,将它们插入一棵01Trie,并通过对它进行dfs得出结果。
如果儿子只有一个,我们选x时就能取与它相反的一位从而让这位成为1,所以在计答案时这一位为1;如果儿子有两个,不论我们这位选0还是1,对手都会取相同的一位使这一位成为0,所以答案中这一位只能为0。
复杂度$O(nm)$
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int N=100005; int a[N],trie[N*26][3],xors[N],sxor[N]; int tot,ans,ansnum,n,m; void ins(int num) { int root=0; for(int i=n-1;i>=0;i--) { int x=(num>>i)&1; if(!trie[root][x])trie[root][x]=++tot; root=trie[root][x]; } } void dfs(int x,int val,int pos) { if(pos==1) { if(val>ans)ans=val,ansnum=1; else if(val==ans)ansnum++; return ; } if(!trie[x][0]||!trie[x][1])dfs(trie[x][0]+trie[x][1],val|(pos>>1),pos>>1); else dfs(trie[x][0],val,pos>>1),dfs(trie[x][1],val,pos>>1); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++)scanf("%d",&a[i]); sxor[1]=a[1]; for(int i=2;i<=m;i++) sxor[i]=sxor[i-1]^a[i]; xors[m]=a[m]; for(int i=m-1;i>=1;i--) xors[i]=xors[i+1]^a[i]; for(int i=0;i<=m;i++) ins(xors[i+1]^(((sxor[i]<<1)/(1<<n)+(sxor[i]<<1))%(1<<n))); dfs(0,0,1<<n); cout<<ans<<endl<<ansnum<<endl; return 0; }
UPD:之前说T3那个式子叫逻辑左移?不对不对,被亚历克斯鹅科普了一下,应该是循环左移(意会为主好吧)。