牛客NOIP暑期七天营-提高组3
-----------今天是被打爆的一天,T3就是用正解做的,但是好像zz了,只得了30分。-----------------
---------------- T1由于没判不合法的情况,也只有15分---------------------
---------------------果然是一个拿不到noip一等奖的人----------------------------
----------------------------毕竟noip都准备改名了?-------------------------
A:破碎的矩阵。
题意:给出N,M,表示有N*M的矩阵,然后给定每一行的异或和,每一列的异或和,求方案数。
思路:如果合法,方案数是pow(2,(N-1)*(M-1)),这个很好想,因为你确定了一个N*M的左上方(N-1)*(M-1)的矩阵后,最后一行一列可以反推,是唯一的。 不合法的情况是,异或和不为0;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=2000010; int ans,N,M,Mod; ll x,a[maxn],b[maxn]; int qpow(int base,int num) { int res=1; while(num){ if(num&1) res=1LL*res*base%Mod; num>>=1; base=1LL*base*base%Mod; }return res; } void solve(int t) { int res=qpow(qpow(qpow(2,N-1),M-1),t); ans=1LL*res%Mod%Mod; } int main() { int T; scanf("%d",&T); while(T--){ ll xxx=0; scanf("%d%d%lld%d",&N,&M,&x,&Mod); rep(i,1,N) scanf("%lld",&a[i]),xxx^=a[i]; rep(i,1,M) scanf("%lld",&b[i]),xxx^=b[i]; if(xxx) puts("0"); else { int t=0; rep(i,0,60) if(x&(1LL<<i)) t++; solve(t); printf("%d\n",ans); } } return 0; }
B:点与面。
题意:求W的个数。
思路:裸题,三次BIT即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define ll long long #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=500010; const int Mod=998244353; int a[maxn],b[maxn],tot; ll L[maxn],R[maxn],ans,sum[maxn],res[maxn]; ll query(int x){ int res=0;while(x){ (res+=sum[x])%=Mod; x-=(-x)&x;} return res;} void add(int x,ll y){ while(x<=tot){ sum[x]+=y; if(sum[x]>=Mod) sum[x]-=Mod; x+=(-x)&x;} } int main() { int N; scanf("%d",&N); rep(i,1,N) scanf("%d",&a[i]),b[i]=a[i]; sort(b+1,b+N+1); tot=unique(b+1,b+N+1)-(b+1); rep(i,1,N) a[i]=lower_bound(b+1,b+tot+1,a[i])-b; rep(i,1,N) L[i]=i-1-query(a[i]),add(a[i],1); rep(i,1,tot) sum[i]=0; for(int i=N;i>=1;i--) R[i]=N-i-query(a[i]),add(a[i],1); rep(i,1,tot) sum[i]=0; rep(i,1,N) res[i]=1; rep(i,1,N) (res[i]*=query(a[i]-1))%=Mod,add(a[i],L[i]); rep(i,1,tot) sum[i]=0; for(int i=N;i>=1;i--) (res[i]*=query(a[i]-1))%=Mod,add(a[i],R[i]); rep(i,1,N) ans+=res[i]; printf("%lld\n",ans%Mod); return 0; }
C:信息传递
题意: Venn的班级有n个人,他们的座位是首尾相连呈环形的。
如果第i个人得到了Venn的雀魂消息,下一秒就会告诉他左边的前Li个人,和右边的前Ri个人。
注意:由于他们是一个环形,第1个人的左边一个人是第n个人,第n个人的右边一个人是第1个人。
Venn为了防止自己掉分的惨案被大家知道,他想知道对于每一个人,如果消息从他这里传播,需要多久整个班级就会知道。
思路:一眼就可以想到做法,由于ans会比较大,所以倍增。 dp[i][j]表示从i出发,经过1<<j妙,最左到哪里,dp[i][j]=min(L[dp[i][j-1]]...L[i]),区间更新, 所以每一层用线段树优化。
但是我只得了30分。 是因为有个wa点。 就是可能消息不是单方向传递的,所以左右转移要同时进行。 比如1先向右传递到2号学生,2号学生的Li非常的小,那么消息就传到很左边了。
#include<bits/stdc++.h> #define pii pair<int,int> #define f first #define s second #define rep(i,a,b) for(int i=a;i<=b;i++) using namespace std; const int maxn=300010; const int inf=1e9; int l[maxn],r[maxn],a[maxn][18],b[maxn][18],Mn[maxn<<2][18],Mx[maxn<<2][18]; void build(int Now,int L,int R,int p) { if(L==R) { Mx[Now][p]=b[L][p]; Mn[Now][p]=a[L][p]; return ; } int Mid=(L+R)>>1; build(Now<<1,L,Mid,p); build(Now<<1|1,Mid+1,R,p); Mx[Now][p]=max(Mx[Now<<1][p],Mx[Now<<1|1][p]); Mn[Now][p]=min(Mn[Now<<1][p],Mn[Now<<1|1][p]); } pii query(int Now,int L,int R,int l,int r,int p) { if(l<=L&&r>=R) return pii{ Mx[Now][p],Mn[Now][p]}; int Mid=(L+R)>>1; pii res,tmp; res.f=-inf,res.s=inf; if(l<=Mid) { tmp=query(Now<<1,L,Mid,l,r,p); res.f=max(res.f,tmp.f); res.s=min(res.s,tmp.s); } if(r>Mid) { tmp=query(Now<<1|1,Mid+1,R,l,r,p); res.f=max(res.f,tmp.f); res.s=min(res.s,tmp.s); } return res; } int main() { int N,M; scanf("%d",&N); M=3*N; rep(i,1,N) scanf("%d",&l[i]),l[i+N]=l[i+N+N]=l[i]; rep(i,1,N) scanf("%d",&r[i]),r[i+N]=r[i+N+N]=r[i]; if(N==1){ puts("0"); return 0; } rep(i,1,M) a[i][0]=max(1,i-l[i]); rep(i,1,M) b[i][0]=min(M,i+r[i]); build(1,1,M,0); rep(j,1,17) { rep(i,1,M){ pii t=query(1,1,M,a[i][j-1],b[i][j-1],j-1); b[i][j]=t.f; a[i][j]=t.s; } build(1,1,M,j); } rep(i,1,N) { int res=0,L=N+i,R=N+i; for(int j=17;j>=0;j--){ pii t=query(1,1,M,L,R,j); int tL=t.s,tR=t.f; if(tR-tL<N-1) R=tR,L=tL,res+=(1<<j); } printf("%d ",res+1); } return 0; }