zl程序教程

您现在的位置是:首页 >  其它

当前栏目

【luogu P6891】ビルの飾り付け 4(DP)(结论)

DP Luogu 结论
2023-09-27 14:28:28 时间

ビルの飾り付け 4

题目链接:luogu P6891

题目大意

给你两个长度为 2n 的序列,然后你要凑一个 2n 的序列每个位置可以从两个序列中对于的位置选一个。
然后要你保证得到的序列单调不降且两个序列各用了 n 个。
要你判断无解或输出其中一个合法方案。

思路

正题

首先一个显然的 DP 设 f i , j , 0 / 1 f_{i,j,0/1} fi,j,0/1 为前 i i i 个其中第一个序列用了 j j j 个最后一个第一个 / 第二个序列是否有答案。
然后这是 n 2 n^2 n2 的,考虑搞点性质。

观察连边的样子,会发现会存在这么一些限制。
有一些地方固定只能选一个,有一些地方选了某个后面跟着不能选某个。
(就小的可以连两个,大的只能连一个,我们从不能连的角度来看就是这样)

前面的很好搞,考虑后面的限制,那整个图肯定可以分成若干个互补联系的限制,然后联系的限制会形成一条链,具体一下就是链上的相邻两个不能同时选。
那对于每个链选的量就是一个区间,那若干个区间加起来也是一个区间,所以我们可以得到这么一个结论:
两个序列可以选的数量都是在一个区间之中的。

那由于互补,我们可以分别 DP 出 f i , 0 / 1 , 0 / 1 f_{i,0/1,0/1} fi,0/1,0/1 为前 i i i 个最后选的是第一个序列或第二个,第一个序列或第二个序列最多能选多少个。
所以只要两个序列的最大值都大于要求,也就落在了区间中。

那我们要求方案,我们就倒推,如果选了还能满足条件就选,然后如果选哪个都不满足就是无解。

ex口胡

牛客有道题是这个的是这个的升级版,就是要求合法选择方案,这里口胡一下懒得写了。

那我们继续从证明的地方来看,我们对于每段链来考虑,假设长度为 x x x,我们要在其中选 y y y 个,那插板法一下就是:
( x − y + 1 y ) \binom{x-y+1}{y} (yxy+1)(减掉的放在你选的后面强制不选)

然后你把这些弄成多项式,然后不难看出就是几个多项式乘起来取某一项,直接分治 + NTT 即可。

代码

#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

const int N = 1e6 + 100;
int n, a[N][2], f[N][2][2];
char ans[N];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n << 1; i++) scanf("%d", &a[i][0]);
	for (int i = 1; i <= n << 1; i++) scanf("%d", &a[i][1]);
	
	memset(f, -0x7f, sizeof(f));
	f[1][0][0] = 1; f[1][0][1] = 0; f[1][1][0] = 0; f[1][1][1] = 1;
	for (int i = 2; i <= n << 1; i++) {
		if (a[i - 1][0] <= a[i][0]) {
			f[i][0][0] = max(f[i][0][0], f[i - 1][0][0] + 1); f[i][0][1] = max(f[i][0][1], f[i - 1][0][1]);
		}
		if (a[i - 1][1] <= a[i][0]) {
			f[i][0][0] = max(f[i][0][0], f[i - 1][1][0] + 1); f[i][0][1] = max(f[i][0][1], f[i - 1][1][1]);
		}
		if (a[i - 1][0] <= a[i][1]) {
			f[i][1][0] = max(f[i][1][0], f[i - 1][0][0]); f[i][1][1] = max(f[i][1][1], f[i - 1][0][1] + 1);
		}
		if (a[i - 1][1] <= a[i][1]) {
			f[i][1][0] = max(f[i][1][0], f[i - 1][1][0]); f[i][1][1] = max(f[i][1][1], f[i - 1][1][1] + 1);
		}
	}
	
	int numa = 0, numb = 0, lst = 2e9;
	for (int i = n << 1; i >= 1; i--) {
		if (numa + f[i][0][0] >= n && numb + f[i][0][1] >= n && a[i][0] <= lst) {
			numa++; ans[i] = 'A'; lst = a[i][0];
		}
		else if (numa + f[i][1][0] >= n && numb + f[i][1][1] >= n && a[i][1] <= lst) {
			numb++; ans[i] = 'B'; lst = a[i][1];
		}
		else {
			printf("-1"); return 0;
		}
	}
	for (int i = 1; i <= n << 1; i++) putchar(ans[i]);
	
	return 0;
}