专栏文章
「SMOI-R2」XA-Game 题解
P11326题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mio3pisi
- 此快照首次捕获于
- 2025/12/02 12:51 3 个月前
- 此快照最后确认于
- 2025/12/02 12:51 3 个月前
题意
Alice 和 Bob 在一个 序列上玩游戏,Alice 先手。
Alice 每次操作选择相邻两数,将它们合并为它们的异或值。
Bob 每次操作选择相邻两数,将它们合并为它们的与值。
求一共有多少 序列满足 Alice 必胜。
其中有 组限制形如序列的 项必为 。(还是算两个不同的序列)。
,多测。
思路
题意转化
不难发现,如果有超过两组连续的两个 (可以有公共的 ),那么 Bob 总可以保留这些零。
例如,Alice 将其中一个 异或上 ,那么相邻的那个 总能又把这个 变回来。
最后总会剩下这些 ,也就是形如 ,那么 Bob 必定赢,不论最开始的先后手。
否则,最后会变成 或 的情况,此时的先手必胜。
而 Alice 不会改变 的数量的奇偶性,只有 Bob 只有对 操作不会改变。
由于至多存在一个 子串,Alice 可以立即把所有的 字串消掉。
那么 Bob 每次都会改变 的数量的奇偶性,共 次。
因为最后只剩一个 ,所以若 的数量的奇偶性与 Bob 的总操作次数 相同,最后所有 就会被消掉,Alice 就输了。
所以,Alice 能赢当且仅当至多存在一个 子串,且 的数量的奇偶性与 Bob 的总操作次数 不同。
暴力 DP
然后就可以DP统计满足条件的序列数量了。先不考虑修改,设 表示前 位,当前为 ,出现 的次数, 的次数的奇偶性。
转移枚举上一位,结合这一位判断是否出现 ,和 的次数的奇偶性。
可以看一眼代码,初始值 。
然后就可以通过一些手模发现 和原来的初始化是等价的,然后就可以从 开始转移,避免后面的一些分类讨论。
最终答案就是 ,其中 为 的奇偶性。
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+10,mod=1e9+7;
int f[maxn][2][2][2];
signed main(){
// freopen("game.in","r",stdin);
// freopen("game.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;cin>>n;
f[1][1][0][1]=1;
f[1][0][0][0]=1;
for(int i=2;i<=n;i++)
for(int nw=0;nw<2;nw++)
for(int s1=0;s1<2;s1++)
for(int lst=0;lst<2;lst++)
for(int x=(!nw&&!lst);x<2;x++)
(f[i][nw][x][s1]+=f[i-1][lst][x-(!nw&&!lst)][s1^nw])%=mod;
int k=((n-1)/2)%2;
cout<<(f[n][0][0][!k]+f[n][0][1][!k]+f[n][1][0][!k]+f[n][1][1][!k])%mod;
return 0;
}
你会发现,这个东西太慢了,不妨先循环展开,就变成了这个样子。
CPP(f[i][0][0][0]+=f[i-1][1][0][0])%=mod;
(f[i][0][0][1]+=f[i-1][1][0][1])%=mod;
(f[i][0][1][0]+=f[i-1][0][0][0])%=mod;
(f[i][0][1][0]+=f[i-1][1][1][0])%=mod;
(f[i][0][1][1]+=f[i-1][0][0][1])%=mod;
(f[i][0][1][1]+=f[i-1][1][1][1])%=mod;
(f[i][1][0][0]+=f[i-1][1][0][1])%=mod;
(f[i][1][0][0]+=f[i-1][0][0][1])%=mod;
(f[i][1][0][1]+=f[i-1][0][0][0])%=mod;
(f[i][1][0][1]+=f[i-1][1][0][0])%=mod;
(f[i][1][1][0]+=f[i-1][0][1][1])%=mod;
(f[i][1][1][0]+=f[i-1][1][1][1])%=mod;
(f[i][1][1][1]+=f[i-1][0][1][0])%=mod;
(f[i][1][1][1]+=f[i-1][1][1][0])%=mod;
为了方便查看,将后三个状态合为一个,值域 ,用二进制的方式存储。
CPP(f[i][0]+=f[i-1][4])%=mod;
(f[i][1]+=f[i-1][5])%=mod;
(f[i][2]+=f[i-1][0]+f[i-1][6])%=mod;
(f[i][3]+=f[i-1][1]+f[i-1][7])%=mod;
(f[i][4]+=f[i-1][5]+f[i-1][1])%=mod;
(f[i][5]+=f[i-1][0]+f[i-1][4])%=mod;
(f[i][6]+=f[i-1][3]+f[i-1][7])%=mod;
(f[i][7]+=f[i-1][2]+f[i-1][6])%=mod;
其中上半部分是本位为 的转移,下半部分是本位为 的转移。
那么只需要在对应的 有限制 的情况下将另一个部分的 值设为 ,就像:
CPPif(s[i].v==0)f[4]=f[5]=f[6]=f[7]=0;
if(s[i].v==1)f[0]=f[1]=f[2]=f[3]=0;
然后就有 做法了,考虑优化。
矩乘优化
这个转移特别像矩阵乘法的形式,直接套上去即可,转移矩阵自己手推一下,这里不展开叙述。
注意每次相邻的两个 值乘上对应的次幂,注意最后的答案要乘上 (询问的是原始序列数量)。
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxm=5e5+10,mod=1e9+7;
struct vec8{
int vec[8];
vec8(){memset(vec,0,sizeof vec);}
int operator[](int x)const{return vec[x];}
int& operator[](int x){return vec[x];}
friend int operator*(const vec8& a,const vec8& b){
int res=0;
for(int i=0;i<8;i++)res=(res+a[i]*b[i]%mod)%mod;
return res;
}
};
struct mat8{
vec8 col[8];
mat8(){}
vec8 operator[](int x)const{return col[x];}
vec8& operator[](int x){return col[x];}
friend vec8 operator*(const vec8&row,const mat8& A){
vec8 res;
for(int i=0;i<8;i++)res[i]=row*A[i];
return res;
}
friend mat8 operator*(const mat8&A,const mat8& B){
mat8 C;
for(int i=0;i<8;i++)
for(int j=0;j<8;j++)
for(int k=0;k<8;k++)
C[i][j]=(C[i][j]+A[i][k]*B[k][j]%mod)%mod;
return C;
}
}M;//注意我的实现比较特别,是先列后行
int ksm(int a,int b){
int c=1;
while(b){
if(b&1)c=c*a%mod;
a=a*a%mod;b>>=1;
}
return c;
}
mat8 ksm(mat8 a,int b){
mat8 c;
for(int i=0;i<8;i++)c[i][i]=1;
while(b){
if(b&1)c=c*a;
a=a*a;b>>=1;
}
return c;
}
struct node{int a,v;}s[maxm];
void solve(){
int n,m;cin>>n>>m;
for(int i=1;i<=m;i++)cin>>s[i].a>>s[i].v;
s[0]={0,-1};s[m+1]={n,-1};
vec8 f;f[4]=1;
for(int i=1;i<=m+1;i++){
f=f*ksm(M,s[i].a-s[i-1].a);
if(s[i].v==0)f[4]=f[5]=f[6]=f[7]=0;
if(s[i].v==1)f[0]=f[1]=f[2]=f[3]=0;
}
int res=0,k=((n-1)/2)&1;
for(int i=0;i<8;i++)
if((i&1)!=k)res=(res+f[i])%mod;
cout<<res*ksm(2,m)%mod<<'\n';
}
signed main(){
// freopen("game.in","r",stdin);
// freopen("game.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
M[0][4]=1;M[1][5]=1;
M[2][0]=1;M[2][6]=1;
M[3][1]=1;M[3][7]=1;
M[4][5]=1;M[4][1]=1;
M[5][0]=1;M[5][4]=1;
M[6][3]=1;M[6][7]=1;
M[7][2]=1;M[7][6]=1;
int t;cin>>t;
while(t--)solve();
return 0;
}
然后你就有 分了,发现瓶颈在快速幂?这我熟,直接光速幂。
光速幂的大概原理就是,把一个数拆成 进制数,其数位只有常数级别。
例如 ,那么 ,其中 。
如果分别预处理 和 的 次幂,就可以 求出幂。
本题中由于 ,这里取 ,那么 ,数位最多为 。
同理拆开预处理,预处理 的 次幂,就可以 求出幂了。
有一个小细节:最后把三个矩阵拼起来的时候直接各自与向量相乘,这样是 而不是 的。
总时间复杂度:,实测可过。
代码
CPP#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxm=5e5+10,mod=1e9+7,maxp=1e5+10,sqp=1e5;
struct vec8{
int vec[8];
vec8(){memset(vec,0,sizeof vec);}
int operator[](int x)const{return vec[x];}
int& operator[](int x){return vec[x];}
friend int operator*(const vec8& a,const vec8& b){
int res=0;
for(int i=0;i<8;i++)res=(res+a[i]*b[i]%mod)%mod;
return res;
}
};
struct mat8{
vec8 col[8];
mat8(){}
vec8 operator[](int x)const{return col[x];}
vec8& operator[](int x){return col[x];}
friend vec8 operator*(const vec8&row,const mat8& A){
vec8 res;
for(int i=0;i<8;i++)res[i]=row*A[i];
return res;
}
friend mat8 operator*(const mat8&A,const mat8& B){
mat8 C;
for(int i=0;i<8;i++)
for(int k=0;k<8;k++)
if(A[i][k])for(int j=0;j<8;j++)
C[i][j]=(C[i][j]+A[i][k]*B[k][j]%mod)%mod;
return C;
}
}E,M,lw[maxp],md[maxp],hi[maxp];//注意我的实现比较特别,是先列后行
int ksm(int a,int b){
int c=1;
while(b){
if(b&1)c=c*a%mod;
a=a*a%mod;b>>=1;
}
return c;
}
struct node{int a,v;}s[maxm];
void init(){
for(int i=0;i<8;i++)E[i][i]=1;
M[0][4]=1;M[1][5]=1;
M[2][0]=1;M[2][6]=1;
M[3][1]=1;M[3][7]=1;
M[4][5]=1;M[4][1]=1;
M[5][0]=1;M[5][4]=1;
M[6][3]=1;M[6][7]=1;
M[7][2]=1;M[7][6]=1;
lw[0]=md[0]=hi[0]=E;
for(int i=1;i<=sqp;i++)lw[i]=lw[i-1]*M;
for(int i=1;i<=sqp;i++)md[i]=md[i-1]*lw[sqp];
for(int i=1;i<=sqp;i++)hi[i]=hi[i-1]*md[sqp];
}
void transfer(vec8& f,int b){
int h=b/sqp/sqp%sqp,m=b/sqp%sqp,l=b%sqp;
f=f*hi[h]*md[m]*lw[l];
}//转移
void solve(){
int n,m;cin>>n>>m;
for(int i=1;i<=m;i++)cin>>s[i].a>>s[i].v;
s[0]={0,-1};s[m+1]={n,-1};
vec8 f;f[4]=1;
for(int i=1;i<=m+1;i++){
transfer(f,s[i].a-s[i-1].a);
if(s[i].v==0)f[4]=f[5]=f[6]=f[7]=0;
if(s[i].v==1)f[0]=f[1]=f[2]=f[3]=0;
}
int res=0,k=((n-1)/2)&1;
for(int i=0;i<8;i++)
if((i&1)!=k)res=(res+f[i])%mod;
cout<<res*ksm(2,m)%mod<<'\n';
}
signed main(){
// freopen("game.in","r",stdin);
// freopen("game.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;cin>>t;init();
while(t--)solve();
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...