A - Xor Battle
算是比较难的
A
A
A题了,当时降智被卡了半天
显然倒着做,我们知道 A A A必胜的状态,记作集合 S S S。假设此时操作的是 A A A,那么 S ′ S' S′的基等于 S S S的基和 a i a_i ai的并。假设此时操作的是 B B B,注意到 A A A必胜和 B B B必败是等价的,所以 S ′ = { x ∣ x ∈ S , x ⊕ a i ∈ S } S'=\{x|x\in S,x\oplus a_i\in S\} S′={x∣x∈S,x⊕ai∈S}。我们知道 S S S是一个线性空间,如果 a i ∈ S a_i\in S ai∈S那么 T ′ = S T'=S T′=S,如果 a i ∉ S a_i\notin S ai∈/S,假设存在 x ∈ S , x ⊕ a i ∈ S x\in S,x\oplus a_i\in S x∈S,x⊕ai∈S,那么根据线性空间的基本性质有 a i ∈ S a_i\in S ai∈S,显然矛盾,因此 T ′ = ∅ T'=\varnothing T′=∅。线性基维护即可。
#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define inf 0x3f3f3f3f
using namespace std;
ll f[65],a[205],m;
int T,n;
string s;
void ins(ll x){
for(int i=62;i>=0;i--){
if(x>>i&1){
if(f[i])x^=f[i];
else{
f[i]=x;return;
}
}
}
}
int qry(ll x){
for(int i=62;i>=0;i--){
if(x>>i&1){
if(f[i])x^=f[i];
else return 0;
}
}return 1;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>T;
while(T--){
cin>>n,memset(f,0,sizeof f);
for(int i=0;i<n;i++)cin>>a[i];
cin>>s;int ok=0;
for(int i=n-1;i>=0;i--){
if(s[i]=='0')ins(a[i]);
else if(!qry(a[i]))ok=1;
}
cout<<ok<<"\n";
}
}
B - 01 Unbalanced
大致猜到了做法,但是不会证最后那个结论 雾
问题相当于求 [ 0 : n ] [0:n] [0:n]前缀和的极差。考虑限制最大值的上界,在此前提下使得最小值最大。我们希望这个最大值的上界最小,因此二分即可。这个结论结合贪心的过程不难证明。
似乎奇偶性这个地方有一点小细节,改一下就能过了
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
string s;
int n,Min,a[N],res2;
stack<int>S;
int check(int mid){
int m=0,m2=0;Min=0;
while(S.size())S.pop();
for(int i=0;i<n;i++){
if(s[i]=='0')m--,a[i]=-1;
else if(s[i]=='1'){
m++,a[i]=1;
if(m>mid){
if(!m2)return 0;
m-=2,m2--,a[S.top()]=-1,S.pop();
}
}
else{
if(m+1<=mid)m++,m2++,a[i]=1,S.push(i);
else m--,a[i]=-1;
}Min=min(Min,m);
}return 1;
}
int main(){
cin>>s,n=s.size();
int l=0,r=n/2,res=0;
while(l<=r){
int mid=l+r>>1;
if(check(mid*2+1))res=mid,r=mid-1;
else l=mid+1;
}
check(res*2+1);
int m=0;
for(int i=0;i<n;i++){
m+=a[i];assert(m<=res*2+1);
Min=min(Min,m);
}res2=res*2+1-Min;
l=0,r=n/2+1,res=0;
while(l<=r){
int mid=l+r>>1;
if(check(mid*2))res=mid,r=mid-1;
else l=mid+1;
}
check(res*2);
m=Min=0;
for(int i=0;i<n;i++){
m+=a[i];assert(m<=res*2);
Min=min(Min,m);
}res2=min(res2,res*2-Min);
cout<<res2;
}
C - Range Set
不妨设 A ≥ B A\ge B A≥B ,然后逆向操作,每次可以把连着的 A A A个 0 0 0或者 B B B个 1 1 1替换成任意字符,如果出现 A A A个 0 0 0就肯定赢了 。那么就把 ≥ B \ge B ≥B的 1 1 1都替换成 0 0 0然后看有没有 ≥ A \ge A ≥A的 0 0 0即可。
假设我们处理了前 i i i个位置,然后要接一段 0 0 0上去,显然可以分成多个阶段去做,如果要接一段 1 1 1上去,但是这一段长度 < A <A <A,那么相当于区间加,如果这一段 ≥ A \ge A ≥A,那么可以先一次性转移 A A A个字符,然后再分阶段转移。
复杂度 O ( n 2 ) O(n^2) O(n2)。
其实有点像
D
F
A
DFA
DFA缩小状态数那种感觉,但是我太菜了代码比大佬的复杂了好几倍
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
const int mod=1e9+7;
int n,A,B,f[5005][5005][3],f2[5005][5005][3];
void add(int &x,int y){
x=(x+y)%mod;
}
signed main(){
cin>>n>>A>>B;
if(A<B)swap(A,B);
f[0][0][0]=f[0][0][1]=1;
for(int i=0;i<n;i++){
for(int j=0;j<=A;j++){
add(f[i][j][0],f2[i][j][0]);
add(f[i][j][1],f2[i][j][1]);
add(f2[i+1][j][0],f2[i][j][0]);
add(f2[i+1][j][1],f2[i][j][1]);
if(f[i][j][0]){
add(f[i+1][min(A,j+1)][0],f[i][j][0]);
add(f[i+1][min(A,j+1)][1],f[i][j][0]);
}
if(f[i][j][1]){
add(f2[i+1][(j>=A)?A:0][0],f[i][j][1]);
add(f2[min(n+1,i+B)][(j>=A)?A:0][0],-f[i][j][1]);
add(f[min(n+1,i+B)][min(A,j+B)][2],f[i][j][1]);
add(f[min(n+1,i+B)][min(A,j+B)][0],f[i][j][1]);
}
if(f[i][j][2]){
add(f[i+1][min(A,j+1)][2],f[i][j][2]);
add(f[i+1][min(A,j+1)][0],f[i][j][2]);
}
}
}
cout<<((ll)f[n][A][0]+f2[n][A][0]+mod+mod)%mod;
}
D - Lamps and Buttons
首先最无脑的策略是,从亮着的灯中任意选一个来撸 (因为排列 p p p是随机的),然后把这个环里面的灯全部撸完(也就是全部点亮),如果遇到自环就失败了。
但是这样似乎就和每个环里面亮着的灯的数目有关系,因此难以优化。
那我们考虑一些比较脑洞的想法。
首先我们考虑,直接从 1 1 1到 A A A开始撸(这也是等价的最优策略),让我们先找到第一个 p i = i p_i=i pi=i的位置 t t t。然后对于 [ 1 : t − 1 ] [1:t-1] [1:t−1]这些亮着的灯会把整个环撸完,对于 [ t + 1 : A ] [t+1:A] [t+1:A]的灯初始是亮着的因此最后也是亮着的,对于 [ A + 1 : n ] [A+1:n] [A+1:n]的点所在的环就必须至少有一个 [ 1 : t − 1 ] [1:t-1] [1:t−1]中的点。
然后 [ 1 : t − 1 ] [1:t-1] [1:t−1]不能有自环,这可以用容斥来解决。因此我们可以等价转化为:统计大小为 x + y + z x+y+z x+y+z的排列, ∀ i ∈ z \forall i\in z ∀i∈z, ∃ j ∈ x \exist j\in x ∃j∈x, i i i和 j j j在同一个环中。
将排列看成一个有向图,每次加入一个 i i i,有两种可能:新建一条边 ( i , i ) (i,i) (i,i)或者选择一条边 ( x , y ) (x,y) (x,y),删掉 ( x , y ) (x,y) (x,y)并加上 ( x , i ) , ( i , y ) (x,i),(i,y) (x,i),(i,y)。
这个轮换令人想到斯特林数。。。。
注意到之前的枚举是 O ( n 2 ) O(n^2) O(n2),因此这里的计算应该是 O ( 1 ) O(1) O(1)的。事实上正解也非常巧妙:考虑在插入 x x x个数后立刻插入 z z z个数,这样 z z z只要不连自环,插在哪里都是合法的。因此方案数为 x ( x + y + z ) ! x + z \frac{x(x+y+z)!}{x+z} x+zx(x+y+z)!。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define ll long long
#define pb push_back
#define fi first
#define se second
using namespace std;
const int mod=1e9+7;
const int N=1e7+5;
int n,A;
ll fac[N],inv[N],res;
ll fpow(ll x,ll y=mod-2){
ll z(1);
for(;y;y>>=1){
if(y&1)z=z*x%mod;
x=x*x%mod;
}return z;
}
void init(int n){
fac[0]=1;for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
inv[n]=fpow(fac[n]);for(int i=n;i>=1;i--)inv[i-1]=inv[i]*i%mod;
}
ll binom(ll x,ll y){
if(x<0||y<0||x<y)return 0;
return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
ll calc(ll x,ll y,ll z){
assert(x+z>=1);
return x*fac[x+y+z]%mod*fac[x+z-1]%mod*inv[x+z]%mod;
}
int main(){
cin>>n>>A;
init(n);
for(int i=1;i<=A+1;i++){
for(int j=0;j<i;j++){
if(j&1){
res=(res-binom(i-1,j)*calc(i-1-j,max(0,A-i),n-A))%mod;
}
else{
res=(res+binom(i-1,j)*calc(i-1-j,max(0,A-i),n-A))%mod;
}
}
}cout<<(res+mod)%mod;
}