【AGC048F】【2020六校联考WC #9】序列【构造】【DP】

【AGC048F/nflsoj#855/floj#3297】 序列

Description

Solution

为什么这题题号这么多?当然是因为出题人大胆搬AGC原题...

官方题解在,写的很好,可惜是英文,以下内容大概是翻译自原题解并加上了自己的理解。

首先将序列翻转,将操作转化为每次取出形如\(“10101010”\)的序列。考虑这样一种情况,我们每次都贪心地取出最长的可以取出的序列,记它们的长度为\(l_1,l_2\dots l_n\),显然这样做是取出元素最多的情况,如果这样都无法取完,那么一定无解。

我们将\(A\)中的元素从大到小排序为\(x_1,x_2\dots x_m\),那么可以证明\(A\)可以被得到当且仅当以下三个条件同时满足:

\(1.\)\(\sum l_i=\sum x_i\)

\(2.\)对于\(\forall j\in\{1,m\}\),有\(\sum_{i=1}^{j} \lceil \frac{l_i}{2}\rceil=\sum_{i=1}^{j} \lceil \frac{x_i}{2}\rceil\),这个代表前\(j\)个序列中\(1\)的个数

\(3.\)对于\(\forall j\in\{1,m\}\),有\(\sum_{i=1}^{j} \lfloor \frac{l_i}{2}\rfloor=\sum_{i=1}^{j} \lfloor\frac{x_i}{2}\rfloor\),这个代表前\(j\)个序列中\(0\)的个数

对于必要性,第\(1\)条左右两边均表示原序列长度,必然成立,对于第\(2,3\)条,每一个\(x_i\)都表示一个形如\(“10101010”\)的序列,而选出前\(j\)\(l_i\)一定能是\(1\)\(0\)的个数最大。

对于充分性,我们待会再证明。现在有了这三条条件我们就能进行\(DP\),设状态\(f[i][j][k][w]\)表示已经考虑了前\(i\)\(x\)\(x_i=j\)\(\sum_{d=1}^{i} \lceil \frac{x_d}{2}\rceil=k\)\(\sum_{d=1}^{i} \lfloor \frac{x_d}{2}\rfloor=w\),因为\(x[i]\le \frac{n}{i}\),于是总状态数是\(\mathcal O(n^3log(n))\)的,转移方程如下:

\[f[i][j][k][w]=\sum_{t\ge j}f[i-1][t][k-\lceil\frac j2\rceil][w-\lfloor\frac j2\rfloor] \]

于是可以前缀和优化达到时间复杂度\(\mathcal O(n^3log(n))\),空间复杂度也能通过滚动数组优化到\(\mathcal O(n^3)\)

回过头来证明充分性,首先给出一个引理:

引理:定义形如\(“1010101"\)的序列为好串。对于两个好串\(a\)\(b\)(\(|a|\le |b|\)),一定能将\(a\)\(b\)的字符全部取出来打乱顺序后重新拆分得到字符串\(c\)\(d\)满足:

\[|a|\le |c|\le |d|\le |b| \]

感性理解:假设将\(a\)\(b\)的字符全部取出来后组成了新字符串\(w\),在\(w\)中取出最长的好串\(p\),那么剩下的字符一定也是一个好串,我们称为\(q\)。必然有\(|b|\le |p|\),假设\(p\)\(q\)是通过一开始我们提到的中贪心方法选择的,那么我们一定可以通过重新安排它们中的元素得到\(c\)\(d\)

现在考虑我们需要通过某种方法重新安排\(l\)中的元素使它们变成\(x\),显然由\(n\le m\),因此我们在\(l\)后加几个\(0\)使\(l\)的长度也变为\(m\)

  • 首先我们寻找到一个\(i\)满足\(l_i<x_i\),如果不满足,那么可以推出\(l=x\),于是工作直接完成。
  • 找到\(i\)后,如果存在\(j\)满足\(j<i\)\(l_j\ge x_j+2\),那么有\(l_i< x_i\le x_j\le l_j-2\),于是根据上面的引理,我们可以将\(l_j\)\(l_i\)重组为\(l_j-2\)\(l_i+2\)
  • 否则,说明一定存在一个\(j\)满足\(j<i\)\(l_j=x_j+1\),否则无法满足性质\(2\)\(3\),因此\(l_i<x_i\le x_j\le l_j-1\),我们可以将\(l_j\)\(l_i\)重组为\(l_j-1\)\(l_i+1\)
  • 反复寻找\(i\),最终我们一定会成功将\(l\)转化为\(x\)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=310;
const int mod=1e9+7;
#define it set<int>::iterator
char ch[N];
int l[N],a[N],n,tot,d[2][N],sum[N];
inline void pre(set<int> s){
	if(!s.size()) return ;
	int op=1,len=0;
	set<int> nxt;
	it now=s.begin();
	while(now!=s.end()){
		if(a[*now]==op) len++,op^=1;
		else nxt.insert(*now);
		now++;
	}
	if(!len) return ;
	l[++tot]=len;
	pre(nxt);
}
ll f[2][N][N][N];
inline void upd(int &x,int y){x=x+y;}
signed main(){
	scanf("%s",ch+1);
	n=strlen(ch+1);
	reverse(ch+1,ch+n+1);
	set<int> s;
	for(int i=1;i<=n;++i) s.insert(i),a[i]=ch[i]-'0';
	pre(s);
	for(int i=1;i<=n;++i) d[0][i]=d[0][i-1]+l[i]/2,d[1][i]=d[1][i-1]+(l[i]+1)/2;
	if(d[0][n]+d[1][n]<n){puts("0");return 0;}
	for(int i=0;i<=n;++i) f[0][i][0][0]=1;
	for(int i=1;i<=n;++i){
		int cur=i&1,last=!cur,d0=d[0][i],d1=d[1][i];
		for(int j=0;j*i<=n&&j<=n;++j)
			for(int k=0;k<=d0;++k)
				for(int w=0;w<=d1;++w)
					f[cur][j][k][w]=f[last][j][k-j/2][w-(j+1)/2];
		for(int j=n/i-1;j>=0;--j){
			for(int k=0;k<=d0;++k)
				for(int w=0;w<=d1;++w)
					f[cur][j][k][w]+=f[cur][j+1][k][w];
		}
	}
	printf("%d\n",f[n&1][0][d[0][n]][d[1][n]]%mod);
	return 0;
}
posted @ 2021-01-25 21:29  cjTQX  阅读(57)  评论(0编辑  收藏  举报