(未完待续。。。)
A - Xor Battle
首先应该很容易发现:如果所有1号可以放的数可以被后面0
号放的数xor起来抵消的话,则0号玩家肯定赢,反之1号赢。
这也非常好证明,我就不证了。
重点是如何解决以下问题:
一个数组a[1]…a[n] ,其中 ( n ≤ 1 e 5 , a [ i ] ≤ 1 e 18 ) (n\leq1e5,a[i]\leq1e18) (n≤1e5,a[i]≤1e18)判断能否取出一些数使得这些数异或起来=k ( k ≤ 1 e 18 ) (k\leq 1e18) (k≤1e18)
这个问题看起来比较棘手,因为直接模拟的话是
O
(
2
n
)
O(2^n)
O(2n)肯定不行。
这里就要介绍一个算法:
首先分享一个英语博客:https://codeforces.com/blog/entry/68953
这已经讲的很细了,但我还是看来很久才理解。我简单地描述一下吧:
就是定义
f
(
i
)
f(i)
f(i)表示最小的
f
(
i
)
f(i)
f(i)其中
(
i
>
>
f
(
i
)
)
a
n
d
1
=
1
(i >>f(i))and1=1
(i>>f(i))and1=1(也就是最低位的1的位)
然后维护以下组数
a
1
,
a
2
,
a
3
.
.
.
,
a
n
a_1,a_2,a_3...,a_n
a1,a2,a3...,an其中对于任何一对i,j 满足
f
(
a
i
)
≠
f
(
a
j
)
f(a_i) \neq f(a_j)
f(ai)=f(aj)其中
i
≠
j
i \neq j
i=j
所以这样的数最多
l
o
g
(
m
a
x
a
i
)
log(maxa_i)
log(maxai)个。
如果要想这个序列里加入一个数
x
x
x ,且依然满足以上性质。则就需要对
x
x
x进行"slightly modify"(略微的改动),然后加入,设改动过的数为
x
′
x'
x′。
但是我们还需要满足所有数组[
a
1
,
a
2
,
a
3
.
.
.
a
n
,
x
a_1,a_2,a_3...a_n,x
a1,a2,a3...an,x]能组成的数,数组[
a
1
,
a
2
,
a
3
.
.
.
x
′
a_1,a_2,a_3...x'
a1,a2,a3...x′]仍可以组成。
*注:这里的”组成“指"异或和"
我们可以执行以下操作:
将[
a
1
.
.
.
a
n
a_1...a_n
a1...an]按照
f
(
a
i
)
f(a_i)
f(ai)升序排序。
然后从1到n迭代,如果
1
a
n
d
(
x
>
>
f
(
a
i
)
)
=
1
1and(x>>f(a_i))=1
1and(x>>f(ai))=1则让x ^=ai。
这样做有以下好处
- 两两的 f f f值不同,数的个数控制在 l o g ( m a x a i ) log(maxa_i) log(maxai)
- x x x可以被 x ′ x' x′即其它的元素组成
这个数组维护好了,那怎样查找是否可以用其中的数来组成一个数
k
k
k呢?
其实很简单,由于
f
f
f的值两两不同,所以直接从低位到高位枚举就ok了。
算法的代码实现:
O
(
n
∗
l
o
g
(
m
a
x
a
i
)
)
O(n*log(maxa_i))
O(n∗log(maxai))
(那个cf blog里的代码,实现起来可能不太一样,但精髓是一样的):
int basis[d]; // basis[i] keeps the mask of the vector whose f value is i
int sz; // Current size of the basis
void insertVector(int mask) {
for (int i = 0; i < d; i++) {
if ((mask & 1 << i) == 0) continue; // continue if i != f(mask)
if (!basis[i]) { // If there is no basis vector with the i'th bit set, then insert this vector into the basis
basis[i] = mask;
++sz;
return;
}
mask ^= basis[i]; // Otherwise subtract the basis vector from this vector
}
}
这样A题瞬间就会做了:
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define KEEP while(1)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define debug_pair(A) cerr<<A.FIR<<" "<<A.SEC<<endl;
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<int,int> mp;
typedef pair<mp,mp> superpair;
const int d=64;
LL store[d];
//from 0
LL a[202];
string s;
void sv(){
int N;
cin>>N;
rb(i,1,N) cin>>a[i];
cin >>s;
reverse(ALL(s));
reverse(a+1,a+1+N);
s='%'+s;
memset(store,0,sizeof(store));
rb(i,1,N){
LL inser=a[i];
rep(j,d){
if(store[j]&&((inser>>(j))&1)){
inser ^=store[j];
}
}
if(s[i]-'0'){
if(inser){
cout<<1<<endl;
return;
}
}
else{
if(inser){
rep(j,d){
if(1&(inser>>j)){
store[j]=inser;
break;
}
}
}
}
}
cout<<0<<endl;
}
int main(){
fastio;
int T;
cin>>T;
while(T--) sv();
return 0;
}
B - 01 Unbalanced
假设用
s
u
m
i
,
0
sum_{i,0}
sumi,0表示1~i的0的个数,
s
u
m
i
,
1
sum_{i,1}
sumi,1反之。
所以答案就是要使
m
a
x
{
∣
(
s
u
m
j
,
0
−
s
u
m
i
,
0
)
−
(
s
u
m
j
,
1
−
s
u
m
i
,
1
)
∣
}
max\{|(sum_{j,0}-sum_{i,0})-(sum_{j,1}-sum_{i,1})|\}
max{∣(sumj,0−sumi,0)−(sumj,1−sumi,1)∣}
(
i
<
j
)
(i< j )
(i<j)尽可能小。
整理一下就可以发现:
m
a
x
{
∣
(
s
u
m
i
,
0
−
s
u
m
i
,
1
)
−
(
s
u
m
j
,
0
−
s
u
m
j
,
1
)
∣
}
max\{|(sum_{i,0}-sum_{i,1})-(sum_{j,0}-sum_{j,1})|\}
max{∣(sumi,0−sumi,1)−(sumj,0−sumj,1)∣}
可以重新理解一下题目
有一个数列 a 1 , a 2 , a 3 . . . , a n ( n ≤ 1 0 5 , a i = ± 1 ) a_1,a_2,a_3...,a_n(n\leq10^5,a_i=±1) a1,a2,a3...,an(n≤105,ai=±1),有些位已经固定了,有些没有,设每一位的前缀和为 s u m 1 , s u m 2 , s u m 3 . . . , s u m n sum_1,sum_2,sum_3...,sum_n sum1,sum2,sum3...,sumn,问他们的极差最小为多少。
有一个很明显的做法就是枚举
s
u
m
i
sum_i
sumi的上界,从前往后贪心使得
s
u
m
i
sum_i
sumi最小值尽可能大,也就是先把所有的’?'换成-1,然后从前往后扫一遍,能变成+1的就变成+1。
这样的复杂度使
O
(
n
2
)
O(n^2)
O(n2)的显然不行。
下面想优化:
设把所有的?换成-1时,最大的前缀和为
Z
Z
Z
如果固定的最大值+2,那么最小值最多+2。所以只需要考虑把
Z
Z
Z和
Z
+
1
Z+1
Z+1分别作为上界就行了。
C - Range Set
假设a>=b
首先如果一个序列
a
a
a可以被达到,那么必定存在一段连续的长度
≥
\geq
≥a/b的 0/1。
倒过来想:
如果有连续的一段长度>=a的0则一定可以。
如果把所有长度>=b的连续的1换成0,满足上一个情况则可以。
正常套路:
有所有情况-不合法的情况则为答案。
以下情况不合法:
- 把所有的长度<b的连续的1看作断点,把序列分成了很多段,把这些段叫做S
- 每个S的长度<a
这样dp就可以搞定了,设
d
p
i
,
j
,
k
dp_{i,j,k}
dpi,j,k表示考虑到第i位,与这位相连的S的长为j,这一位是k。
那么可以用以下的方式转移:
d
p
i
,
0
,
1
=
∑
d
p
m
,
n
,
0
(
i
−
m
<
b
,
n
≤
m
i
n
(
m
,
a
)
)
dp_{i,0,1}=\sum dp_{m,n,0}(i-m<b,n\leq min(m,a))
dpi,0,1=∑dpm,n,0(i−m<b,n≤min(m,a))
d
p
i
,
j
,
0
=
∑
d
p
i
−
m
,
j
−
m
,
1
(
j
≠
0
,
j
≤
m
i
n
(
i
,
a
)
,
m
≤
j
)
dp_{i,j,0}=\sum dp_{i-m,j-m,1}(j\neq0,j \leq min(i,a) ,m\leq j )
dpi,j,0=∑dpi−m,j−m,1(j=0,j≤min(i,a),m≤j)
d
p
i
,
j
,
1
=
∑
d
p
i
−
m
,
j
−
m
,
0
(
j
≠
0
,
j
≤
m
i
n
(
i
,
a
)
,
b
≤
m
≤
j
)
dp_{i,j,1}=\sum dp_{i-m,j-m,0}(j\neq0,j \leq min(i,a) ,b\leq m\leq j )
dpi,j,1=∑dpi−m,j−m,0(j=0,j≤min(i,a),b≤m≤j)
这是
O
(
n
3
)
O(n^3)
O(n3)的优化比较套路可以看以下代码:
/*
AuThOr Gwj
*/
#include<bits/stdc++.h>
#define rb(a,b,c) for(int a=b;a<=c;++a)
#define rl(a,b,c) for(int a=b;a>=c;--a)
#define LL long long
#define IT iterator
#define PB push_back
#define II(a,b) make_pair(a,b)
#define FIR first
#define SEC second
#define FREO freopen("check.out","w",stdout)
#define rep(a,b) for(int a=0;a<b;++a)
#define KEEP while(1)
#define SRAND mt19937 rng(chrono::steady_clock::now().time_since_epoch().count())
#define random(a) rng()%a
#define ALL(a) a.begin(),a.end()
#define POB pop_back
#define ff fflush(stdout)
#define fastio ios::sync_with_stdio(false)
#define debug_pair(A) cerr<<A.FIR<<" "<<A.SEC<<endl;
using namespace std;
const int INF=0x3f3f3f3f;
typedef pair<int,int> mp;
typedef pair<mp,mp> superpair;
int n,a,b;
LL g[10050][2],allsum,sum[5005],f[5005][5005][2];
const int MOD=1e9+7,base=5010;
int main(){
fastio;
cin>>n>>a>>b;
if(a<b) swap(a,b);
f[0][0][1]=1;
g[0+base][1]=1;
f[0][0][0]=1;
sum[0]=1;
allsum=1;
rb(i,1,n){
if(i>=b){
allsum-=sum[i-b];
allsum+=MOD;
allsum%=MOD;
rb(j,0,a-1){
if(j>=b)
g[j-i+base][0]+=f[i-b][j-b][0];
g[j-i+base][0]%=MOD;
}
}
rb(j,0,a-1){
if(j<=i)
rep(k,2){
if(j==0){
if(k==1){
f[i][j][1]=allsum;
}
}
else{
if(!k){
f[i][j][k]=g[j-i+base][1];
}
else{
f[i][j][k]=g[j-i+base][0];
}
}
}
sum[i]+=f[i][j][0];
sum[i]%=MOD;
g[j-i+base][1]+=f[i][j][1];
g[j-i+base][1]%=MOD;
}
allsum+=sum[i];
allsum%=MOD;
}
LL res=1;
rb(i,1,n) res<<=1,res%=MOD;
// cout<<f[1][0][1]<<endl;
rb(j,0,a-1){
rep(k,2){
res-=f[n][j][k];
// cout<<j<<" "<<k<<" "<<f[n][j][k]<<endl;
res+=MOD;
res%=MOD;
}
}
cout<<res<<endl;
return 0;
}