AtCoder Grand Contest 048

Preface

难得ABCD都会做,但是EF完全不会,网上暂时没中文题解,官方的题解根本策不懂的说,因此先鸽着以后再说(咕咕咕预定)

Upt:10/26更新了EF的做法,还是很劲的说


A - atcoder < S

论一个人可以多么nt

很显然可以先判掉无需操作和无解的情况,那么显然现在字符串中一定存在至少一个不为\(a\)的位置

找到这个位置直接暴力往前交换即可,不难证明这样一定是最优的

#include<cstdio>
#include<iostream>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
int t,ans,lst; string s;
inline bool cmp(const string& s,const string& t)
{
	for (RI i=0;i<s.size()&&i<t.size();++i)
	if (s[i]!=t[i]) return s[i]<t[i]; return s.size()<t.size();
}
int main()
{
	for (cin>>t;t;--t)
	{
		cin>>s; if (cmp("atcoder",s)) { puts("0"); continue; }
		int pos=-1,ans=0; for (RI i=0;i<s.size();++i)
		if (s[i]>'a') { pos=i; break; } if (!~pos) { puts("-1"); continue; }
		while (!cmp("atcoder",s)) swap(s[pos],s[pos-1]),--pos,++ans;
		printf("%d\n",ans);
	}
	return 0;
}

B - Bracket Score

考虑我们定下某些下标来填(),假设共有\(x\)个这样的位置(\(x\)显然是偶数)

一个重要结论:一个合法的\(x\)个下标集合\(\Leftrightarrow\)\(x\)个下标构成为一半奇数一半偶数

充分性:我们可以在合法的括号序列中找到至少一对相邻()或·[],它们显然是一个下标奇数一个下标偶数,删除它们后运用归纳法即可

必要性:显然存在至少一组配对\(x\)个下标的方案,满足一个奇数位置和一个偶数位置配对,这样中间腾出的一定是偶数个空隙,[]也能找到一组合法的配对

那么现在问题就变成要找出\(x\)个下标满足构成为一半奇数一半偶数,并且最大化答案即可

根据某道题目中的经典套路,我们容易想到对于任意一种选择了\(\frac{n}{2}\)的分配方案,假设为一个\(01\)

我们把所有偶数下标上的数全部取反,不难证明此时所有\(0\)的位置的下标的构成必然是一半奇数一半偶数(PS:具体思路已补充在评论区)

偶数位取反后把所有的\(b_i\)都取了,然后取\(a_i-b_i\)\(\frac{n}{2}\)大的位置即可

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,a[N],b[N],c[N]; long long ans;
int main()
{
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=1;i<=n;++i) if (scanf("%d",&b[i]),!(i&1)) swap(a[i],b[i]);
	for (i=1;i<=n;++i) c[i]=a[i]-b[i],ans+=b[i];
	for (sort(c+1,c+n+1),i=(n>>1)+1;i<=n;++i) ans+=c[i];
	return printf("%lld",ans),0;
}

C - Penguin Skating

这类位置的题目很容易想到在\(0,L+1\)添加虚拟的点,然后求出所有位置的差值

这样两个序列相等就等价于差值的序列相等(因为起点终点都相等)

我们观察到此时在差值序列\(d\)上,一次移动相当于把\(d_i\)的值加给\(d_{i-1}\)\(d_{i+1}\),然后把\(d_i\)清零

考虑贪心,从左到右枚举\(B\)的差值序列对应的位置然后用\(A\)的差值序列去凑即可,这样显然是最优的

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,l,a[N],b[N],da[N],db[N]; long long ans;
int main()
{
	RI i,j; for (scanf("%d%d",&n,&l),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=1;i<=n;++i) scanf("%d",&b[i]); a[n+1]=b[n+1]=l+1;
	for (i=1;i<=n+1;++i) da[i]=a[i]-a[i-1]-1,db[i]=b[i]-b[i-1]-1;
	for (i=j=1;i<=n+1;++i) if (db[i])
	{
		while (!da[j]) ++j; int lst=j,cur=0;
		while (j<=n+1&&cur<db[i]) cur+=da[j],++j;
		if (cur!=db[i]) return puts("-1"),0;
		ans+=max(0,j-i-1)+max(0,i-lst);
	}
	return printf("%lld",ans),0;
}

D - Pocky Game

从未写过这么诡异的DP,结果正如陈指导所言:乱写之后乱调就过了

首先我们要注意到一个性质:每个人每次操作要么取\(1\)个要么取完

考虑一个剩下\(x\)个石子的状态能转移到的状态肯定是包含了所有剩下\(y(y<x)\)个石子的状态能转移到的状态,因此石子个数越多越好

首先我们可以很naive地设计出一个状态,\(f1_{l,r,x},f2_{l,r,x}\)表示对于\([l,r]\)的石子,左边(或右边)不完整的一堆是\(x\)时谁能胜利,但这样显然无法把石子这一维加入DP中,直接GG

对于这类无法放入状态的信息,我们就要考虑把它们记录到结果里,我们修改一下状态,设\(f1_{l,r}\)表示对于\([l,r]\)的石子,当\(a_l\)至少为多少时第一个人能赢(因为对于第一个人我们显然不需要管右边怎么样了),\(f2_{l,r}\)同上

接下来我们考虑怎么转移,以下以\(f1_{l,r}\)为例:

  • \(f2_{l+1,r}>a_r\)时,说明无论\(a_l\)取多少第一个人都必胜,因此\(f1_{l,r}=1\)

  • \(f2_{l+1,r}\le a_r\)时,考虑此时两个人肯定会一直进行一人取一个的对峙,考虑当右边的那一堆被取到\(f2_{l+1,r}\)时若第一个人已经把\(a_l\)取完了第二个人就获胜了,因此我们得出:

    \[f1_{l,r}-f1_{l,r-1}>a_r-f2_{l+1,r}\\ \Leftrightarrow f1_{l,r}=a_r-f2_{l+1,r}+f1_{l,r-1}+1 \]

对于\(f2_{l,r}\)同理,总复杂度为\(O(Tn^2)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=105;
struct element
{
	int x,y; //x:l is not completed; y:r is not completed
	inline element(CI X=-1,CI Y=-1) { x=X; y=Y; }
}f[N][N]; int t,n,a[N];
inline element DP(CI l,CI r)
{
	if (~f[l][r].x||~f[l][r].y) return f[l][r]; if (l==r) return element(1,1);
	element ret; if (DP(l+1,r).y>a[r]) ret.x=1; else ret.x=a[r]+DP(l,r-1).x-DP(l+1,r).y+1;
	if (DP(l,r-1).x>a[l]) ret.y=1; else ret.y=a[l]+DP(l+1,r).y-DP(l,r-1).x+1; return f[l][r]=ret;
}
signed main()
{
	for (scanf("%lld",&t);t;--t)
	{
		RI i,j; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
		for (i=1;i<=n;++i) for (j=i;j<=n;++j) f[i][j].x=f[i][j].y=-1;
		puts(DP(1,n).x<=a[1]?"First":"Second");
	}
	return 0;
}

E - Strange Relation

这题搞清楚一个地方其实就不难了,考虑对于一对\(j<i\)的关系,若\(a_j-T<a_i+x_i\times T\),那么我们显然可以把\(x_i\)\(1\)而保障限制始终满足

那么我们从后往前考虑,对于\(a_{i+1},\cdots,a_n\)中的\(a_j\),在其前面插入\(a_i\)\(x_j\)的影响(不考虑\(a_1,\cdots,a_{i-1}\)

  • \(x_i=0\),前面没有数字了
  • \(a_j+T\times x_j> a_i-T\),如上所述,令\(x_j\)\(1\)
  • \(a_j+T\times x_j\le a_i-T\)\(x_j\)不变

我们发现插入\(a_i\)时这些操作对于\(j\)时独立的,因此我们可以依次遍历\(a_{j-1},a_{j-2},\cdots,a_1\)即可确定\(a_j\)

我们考虑对于每个位置\(p\)的每一个取值分别DP,设\(f_{i,j}\)表示遍历到\(a_i\)\(x_k=j\)的方案数,\(O(k)\)的转移显然

最后贡献就是\(k^{n-p}\times \sum_{i=1}^n i\times f_{1,i}\),总复杂度是\(O(n^3k^2)\)

#include<cstdio>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=55,mod=1e9+7;
int n,m,t,a[N][N],f[N][N],ans;
int main()
{
	RI i,j,k,p,q; for (scanf("%d%d%d",&n,&m,&t),i=1;i<=n;++i)
	for (j=1;j<=m;++j) scanf("%d",&a[i][j]);
	for (i=1;i<=n;++i)
	{
		for (ans=0,j=1;j<=m;++j)
		{
			for (memset(f,0,sizeof(f)),f[i][0]=1,k=i-1;k;--k)
			for (p=0;p<n;++p) for (q=1;q<=m;++q)
			if (a[i][j]+1LL*t*p<=a[k][q]-t)	(f[k][p]+=f[k+1][p])%=mod;
			else (f[k][p+1]+=f[k+1][p])%=mod;
			for (k=1;k<=n;++k) (ans+=1LL*k*f[1][k]%mod)%=mod;
		}
		for (j=i+1;j<=n;++j) ans=1LL*ans*m%mod; printf("%d\n",ans);
	}
	return 0;
}

F - 01 Record

首先有一个比较显然的性质,对一个数连续操作时必定是\(01\)交错排列的,并且结尾一定是\(1\)

因此我们考虑把整个序列翻转之后,每次贪心地找到最长的\(101010\cdots\)形式的子序列并删除,记每次删除的字符串长度为\(l_1,l_2,\cdots,l_n\)

若开头出现\(0\)显然就无解了,否则我们发现此时当所有数分别等于\(l\)时必然存在一组解

现在我们假设\(S\)中的元素从大到小分别为\(x_1,x_2,\cdots,x_m\),显然\(m\ge n\),则一组\(x\)合法当且仅当:

  • \(L=\sum_{i=1}^n l_i=\sum_{i=1}^m x_i\)
  • \(\forall 1\le i\le n,\sum_{j=1}^i \lfloor \frac{l_j}{2}\rfloor\ge \sum_{j=1}^i \lfloor \frac{x_j}{2}\rfloor\)
  • \(\forall 1\le i\le n,\sum_{j=1}^i \lceil \frac{l_j}{2} \rceil \ge \sum_{j=1}^i \lceil \frac{x_j}{2} \rceil\)

首先考虑其必要性,第一个条件显然,因为对\(x_i\)操作必然会产生长度为\(x_i\)的字符串,然而有解的情况不存在剩余,因此相等

对于后两个限制,以第二个为例,由于每个\(x_i\)产生了长度为\(x_i\)\(101010\cdots\)的贡献,直接以此为\(l_i\)时恰好满足限制

然后我们如果把\(x_i\)分开,即让多个\(x_j\)来产生与\(x_i\)相等的贡献,它们的前缀和一定会变小,因此满足这个条件,第三个限制同理

关于充分性的证明过程可以看Official Editorial,这里不再赘述

然后我们就可以DP了,设\(f_{i,j,k,t}\)表示在\(x_1,x_2,\cdots,x_i\)中满足\(\sum_{s=1}^i \lfloor \frac{x_s}{2}\rfloor=j\)\(\sum_{s=1}^i \lceil \frac{x_s}{2} \rceil=k\)\(x_i=t\)的方案数,最终的答案即为

\[\sum_{i=n}^L \sum_{t=1}^L f_{i,\sum_{j=1}^n \lfloor \frac{l_j}{2}\rfloor,\sum_{k=1}^n \lceil \frac{l_k}{2} \rceil,t} \]

此时复杂度为\(O(L^5)\),考虑转移时可以用前缀和优化为\(O(L^4)\),同时由于\(x_1\ge x_2\ge\cdots\ge x_i\),而\(\sum_{j=1}^i x_i\le L\),因此\(t\le \lfloor \frac{L}{i}\rfloor\),此时复杂度为\(O(L^3\ln L)\),滚存一下可以把空间也整过去

#include<cstdio>
#include<cstring>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=305,mod=1e9+7;
char s[N]; int n,m,len,a[N],s1[N],s2[N],f[2][N][N][N],ans;
int main()
{
	RI i,j,k,t; for (scanf("%s",s+1),m=len=strlen(s+1),i=1;i<=m;++i)
	if (i<m-i+1) swap(s[i],s[m-i+1]); while (len)
	{
		if (s[1]=='0') return puts("0"),0; int c=1,tp=len;
		for (len=0,++n,i=1;i<=tp;++i) if (s[i]-'0'==c) ++a[n],c^=1; else s[++len]=s[i];
	}
	for (i=1;i<=m;++i) s1[i]=s1[i-1]+a[i]/2;
	for (i=1;i<=m;++i) s2[i]=s2[i-1]+(a[i]+1)/2;
	for (f[0][0][0][m]=i=1;i<=m;++i)
	{
		for (j=0;j<=s1[i];++j) for (k=0;k<=s2[i];++k)
		for (t=0;t<=m/max(i-2,1);++t) f[i&1][j][k][t]=0;
		for (j=0;j<=s1[i-1];++j) for (k=0;k<=s2[i-1];++k)
		{
			for (t=m/max(i-1,1)-1;t;--t) (f[i&1^1][j][k][t]+=f[i&1^1][j][k][t+1])%=mod;
			for (t=1;t<=m/i;++t) if (j+t/2<=s1[i]&&k+(t+1)/2<=s2[i])
			(f[i&1][j+t/2][k+(t+1)/2][t]+=f[i&1^1][j][k][t])%=mod;
		}
		if (i>=n-1) for (j=1;j<=m;++j) (ans+=f[i&1][s1[n]][s2[n]][j])%=mod;
	}
	return printf("%d",ans),0;
}

Postscript

数数题还是懵逼,DP水平还是太菜……

posted @ 2020-10-22 21:15  空気力学の詩  阅读(267)  评论(2编辑  收藏  举报