NOIP模拟测试「简单的区间·简单的玄学·简单的填数·简单的序列」
简单的区间
$update$
终于$AC$了
找到$(sum[r]+sum[l](sum表示以中间点为基准的sum)-mx)\%k==0$的点
注意这里$sum$表示是以$mid$为基准点,(即$sum[l]$为后缀和,$sum[r]$为前缀和)
回忆$(sum[r]-sum[l])\%k==0$这个经典问题做法(入阵曲简化版),开桶,桶里维护$sum[l]\%k$,那么$r$贡献就是桶里$sum[r]\%k$个数
于是这个题开桶维护$sum$,问题转化为求$max$即可
记录$max$位置是否$>mid$,区别对待
设$f[i][0]$表示$max$在$mid$右面,$f[i][1]$表示$max$在$mid$左面
$f[i][0]$存下右面$sum[r]-mx$,找桶里是否存在左面$sum[l]$
$f[i][1]$存下右面$sum[r]$ 找到左面是否存在$mx-sum[l]$
完了
#include<bits/stdc++.h> using namespace std; #define ll long long #define A 1010101 ll f[A][2],a[A],pos[A],mx[A],sum[A]; ll cnt,n,k,ans; void solve(ll l,ll r){ if(l==r) return ; ll mid=(l+r)>>1; cnt=sum[mid]=mx[0]=0; for(ll i=mid+1;i<=r;i++){ if(a[i]>a[mx[cnt]]) mx[++cnt]=i; sum[i]=(sum[i-1]+a[i])%k; f[(sum[i]-a[mx[cnt]]%k+k)%k][0]++; pos[i]=mx[cnt]; // printf("f[%lld]=%lld sum=%lld cnt=%lld\n",(sum[i]-a[mx[cnt]]%k+k)%k,f[(sum[i]-a[mx[cnt]]%k+k)%k][0],sum[i],cnt); } mx[cnt+1]=r+1; ll suml=0,rnow=mid+1,mxl=0,p=1; for(ll i=mid;i>=l;i--){ suml=(suml+a[i])%k; mxl=max(mxl,a[i]); while(p<=cnt&&a[mx[p]]<=mxl) p++; while(rnow<mx[p]) { f[(sum[rnow]-a[pos[rnow]]%k+k)%k][0]--; f[sum[rnow]%k][1]++; rnow++; } // printf("ans=%lld f[%lld][1]=%lld p=%lld rnow=%lld mx[%lld]=%lldsum[%lld]=%lld\n",ans,(k+mxl%k-suml)%k,f[(k+mxl%k-suml)%k][1],p,rnow,p,mx[p],rnow,sum[rnow]); ans+=f[(mxl-suml+k)%k][1]; if(p<=cnt) ans+=f[(k-suml)%k][0]; // printf("ans=%lld f[%lld][0]=%lld\n",ans,k-suml,f[(k-suml)%k][0]); } for(ll i=mid+1;i<rnow;i++) f[sum[i]][1]--; for(ll i=rnow;i<=r;i++) f[(sum[i]-a[pos[i]]%k+k)%k][0]--; solve(l,mid);solve(mid+1,r); } int main(){ scanf("%lld%lld",&n,&k); for(ll i=1;i<=n;i++) scanf("%lld",&a[i]); solve(1,n); printf("%lld",ans); }
简单的玄学
题解
题目中说至少两个相同那么答案就所有方案-全不相同
所有方案${(2^n)}^m=2^{n*m}$,
互不相同,首先第一个随便选剩下避开已经选过就行$2^n*2^{n-1}......2^{n-m+1}$
那么题目很傻逼的让你取模并且约分,你需要先约分再取模(取模再约分的话这个题就太水了,所以是先约分再取模)
思考怎么约分
下面全是$2$多少次方,于是我们看上面多少个二就行了
$2^n*(2^{n}-1)......({2^n}-m+1)$很恶心,思考转化
性质:$2^n-x$中二个数$=$$x$中二的个数
证明:假设$x$可以表示为$j*(2^w)$(j可以是分数),乘法分配率$2^w*(2^{n-w}-j)$后面这个里面没有别的$2$因子了,原式$=$$2^w$,又$j$中没有$2$因子故相乘因子数不变,得证
那么原式就变成求$(m-1)!$里$2$因子数
可以简单求
for(ll i=1;(1ll<<i)<=m-1;i++){ (ercnt+=(m-1)/(1ll<<i)); }
例如$1$ $2$ $3$ $4$ $5$ $6$ $7$ $8$这个序列
分别有$(2^1)*2$,$(2^2)*1$,$(2^3)*1$那么就是$8/8+8/4+8/2$
可以看作$/2$时给所有有$2$因子填上一个二(即$2$,$4$,$6$,$8$中填一个2),此时$4$还剩$1$个没填$8$还剩$2$个没填
$/4$给$4$,$8$里填此时$8$还剩$1$个没填
最后$/8$,全部填满
代码
#include<bits/stdc++.h> using namespace std; #define ll long long const ll mod=1e6+3; const ll phi=1e6+2; ll x,y,n,m; ll meng(ll x,ll k){ ll ans=1; for(;k;k>>=1,x=x*x%mod) if(k&1) ans=ans*x%mod; return ans; } ll gcd(ll x,ll y){ if(y==0) return x; return gcd(y,x%y); } int main(){ // freopen("sd.txt","w",stdout); scanf("%lld%lld",&n,&m); /* if(log(m)>n){ printf("1 1\n"); return 0; } */ ll maxn=meng(2,n%phi); y=meng(maxn,m%phi); x=1; ll ercnt=n; for(ll i=0;i<m;i++){ // printf("maxn-i=%lld i=%lld m=%lld x=%lld\n",maxn-i,i,m,x); x=x*(maxn-i)%mod; if(!x) break; }//2逆元500002 for(ll i=1;(1ll<<i)<=m-1;i++){ (ercnt+=(m-1)/(1ll<<i)); printf("ercnt=%lld 1<<=%lld\n",ercnt-n,1ll<<i); } y=y*meng(500002,ercnt)%mod; x=x*meng(500002,ercnt)%mod; printf("%lld %lld\n",(y-x+mod)%mod,y); }
简单的填数
题解
一个$up$代表填的上界,$down$代表填的下界
先不考虑已经填了的
$up$两位一进,$down$五位一进
考虑已经填的
先考虑上界
若$a[i]>up$比上界大肯定不合法
若$a[i]=up$取$min(2,up)$
若$a[i]<up$则将$up$调整到$a[i]$次数变为$2$
下界类似
若$a[i]<down$比下界小不合法
若$a[i]>down$将$down$调整到$a[i]$
统计答案时反着扫
序列为什么不是$up$呢
7 0 0 0 2 0 2 0 正解 2 1 1 2 2 2 2 2 用up: 2 1 1 2 2 3 2 2
代码
/* 7 0 0 0 2 0 2 0 hack 2 1 1 2 2 2 2 2 up: 2 1 1 2 2 3 2 2 10f */ #include<bits/stdc++.h> using namespace std; #define ll long long #define A 1010101 struct node { ll cnt,x; }up[A],down[A]; ll n; ll a[A],tong[A]; int main(){ // freopen("da.in","r",stdin); freopen("ans.bf","w",stdout); scanf("%lld",&n); for(ll i=1;i<=n;i++){ scanf("%lld",&a[i]); } if(a[1]!=1&&a[1]!=0){ printf("-1\n"); return 0; } up[1].cnt=1,up[1].x=1; down[1].cnt=1,down[1].x=1; for(ll i=2;i<=n;i++){ up[i]=up[i-1],down[i]=down[i-1]; if(++up[i].cnt>2) up[i].cnt=1,up[i].x++; if(++down[i].cnt>5) down[i].cnt=1,down[i].x++; if(a[i]){ if(up[i].x>a[i]){ up[i].x=a[i]; up[i].cnt=2; } else if(up[i].x==a[i]){ up[i].cnt=min(up[i].cnt,2ll); } if(down[i].x<a[i]) down[i].x=a[i],down[i].cnt=1; if(up[i].x<a[i]||down[i].x>a[i]){ printf("-1\n"); return 0; } } } if(up[n].cnt==1){ up[n].x=up[n-1].x; } if(up[n].x<down[n].x){ printf("-1\n"); return 0; } printf("%lld\n",up[n].x); tong[up[n].x]=1; a[n]=up[n].x; for(ll i=n-1;i>=1;i--){ if(!a[i]){ ll t=min(a[i+1],up[i].x); if(tong[t]==5) t--; a[i]=t; } tong[a[i]]++; } for(ll i=1;i<=n;i++){ printf("%lld ",a[i]); } }
简单的序列
这是一个简单$dp$,但我觉得很棒在此写下题解
真的非常简单,
有一个长度$n$括号序列(只有$"()"$ ),给定其中长度为$m$一段,求满足括号匹配方案数
$n,m<=1e6$ $n-m<=4000$
题解
性质:我们发现一个合法匹配序列左扩号时刻比右括号多(显然),最后左扩号数量等于右括号数量
设$f[i][j]$表示长度为$i$序列,左扩号比右括号多$j$个方案数
那么类似的设$g[i][j]$为右括号比左扩号多$j$的方案数
(其实$f$和$g$值完全一样)
转移非常简单
当前括号可能是$($则贡献$f[i][j]=f[i-1][j-1]$为$)$则$f[i][j]=f[i-1][j+1]$
总贡献$f[i][j]=f[i-1][j-1]+f[i-1][j+1]$
类似的$g[i][j]=g[i-1][j-1]+g[i-1][j+1]$
那么思考统计答案
其实也非常简单
枚举第一段长度$i$,第一段左扩号比右括号多$j$,设给定序列左扩号比右括号多$j$
$ans=\sum\limits_{i=1}^{i<=n-m} \sum\limits_{j=0}^{j<=i} f[i][j]*g[(n-m)-i][j+tot]$
注意判是否合法
代码
#include<bits/stdc++.h> using namespace std; #define ll long long #define A 4040 const ll mod=1e9+7; char c[2020202]; ll f[A][A]; ll tot,mint,n,m,ans; int main(){ // freopen("da.in","r",stdin); freopen("ans.bf","w",stdout); scanf("%lld%lld",&n,&m); scanf("%s",c+1); for(ll i=1;i<=m;i++){ if(c[i]=='(') tot++; else tot--; if(i==1) mint=tot; else mint=min(mint,tot); } f[0][0]=1; for(ll i=1;i<=n-m;i++){ for(ll j=0;j<=i;j++){ if(j==0) f[i][j]=f[i-1][j+1]; else f[i][j]=(f[i-1][j+1]+f[i-1][j-1])%mod; } } for(ll i=0;i<=n-m;i++){ for(ll j=0;j<=i;j++){ if(j+mint>=0&&j+tot<=n-m) ans=(ans+f[i][j]*f[(n-m)-i][j+tot]%mod)%mod; } } printf("%lld\n",ans); }
我没数据,也没法提交,和$std$对拍了一下
下面是我的数据生成及对拍
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 system("g++ bf.cpp -o bf"); 5 system("g++ sol.cpp -o sol"); 6 system("g++ da.cpp -o da"); 7 int rp=0; 8 while(++rp){ 9 cout<<rp<<" "; 10 system("./da"); 11 system("./sol"); 12 system("./bf"); 13 if(system("diff -B -b ans.sol ans.bf")){ 14 puts("WA"); 15 while(1); 16 } 17 puts("AC"); 18 } 19 }
1 #include<bits/stdc++.h> 2 using namespace std; 3 int main(){ 4 freopen("da.in","w",stdout); 5 srand(time(NULL)); 6 7 int m=rand()%10000+300; 8 int c=rand()%m+1; 9 while(m-c>2000){ 10 c=rand()%m+1; 11 } 12 cout<<m<<" "<<c<<endl; 13 for(int i=1;i<=c;i++){ 14 if(rand()%2){ 15 printf("("); 16 } 17 else printf(")"); 18 } 19 cout<<endl; 20 }