【loj 3271】「JOISC 2020 Day1」建筑装饰 4【DP】

传送门

Solution

首先考虑\(\mathcal O(n^2)DP\)

\(ch[0]\)\(A\)数组,\(ch[1]\)\(B\)数组

\(f[i][j][0/1]\)表示已经考虑了前\(i\)位,选了\(j\)\(B\)中的数,当前位置选择了\(0\)(A中的数)或\(1\)(B中的数),此时是否合法

易得转移方程为:

\[f[i][j][0]|=f[i-1][j][k]\cdot[ch[0][i]>=ch[k][i-1]]\\ f[i][j+1][1]|=f[i-1][j][k]\cdot[ch[1][i]>=ch[k][i-1]] \]

因为\(f\)的值均为\(0、1\),打表后可以发现对于对于任意的\(f[i][j][k]\),是\(f\)\(1\)\(j\)一定是连续的一段,考虑开\(2\)个数组\(fl[i][0/1]\)\(fr[i][0/1]\)表示考虑了前\(i\)位,当前位置选\(0/1\)时,合法的\(j\)的范围

转移直接取并即可:

if(fl[i-1][k]>fr[i-1][k]) continue;
if(ch[0][i]>=ch[k][i-1]) fl[i][0]=min(fl[i][0],fl[i-1][k]),fr[i][0]=max(fr[i][0],fr[i-1][k]);
if(ch[1][i]>=ch[k][i-1]) fl[i][1]=min(fl[i][1],fl[i-1][k]+1),fr[i][1]=max(fr[i][1],fr[i-1][k]+1);

至于输出方案,我们直接从末位置出发反过来向前枚举当前答案是怎么转移过来的即可

Code

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
const int M=4e3+10;
const int inf=0x3f3f3f3f;
int n,ch[2][N],s[N][2],fl[N][2],fr[N][2];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=2*n;++i) scanf("%d",&ch[0][i]);
	for(int i=1;i<=2*n;++i) scanf("%d",&ch[1][i]);
	fl[0][0]=fr[0][0]=0;
	fl[0][1]=fr[0][1]=0;
	for(int i=1;i<=2*n;++i){
		fl[i][0]=inf;fr[i][0]=-inf;
		fl[i][1]=inf;fr[i][1]=-inf;
		for(int k=0;k<=1;++k){
			if(fl[i-1][k]>fr[i-1][k]) continue;
			if(ch[0][i]>=ch[k][i-1]) fl[i][0]=min(fl[i][0],fl[i-1][k]),fr[i][0]=max(fr[i][0],fr[i-1][k]);
			if(ch[1][i]>=ch[k][i-1]) fl[i][1]=min(fl[i][1],fl[i-1][k]+1),fr[i][1]=max(fr[i][1],fr[i-1][k]+1);
		}
	}
	int now=-1;
	if(fl[2*n][0]<=n&&n<=fr[2*n][0]) now=0;
	else if(fl[2*n][1]<=n&&n<=fr[2*n][1]) now=1;
	if(now==-1){puts("-1");return 0;}
	vector<int> ve;
	int sum=n;
	for(int i=2*n;i>=1;--i){
		ve.push_back(now);
		if(now==1) --sum;
		for(int k=0;k<=1;++k){
			if(fl[i-1][k]>sum||fr[i-1][k]<sum) continue;
			if(ch[now][i]>=ch[k][i-1]){
				now=k;
				break;
			}
		}
	}
	for(int j=ve.size()-1;j>=0;--j) printf("%c",ve[j]+'A');
	return 0;
}
posted @ 2021-01-09 12:37  cjTQX  阅读(85)  评论(0编辑  收藏  举报