AtCoder Grand Contest 045

Preface

这场比赛真的是鸽了太久了的说,一来题挺难的,二来中间因为ZJOI和市统测占用了不少时间

所以题目都记不太得了随便胡一下吧

PS:太菜了所以只做了ABCD,其中BCD都是看一半题解才会的菜哭


A - Xor Battle

首先肯定考虑倒着做,我们维护\(0\)玩家能获胜的集合\(S\),考虑从后往前判断:

  • 当前为\(0\)玩家的回合,那么\(S'=S\cup\{x\operatorname{xor} a_i,x\in S\}\)
  • 当前为\(1\)玩家的回合,那么若\(a_i\in S\),则\(S'=S\);否则\(S'=\emptyset\)

为什么在\(1\)玩家的回合我们可以这么做?因为考虑\(0\)玩家的回合时我们相当于维护了一个线性基

\(a_i\in 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_i\ge l\)的情况下最小化最大值

首先我们考虑先把?全填成\(1\),然后贪心地从前往后改,考虑是否把\(1\)改成\(0\)(因为改后面的一定不会比前面优),并保证\(pre_i\ge 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\not =i\),若\(p_i\)暗了则重新点亮它。容易发现此时Snuke可以转而操作\(p_i\),直到把整个置换环上所有灯都点亮

因此我们可以得出一个合法的排列要满足:

  • \(t\)为最小的\(p_t=t\)的位置,若不存在则\(t=A+1\)
  • 对于所有的\(A+1\le i\le 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}\),因此

\[f(a,b,c)=a!\times \frac{(a+b-1)!}{(a-1)!}\times \frac{(a+b+c)!}{(a+b)!}\\ =(a+b+c)!\times \frac{a}{a+b} \]

所以就做完了,复杂度\(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

最近做题好慢啊,不过暑假要来了,能不能最后冲一次呢?

posted @ 2020-07-06 12:20  空気力学の詩  阅读(226)  评论(0编辑  收藏  举报