Preface
这场比赛真的是鸽了太久了的说,一来题挺难的,二来中间因为ZJOI和市统测占用了不少时间
所以题目都记不太得了随便胡一下吧
PS:太菜了所以只做了ABCD,其中BCD都是看一半题解才会的菜哭
A - Xor Battle
首先肯定考虑倒着做,我们维护(0)玩家能获胜的集合(S),考虑从后往前判断:
- 当前为(0)玩家的回合,那么(S'=Scup{xoperatorname{xor} a_i,xin S})
- 当前为(1)玩家的回合,那么若(a_iin S),则(S'=S);否则(S'=emptyset)
为什么在(1)玩家的回合我们可以这么做?因为考虑(0)玩家的回合时我们相当于维护了一个线性基
若(a_iin S)那么无论哪个数(operatorname{xor} a_i)都始终在线性基内,那么必输
否则它随便异或上一个数都不在(S)中(考虑异或的性质),那么必胜
所以直接线性基维护即可
#include<cstdio>
#define RI register int
using namespace std;
typedef long long LL;
const int N=205;
struct Linear_Basis
{
LL p[63];
inline void clear(void)
{
for (RI i=0;i<63;++i) p[i]=0;
}
inline void insert(LL x)
{
for (RI i=62;~i;--i) if ((x>>i)&1)
{
if (!p[i]) return (void)(p[i]=x); x^=p[i];
}
}
inline bool exist(LL x)
{
for (RI i=62;~i;--i) if ((x^p[i])<x) x^=p[i]; return !x;
}
}l;
int t,n; LL a[N]; char s[N];
int main()
{
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
scanf("%s",s+1); if (s[n]=='1') { puts("1"); continue; }
bool flag=0; for (l.clear(),i=n;i;--i) if (s[i]=='1')
{
if (!l.exist(a[i])) { flag=1; break; }
} else l.insert(a[i]);
puts(flag?"1":"0");
}
return 0;
}
B - 01 Unbalanced
首先考虑转化问题,把(0)看作(-1),(1)看作(1),然后一个区间的贡献就是区间和的绝对值
转化一下我们发现只要求出前缀和(pre_i),(pre)的极差就是最大贡献,所以我们要最小化极差
然后很naive想着随便枚举一下久好了,但是一个顺序问题怎么都搞不来,直接GG
看了题解,由于这里有两个border,所以我们卡死一个再优化另一个
考虑枚举(pre)的(min)为(l),那么现在我们要保证(pre_ige l)的情况下最小化最大值
首先我们考虑先把?
全填成(1),然后贪心地从前往后改,考虑是否把(1)改成(0)(因为改后面的一定不会比前面优),并保证(pre_ige l)
然后就有了一个(O(n^2))的做法,设(min{pre_i}=M),枚举(l=M,M-1,cdots 0)
观察贪心的过程,容易发现当(l=M-2)时的答案一定不会优于(l=M)时的答案,(l=M-3)时同理
因此只用考虑(l=M)和(l=M-1)的情况即可
#include<cstdio>
#include<cstring>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005;
char s[N]; int n,pfx[N],smi[N];
inline int work(CI mi)
{
RI i; int cur=0,mx=0; for (i=1;i<=n;++i)
{
if (s[i]=='?'&&smi[i]-cur-2>=mi) cur+=2;
mx=max(mx,pfx[i]-cur);
}
return mx-mi;
}
int main()
{
RI i; for (scanf("%s",s+1),n=strlen(s+1),i=1;i<=n;++i)
pfx[i]=pfx[i-1]+(s[i]=='0'?-1:1); smi[n]=pfx[n];
for (i=n-1;~i;--i) smi[i]=min(smi[i+1],pfx[i]);
return printf("%d",min(work(smi[0]),work(smi[0]-1))),0;
}
C - Range Set
首先我们发现(A,B)可以互换,因为可以在开始就把整个序列染成(1)然后将两种操作反转过来
因此我们考虑对于最后结果的序列(S),任意一个长度大于等于(A)的连续(0)区间可以把它染成任意一种颜色
同理对于任意一个长度大于等于(B)的连续(1)区间可以把它染成任意一种颜色
因此我们得到了判断一个序列是否合法的方法:将序列中所有长度大于等于(A)的连续(0)区间染成(1),若存在长度大于等于(B)的(1)区间,则序列合法
那么首先我们可以先DP出包含大于等于(A)个连续的(0)的长度为(i)的区间方案数(g_i),然后DP出不包含大于等于(A)个连续的(0)且不包含大于等于(B)个连续的(1)的串的方案数,用(g_i)做转移系数即可(说的好绕但很好写的)
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=5005,mod=1e9+7;
int n,a,b,f[N][2],g[N],dp[N][2],ans;
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int sum(CI x,CI y)
{
int t=x+y; return t>=mod?t-mod:t;
}
inline int sub(CI x,CI y)
{
int t=x-y; return t<0?t+mod:t;
}
inline int quick_pow(int x,int p,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
int main()
{
RI i,j; scanf("%d%d%d",&n,&a,&b); if (a>b) swap(a,b);
if (b==1) return printf("%d",quick_pow(2,n)),0;
for (f[0][0]=f[0][1]=i=1;i<=n;++i) for (j=1;j<=i;++j)
{
if (j>=a) inc(f[i][0],f[i-j][1]); inc(f[i][1],f[i-j][0]);
}
for (g[0]=i=1;i<=n;++i) g[i]=sum(f[i][0],f[i][1]);
for (i=1;i<n;++i)
{
if (i<a) dp[i][0]=1; for (j=1;j<min(i,a);++j) inc(dp[i][0],dp[i-j][1]);
if (i<b) dp[i][1]=g[i-1]; for (j=1;j<min(i,b);++j)
inc(dp[i][1],1LL*dp[i-j][0]*(j<=2?1:g[j-2])%mod);
if (n-i<a) inc(ans,dp[i][1]); if (n-i<b) inc(ans,1LL*dp[i][0]*g[n-i-1]%mod);
}
return printf("%d",sub(quick_pow(2,n),ans)),0;
}
D - Lamps and Buttons
刚开始读错题自闭了好久的说,后来发现原来Snuke不知道(p_i),那么其实排列随机,因此Snuke的策略其实没有影响
因此我们直接假定Snuke每次从编号小到大依次尝试按下一盏亮着的且未被操作的灯,容易发现有以下情况:
- (p_i=i),此时(i)熄灭,Snuke失败
- (p_i ot =i),若(p_i)暗了则重新点亮它。容易发现此时Snuke可以转而操作(p_i),直到把整个置换环上所有灯都点亮
因此我们可以得出一个合法的排列要满足:
- 令(t)为最小的(p_t=t)的位置,若不存在则(t=A+1)
- 对于所有的(A+1le ile N),(i)所在的置换环上至少存在一个(le t-1)的元素
考虑枚举(t)的位置,显然这样会有重复,所以我们还要枚举前面有多少个自环来容斥
考虑现在其实整个序列中的数有三类,一类是([1,t-1])的(设有(a)个),一类是([A+1,N])的(设有(b)个),一类是([t+1,A])的(设有(c)个)
考虑如何求方案数(f(a,b,c)),首先是前(a)个数有(a!)种放法,然后考虑加入(b)个元素,因为每个元素都要插入前面的一个环内,因此方案数是(a^{overline{b}})
对于最后(c)个元素,因为可以随便放,因此方案数是((a+b+1)^{overline c}),因此
所以就做完了,复杂度(O(A^2+N))
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e7+5,mod=1e9+7;
int n,m,fact[N],inv[N],ans;
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline void dec(int& x,CI y)
{
if ((x-=y)<0) x+=mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
for (inv[n]=quick_pow(fact[n]),i=n-1;~i;--i) inv[i]=1LL*inv[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
return 1LL*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
inline int calc(CI a,CI b,CI c)
{
return 1LL*fact[a+b+c]*a%mod*quick_pow(a+b)%mod;
}
int main()
{
RI i,j; for (scanf("%d%d",&n,&m),init(n),i=1;i<=m+1;++i)
for (j=0;j<i;++j) if (j&1) dec(ans,1LL*calc(i-1-j,n-m,max(m-i,0))*C(i-1,j)%mod);
else inc(ans,1LL*calc(i-1-j,n-m,max(m-i,0))*C(i-1,j)%mod);
return printf("%d",ans),0;
}
Postscript
最近做题好慢啊,不过暑假要来了,能不能最后冲一次呢?