2021.7.19 Contest 题解
T1:
Description:
小a有一个数列 (a),记 (f(x)) 表示斐波那契数列第 (x)项,其中 (f(1)=f(2)=1)。
若干次操作,(1) 表示对数列 (a) 区间加,(2) 表示求 (sum f(a_i)),其中 (i) 在 (l) 到 (r) 之间。
由于 (2) 的答案可能非常大,所以对 (10^9+7) 取模。
Input:
第一行两个数 (n,m) 分别表示序列长度和询问次数。
接下来一行 (n) 个数,为 (a) 数组。
接下来 (m) 行,每行为 (1) 操作或 (2) 操作。
Output:
对于每个询问,输出询问内容。
Sample1 Input:
5 4
1 1 2 1 1
2 1 5
1 2 4 2
2 2 4
2 1 5
Sample1 Output:
5
7
9
Hint:
对于 (30\%) 的数据,(nle10^3)。
对于 (60\%) 的数据,(nle10^4)。
对于 (100\%) 的数据,(nle10^5)。
题目分析:
众所周知可以用矩阵乘法算出斐波那契数列第 (x) 项。线段树维护矩阵板子题,区间加可以转化为区间乘上一个矩阵,查询就是矩阵加法。
代码如下(马蜂很丑,不喜勿喷)——
#include<bits/stdc++.h>
#define N 100005
#define LL long long
using namespace std;
int n,m,a[N];const int p=1e9+7;
struct node{int g[3][3];inline void C(){g[0][0]=g[1][1]=1,g[1][0]=g[0][1]=0;} }S[N<<2],T[N<<2],A,B,C,D,V;
inline void M(node x,node y,node &z){
z.g[0][0]=(1ll*x.g[0][0]*y.g[0][0]+1ll*x.g[0][1]*y.g[1][0])%p;z.g[0][1]=(1ll*x.g[0][0]*y.g[0][1]+1ll*x.g[0][1]*y.g[1][1])%p;
z.g[1][0]=(1ll*x.g[1][0]*y.g[0][0]+1ll*x.g[1][1]*y.g[1][0])%p;z.g[1][1]=(1ll*x.g[1][0]*y.g[0][1]+1ll*x.g[1][1]*y.g[1][1])%p;
}
inline void P(node x,node y,node &z){z.g[0][0]=x.g[0][0]+y.g[0][0],(z.g[0][0]>=p)&&(z.g[0][0]-=p),z.g[1][0]=x.g[1][0]+y.g[1][0],(z.g[1][0]>=p)&&(z.g[1][0]-=p),z.g[0][1]=x.g[0][1]+y.g[0][1],(z.g[0][1]>=p)&&(z.g[0][1]-=p),z.g[1][1]=(x.g[1][1]+y.g[1][1])%p,(z.g[1][1]>=p)&&(z.g[1][1]-=p);}
inline node power(int y){A.C();B.g[0][0]=B.g[1][0]=B.g[0][1]=1,B.g[1][1]=0;while(y){if(y&1) M(A,B,C),A=C;y>>=1,M(B,B,C),B=C;}return A;} inline void PU(int x){P(S[x<<1],S[x<<1|1],S[x]);}
inline void PD(int x){M(T[x<<1],T[x],C),T[x<<1]=C,M(T[x<<1|1],T[x],C),T[x<<1|1]=C,M(S[x<<1],T[x],C),S[x<<1]=C,M(S[x<<1|1],T[x],C),S[x<<1|1]=C;T[x].C();}
inline void BD(int x,int l,int r){T[x].C();if(l==r){S[x]=power(a[l]-1);return;} int mid=l+r>>1;BD(x<<1,l,mid),BD(x<<1|1,mid+1,r),PU(x);}
inline void U(int x,int l,int r,int ll,int rr,int v){if(l>=ll&&r<=rr){M(S[x],V,D),S[x]=D,M(T[x],V,D),T[x]=D;return;}PD(x);int mid=l+r>>1;if(mid>=ll) U(x<<1,l,mid,ll,rr,v);if(mid<rr) U(x<<1|1,mid+1,r,ll,rr,v);PU(x); }
inline int Q(int x,int l,int r,int ll,int rr){if(l>=ll&&r<=rr) return S[x].g[0][0];PD(x);int mid=l+r>>1,res=0;if(mid>=ll) res=Q(x<<1,l,mid,ll,rr);if(mid<rr) res+=Q(x<<1|1,mid+1,r,ll,rr),(res>=p)&&(res-=p);return res;} struct FastIO{
static const int S=1048576;
char buf[S],*L,*R;int stk[20],Top;~FastIO(){clear();}
inline char nc(){return L==R&&(R=(L=buf)+fread(buf,1,S,stdin),L==R)?EOF:*L++;}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline void pc(char ch){Top==S&&(clear(),0);buf[Top++]=ch;}inline void endl(){pc('
');}
FastIO& operator >> (char&ch){while(ch=nc(),ch==' '||ch=='
');return *this;}
template<typename T>FastIO& operator >> (T&ret){
ret=0;int f=1;char ch=nc();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=nc();}
while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=nc();}ret*=f;return *this;
}
FastIO& operator >> (char* s){int Len=0;char ch=nc();while(ch!='
'){*(s+Len)=ch;Len++;ch=nc();}}
template<typename T>FastIO& operator << (T x){
if(x<0){pc('-');x=-x;}do{stk[++stk[0]]=x%10;x/=10;}while(x);
while(stk[0]) pc('0'+stk[stk[0]--]);return *this;
}
FastIO& operator << (char ch){pc(ch);return *this;}
FastIO& operator << (string str){int Len=str.size()-1;for(stk[0]=0;Len>=0;Len--) stk[++stk[0]]=str[Len];while(stk[0]) pc(stk[stk[0]--]);return *this;}
}fin,fout;
int main(){
fin>>n>>m;for(register int i=1;i<=n;i++) fin>>a[i];BD(1,1,n);while(m--){int op,l,r,x;fin>>op>>l>>r;if(op==2) fout<<Q(1,1,n,l,r)<<'
';else fin>>x,V=power(x),U(1,1,n,l,r,x);}return 0;
}
T2:
Description:
小a有三种颜色的珠子,分别有 (x,y,z) 个,保证 (x+y+z) 一定是某个高度为 (n) 的金字塔,即第 (i) 行有恰好 (i) 个珠子,并且小a希望每一行的珠子颜色相同,现在小a想问你对于给定的 (x,y,z) ,是否存在这样一组解。
Input:
第一行一个数 (T)。
之后每行三个数 (x,y,z) ,保证 (x+y+zle2 imes 10^9)。
Output:
(T) 行,每行一个数 (0/1) 表示输入是否可行。
Sample1 Input:
3
1 2 3
4 5 6
6 7 8
Sample1 Output:
1
1
1
Hint:
对于 (30\%) 的数据,(x+y+zle300)。
对于 (60\%) 的数据,(x+y+zle10^5)
对于 (100\%) 的数据,(x+y+zle2 imes10^9)。
题目分析:
我们强制 (xleq y leq z) 。首先我们考虑打表,发现当且仅当 (x=y=1) 或 (x=y=2) 时,无解。
我们思考为什么这个结论是对的。对于一组 (x,y,z) ,我们考虑如下的构造方式:
不断用 (z) 去填充当前金字塔的底层 (i) ,直到 (z<i),此时我们将 (x,y,z) 重排,继续重复上述操作,直到 (x=y=z=1) 或 (x=y=z=2) ,这时我们肯定能选出在之前填充的金字塔的某一排,用填充上一排的颜色与它交换,此时这一种颜色的数量就会多出一个,就能继续用于金字塔的填充。于是我们得到了一种简单的构造方法。
代码如下(马蜂很丑,不喜勿喷)——
#include<bits/stdc++.h>
#define N 200005
#define LL long long
using namespace std;
int T,x,y,z;//bool f[1005][1005];
struct FastIO{
static const int S=1048576;
char buf[S],*L,*R;int stk[20],Top;~FastIO(){clear();}
inline char nc(){return L==R&&(R=(L=buf)+fread(buf,1,S,stdin),L==R)?EOF:*L++;}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline void pc(char ch){Top==S&&(clear(),0);buf[Top++]=ch;}inline void endl(){pc('
');}
FastIO& operator >> (char&ch){while(ch=nc(),ch==' '||ch=='
');return *this;}
template<typename T>FastIO& operator >> (T&ret){
ret=0;int f=1;char ch=nc();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=nc();}
while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=nc();}ret*=f;return *this;
}
FastIO& operator >> (char* s){int Len=0;char ch=nc();while(ch!='
'){*(s+Len)=ch;Len++;ch=nc();}}
template<typename T>FastIO& operator << (T x){
if(x<0){pc('-');x=-x;}do{stk[++stk[0]]=x%10;x/=10;}while(x);
while(stk[0]) pc('0'+stk[stk[0]--]);return *this;
}
FastIO& operator << (char ch){pc(ch);return *this;}
FastIO& operator << (string str){int Len=str.size()-1;for(stk[0]=0;Len>=0;Len--) stk[++stk[0]]=str[Len];while(stk[0]) pc(stk[stk[0]--]);return *this;}
}fin,fout;
int main(){
fin>>T;while(T--){
// for(register int X=2;X<=10;X++){
// int S=X*(X+1)/2;for(register int xx=1;xx<=S/3;xx++) for(register int yy=xx;yy<=S-xx-yy;yy++){
// int x=xx,y=yy,z=S-x-y;if(x>y) swap(x,y);if(x>z) swap(x,z);if(y>z) swap(y,z);int l=0,r=64000,n=0;while(l<=r)
// {int mid=l+r>>1;if(1ll*mid*(mid+1)<=2ll*(x+y+z)) n=mid,l=mid+1;else r=mid-1;}memset(f,0,sizeof(f)),f[0][0]=1;
// for(register int i=1,m=0;i<=n;m+=i,i++) for(register int j=0;j<=m;j++) for(register int k=0;k<=m-j;k++) f[j+i][k]|=f[j][k],f[j][k+i]|=f[j][k];
// cout<<x<<' '<<y<<' '<<z<<' ';if(f[x][y]) puts("1");else puts("0");
// }
// }//打表找规律
fin>>x>>y>>z;if(x>y) swap(x,y);if(x>z) swap(x,z);if(y>z) swap(y,z);if(x==y&&(x==1||x==2)) puts("0");else puts("1");
}
return 0;
}
T3:
Description:
给定一个 (n*m) 的 (01) 矩阵,(q) 次询问,每次询问给出两个点 ((x1,y1),(x2,y2)) ,问能否从 ((x1,y1)) 仅向右向下走到达 ((x2,y2)) ,其中所有 (1) 的点都无法通行,并且保证询问的点上是 (0) 。
Input:
第一行三个数 (n,m,q) 之后 (n) 行每行一个长度是 (m) 的 (01) 串 最后 (q) 行每行四个数表示 (x1,x2,y1,y2)
Output:
对于可达的情况输出Yes,否则输出No
Sample1 Input:
5 5 8
00011
01001
01000
00001
00000
5 5 4 4
3 5 1 3
1 1 3 5
4 4 3 5
3 5 2 3
1 1 3 5
2 4 1 2
2 3 2 4
Sample1 Output:
Yes
Yes
No
No
No
No
Yes
No
Hint:
data range:
对于 (20\%) 的数据,(n,mle200,qle5000)
对于 (60\%) 的数据,(n,mle300,qle100000)
对于 (100\%) 的数据,(n,mle500,qle1000000)
注意前 (80\%) 的数据都有相当大的梯度
题目分析:
我们考虑用 (bitset) 维护以某个点出发能够到达那些点,这样的时间复杂度和空间复杂度都是 (O(frac{n^4}{w}))。时间复杂度足已通过此题,但是空间复杂度会爆炸。
我们考虑将询问离线,用 (vector) 存起来,于是空间复杂度可以降为 (frac{n^3}{w}),可以通过此题。
当然,我们可以通过分治将复杂度优化成 (O(frac{n^3logn}{w}))
标程代码如下(马蜂很丑,不喜勿喷)——
#include<bits/stdc++.h>
#define N 505
#define LL long long
using namespace std;
struct node{int x,y,id;};int n,m,q;bool ans[1000005];char s[N][N];bitset<N*N> F[2][N];vector<node> g[N][N];inline int num(int x,int y){return (x-1)*m+y-1;}
inline int read(){int ret=0,f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-') f=-f;ch=getchar();}while(isdigit(ch)) ret=(ret<<1)+(ret<<3)+ch-'0',ch=getchar();return ret*f;}
int main(){
n=read(),m=read(),q=read();for(register int i=1;i<=n;i++) scanf("%s",s[i]+1);
for(register int i=1;i<=q;i++){int sx=read(),tx=read(),sy=read(),ty=read();g[sx][sy].push_back(node{tx,ty,i});}
for(register int i=n,op=0;i;i--,op^=1) for(register int j=m;j;j--) if(s[i][j]=='0'){
F[op][j].reset();if(j^m&&s[i][j+1]=='0') F[op][j]=F[op][j+1];if(i^n&&s[i+1][j]=='0') F[op][j]|=F[op^1][j];F[op][j].set(num(i,j));
int S=g[i][j].size();for(register int k=0;k<S;k++) ans[g[i][j][k].id]=F[op][j][num(g[i][j][k].x,g[i][j][k].y)];
}
for(register int i=1;i<=q;i++) if(ans[i]) puts("Yes");else puts("No");return 0;
}
T4:
Description:
做到这里想必你已经接近ak了,那么不如赶紧做完这题去打游戏。
Takahashi
和 Aoki
玩取数游戏,双方轮流行动,从桌上一行数的两端中的某一段取一个数并加入自己的集合直到没有数可以取,此时集合内所有数的异或和大的人获胜。
Input:
第一行一个数 (T) ,表示数据组数
之后 (T) 行,每行第一个数 (n) 表示桌上数的个数
之后一行 (n) 个数表示桌上的 (n) 个数
Output:
先手必胜输出First,后手必胜输出Second,平局输出Draw。
Sample1 Input:
3
2
3 3
2
3 5
3
4 4 4
Sample1 Output:
Draw
First
Second
Hint:
对于 (30\%) 的数据,(nle1000)
对于 (100\%) 的数据,(Tle40, nle50000),所有输入为不超过INTMAX的正整数
题目分析:
首先有个显而易见的结论就是,如果所有数字的异或和为 (0) ,则必为平局;
在此基础上,如果异或和不为 (0) ,我们从高位到低位按位枚举,找到第一个异或和不为 (0) 的一位。显然胜负性只和这一位上的数字有关。于是问题转化为 0/1 序列上的博弈问题。
考虑DP,我们可以设 (f_{i,j}) 表示区间 ([i,j]) 中先手必败还是必胜,于是我们得到了一个 (O(T*n^2)) 的做法。
我们考虑打表找规律,可以发现若 (n) 为偶数,则先手必胜;
若 (n) 为奇数,则先手获胜必须满足以下两种条件中的任意一种条件:
条件一:
1.序列 (a_1,a_2,…,a_n) 中 (a_1=1)
2.将 (a_1) 删去后剩下的 (a_2,a_3,…,a_n)中不断删去两端相同的数
3.剩下的序列 (a_k,a_{k+1},…,a_{k+2*p-1}) 中满足 (a_k=a_{k+1}, a_{k+2}=a_{k+3},…,a_{k+2*p-2}=a_{k+2*p-1})
条件二
1.序列 (a_1,a_2,…,a_n) 中 (a_n=1)
2.将 (a_n) 删去后剩下的 (a_1,a_2,…,a_{n-1})中不断删去两端相同的数
3.剩下的序列 (a_k,a_{k+1},…,a_{k+2*p-1}) 中满足 (a_k=a_{k+1}, a_{k+2}=a_{k+3},…,a_{k+2*p-2}=a_{k+2*p-1})
证明的话留作读者的思考。(其实是我忘记怎么证了
代码如下(马蜂很丑,不喜勿喷)——
#include<bits/stdc++.h>
#define N 50005
using namespace std;
int T,n,a[N];bool ok[N],f[1005][1005][2],g[1005][1005];
inline void get(int x){for(register int i=n-1;~i;i--) if((1<<i)&x) putchar('1');else putchar('0');} struct FastIO{
static const int S=1048576;
char buf[S],*L,*R;int stk[20],Top;~FastIO(){clear();}
inline char nc(){return L==R&&(R=(L=buf)+fread(buf,1,S,stdin),L==R)?EOF:*L++;}inline void clear(){fwrite(buf,1,Top,stdout);Top=0;}
inline void pc(char ch){Top==S&&(clear(),0);buf[Top++]=ch;}inline void endl(){pc('
');}
FastIO& operator >> (char&ch){while(ch=nc(),ch==' '||ch=='
');return *this;}
template<typename T>FastIO& operator >> (T&ret){
ret=0;int f=1;char ch=nc();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=nc();}
while(ch>='0'&&ch<='9'){ret=ret*10+ch-'0';ch=nc();}ret*=f;return *this;
}
FastIO& operator >> (char* s){int Len=0;char ch=nc();while(ch!='
'){*(s+Len)=ch;Len++;ch=nc();}}
template<typename T>FastIO& operator << (T x){
if(x<0){pc('-');x=-x;}do{stk[++stk[0]]=x%10;x/=10;}while(x);
while(stk[0]) pc('0'+stk[stk[0]--]);return *this;
}
FastIO& operator << (char ch){pc(ch);return *this;}
FastIO& operator << (string str){int Len=str.size()-1;for(stk[0]=0;Len>=0;Len--) stk[++stk[0]]=str[Len];while(stk[0]) pc(stk[stk[0]--]);return *this;}
}fin,fout;
int main(){
fin>>T;for(register int tt=0;tt<T;tt++){
fin>>n;int S=0;for(register int i=1;i<=n;i++) fin>>a[i],S^=a[i],ok[i]=0;if(!S){puts("Draw");continue;} if(!(n&1)){puts("First");continue;}
/*for(register int i=1;i<=n;i++) for(register int j=i;j<=n;j++) f[i][j][0]=f[i][j][1]=1;*/for(register int s=30;~s;s--) if((1<<s)&S){
int sum=0;for(register int i=1;i<=n;i++) if((1<<s)&a[i]) ok[i]=1,sum++/*,g[i][i]=1,f[i][i][0]=1,f[i][i][1]=0;else g[i][i]=0,f[i][i][1]=1,f[i][i][0]=0*/;
if(sum%4!=1){puts("Second");break;} int i=2,j=n;while(ok[i]==ok[j]&&i<=j) i++,j--;if(i>j){puts("First");break;} bool flg=1;
for(register int k=i;k<=j;k+=2) if(ok[k]^ok[k+1]){flg=0;break;}if(flg){puts("First");break;}
i=1,j=n-1;while(ok[i]==ok[j]&&i<=j) i++,j--;if(i>j){puts("First");break;} flg=1;
for(register int k=i;k<=j;k+=2) if(ok[k]^ok[k+1]){flg=0;break;}if(flg){puts("First");break;} puts("Second");break;
// for(register int l=2;l<=n;l++) for(register int i=n-l+1;i;i--){
// int j=i+l-1;if(ok[i]^g[i+1][j]) f[i][j][1]&=f[i+1][j][1],f[i][j][0]&=f[i+1][j][0];else f[i][j][1]&=f[i+1][j][0],f[i][j][0]&=f[i+1][j][1];
// if(ok[j]^g[i][j-1]) f[i][j][1]&=f[i][j-1][1],f[i][j][0]&=f[i][j-1][0];else f[i][j][1]&=f[i][j-1][0],f[i][j][0]&=f[i][j-1][1];g[i][j]=(g[i][j-1]^ok[j]);f[i][j][0]=(!f[i][j][0]),f[i][j][1]=(!f[i][j][1]);
// }
// if(f[1][n][0]) get(tt),cout<<':';
// if(f[1][n][0]) puts("First");else puts("Second"); break;
}
}
return 0;
}