[ABC368F] Dividing Game
题意:
有 \(n\) 堆石子,第 \(i\) 堆有 \(a_i\) 颗石子,每次可以拿走任意一堆石子数量任何数量的棋子,但是要保证拿走之后该堆的石子数量为原来的约数(不能不拿)。
问是先手必胜还是后手必胜。
\(n,a_i \le 10^5\)。
思路:
发现与 Nim 游戏类似,且全局信息公开,状态有限,故为公平组合游戏。
考虑构造必胜状态,设 \(\operatorname{F}(a) = \operatorname{xor}\limits_{i=1}^n \operatorname{f}(a_i)\),若 \(a_i = \prod\limits_{i=1}^k p_i^{q_i} (p_i \in prime)\),则 \(\operatorname{f}(a_i) = \sum\limits_{i=1}^k q_i\)。
手玩后容易发现:
-
当 \(\operatorname{F}(a) \ne 0\) 时为必胜态。
-
当 \(\operatorname{F}(a) = 0\) 时为必败态。
考虑证明。
-
当游戏无法进行时,即 \(a = \{1,1,\cdots,1,1\}\) 时,因为 \(1\) 没有质因子,故 \(\operatorname{f}(1)=0\),因为任意个 \(0\) 异或都得 \(0\),故 \(\operatorname{F}(a) = 0\),该状态为必败态。
-
若当前为必胜态,故绝对可以通过一些策略,使得下一个状态为必败态:
-
定义 \(\operatorname{F}(a)\) 二进制为 \(1\) 的最高位为 \(k\)。
-
则必然有一个 \(\operatorname{f}(a_i)\) 的第 \(k\) 位为 \(1\)。
-
考虑令 \(\operatorname{f}(a_i)' \gets \operatorname{f}(a_i) \operatorname{xor} \operatorname{F}(a)\),因为 \(\operatorname{F}(a)\) 的最高位为 \(k\),这样就消去了 \(\operatorname{f}(a_i)\) 第 \(k\) 位的值,因为 \(2^i > \sum_{i=0}^{i-1} 2^i\),故 \(\operatorname{f}(a_i) \operatorname{xor} \operatorname{F}(a) < \operatorname{f}(a_i)\)。
-
现在我们来考虑下是否可以取石子使得 \(\operatorname{f}(a_i)\) 变为小于它的任何数,容易发现是可以的,我们可以除掉任意质因子的任意次幂。
-
此时 \(\operatorname{F}(a)' = \operatorname{f}(a_1) \operatorname{xor} \operatorname{f}(a_2) \cdots \operatorname{f}(a_i) \operatorname{xor} \operatorname{F}(a) \operatorname{xor} \operatorname{f}(a_{i+1}) \cdots \operatorname{f}(a_n) = \operatorname{F}(a) \operatorname{xor} \operatorname{F}(a) = 0\)。
-
此时的后继状态 \(\operatorname{F}(a)'\) 就是必败态了。
-
-
若当前为必败态:
- 即 \(\operatorname{F}(a) = 0\),即所有 \(\operatorname{f}(a_i)\) 中二进制第 \(j\) 位为 \(1\) 数量为偶数,那么我们无论如何改变,肯定有至少一位的奇偶性发生变化,使得后继状态 \(\operatorname{F}(a)' \ne 0\),为必胜态。
这样我们证明了只有当 \(\operatorname{F}(a) \ne 0\) 时,先手必胜。
那么可以使用线性筛筛出每个数是被哪个质数给筛掉的,就可以快速求出一个数的所有质因子幂次之和。
时间复杂度为 \(O(N \log W)\)。
上面是给博弈论小白看的,但是如果您对博弈论颇为熟练,在发现我们可以将一个数的所有质因子幂次之和变为任何比它小的数时,就转化为了以所有质因子幂次之和为石子数的 Nim 游戏。
Code:
#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(a) memset(a,0,sizeof(a))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
const ll N=1e5+10;
inline ll read(){
ll x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
inline void write(ll x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9)
write(x/10);
putchar(x%10+'0');
}
ll cnt;
ll P[N],F[N];
bool f[N];
void init(){
For(i,2,N-1){
if(!f[i]){
P[++cnt]=i;
F[i]=i;
}
For(j,1,cnt){
if(i*P[j]>=N)
break;
F[i*P[j]]=P[j];
f[i*P[j]]=1;
if(i%P[j]==0)
break;
}
}
}
int main(){
init();
int N;
cin >> N;
vector<int> A(N);
for (int i = 0; i < N; i++){
cin >> A[i];
}
int g = 0;
for (int i = 0; i < N; i++){
if (A[i] == 1){
continue;
}
int c = 0;
while(A[i]>1){
A[i]/=F[A[i]];
c++;
}
g ^= c;
}
if (g != 0){
cout << "Anna" << endl;
} else {
cout << "Bruno" << endl;
}
}
[ABC368G] Add and Multiply Queries
题意:
给定两个长度为 \(n\) 的序列 \(a,b\),进行 \(m\) 次操作,需要支持 \(a,b\) 的单点修,进行多次区间查询:
- 按顺序从 \(l\) 走到 \(r\),初始 \(v=0\),设当前走到第 \(i\) 个位置,可以令 \(v\) 变为 \(v + a_i\) 或 \(v \times b_i\),问最后能取的最大值(答案 \(\le 10^{18}\))。
\(n,m \le 2 \times 10^5\)。
思路:
注意到 \(ans \le 10^{18}\),则如果中间都选乘的话,满足 \(b_i > 1(i \in [l,r])\) 的 \(i\) 最多只有 \(\log ans\) 个。
那么在查询区间 \([l,r]\) 的贡献时,先令 \(v = a_l\),然后再 \([l+1,r]\) 中二分出第一个 \(b_j > 1\) 的 \(j\),对于 \([l+1,j-1]\) 范围内的肯定都是选加,然后取 \(v \gets \max(v+a_j,v \times b_j)\) 即可;后面依次。
考虑使用 set
维护所有 \(b_i > 1\) 的 \(i\),使用树状数组维护前缀和即可。
时间复杂度为 \(O(N \log N \log W)\)。
Code:
#include<bits/stdc++.h>
#define Add(x,y) (x+y>=mod)?(x+y-mod):(x+y)
#define lowbit(x) x&(-x)
#define pi pair<ll,ll>
#define pii pair<ll,pair<ll,ll>>
#define iip pair<pair<ll,ll>,ll>
#define ppii pair<pair<ll,ll>,pair<ll,ll>>
#define fi first
#define se second
#define full(l,r,x) for(auto it=l;it!=r;it++) (*it)=x
#define Full(a) memset(a,0,sizeof(a))
#define open(s1,s2) freopen(s1,"r",stdin),freopen(s2,"w",stdout);
#define For(i,l,r) for(int i=l;i<=r;i++)
#define _For(i,l,r) for(int i=r;i>=l;i--)
using namespace std;
typedef long double lb;
typedef double db;
typedef unsigned long long ull;
typedef long long ll;
const ll N=1e5+10;
inline ll read(){
ll x=0,f=1;
char c=getchar();
while(c<'0'||c>'9'){
if(c=='-')
f=-1;
c=getchar();
}
while(c>='0'&&c<='9'){
x=(x<<1)+(x<<3)+(c^48);
c=getchar();
}
return x*f;
}
inline void write(ll x){
if(x<0){
putchar('-');
x=-x;
}
if(x>9)
write(x/10);
putchar(x%10+'0');
}
ll n,q,op,p,x,y,v;
ll a[N],b[N],t[N];
set<ll> S;
void add(ll x,ll v){
for(int i=x;i<=n;i+=lowbit(i))
t[i]+=v;
}
ll query(ll x){
ll ans=0;
for(int i=x;i;i-=lowbit(i))
ans+=t[i];
return ans;
}
int main(){
n=read();
For(i,1,n){
a[i]=read();
add(i,a[i]);
}
For(i,1,n){
b[i]=read();
if(b[i]>1)
S.insert(i);
}
q=read();
while(q--){
op=read(),x=read(),y=read();
if(op==1){
add(x,-a[x]);
a[x]=y;
add(x,a[x]);
}
else if(op==2){
if(y>1&&b[x]<2)
S.insert(x);
if(y<2)
S.erase(x);
b[x]=y;
}
else if(op==3){
v=a[x];
p=x+1;
while(p<=y){
auto it=S.lower_bound(p);
if(it==S.end()||(*it)>y){
v+=query(y)-query(p-1);
p=y+1;
}
else{
ll h=(*it);
v+=query(h-1)-query(p-1);
p=h+1;
v=max(v+a[h],v*b[h]);
}
}
write(v);
putchar('\n');
}
}
return 0;
}