seq(2018.10.24)

一道\(dp\)题。。。
期望\(40\)分解法
预处理:离散化,然后让连续一段值相同的元素合并为一个元素。
正式\(DP\)
显然有个最差策略为每个元素处都切一次,则切的次数为元素的个数\(-1\)
相对地来说就是假设全部元素之间就已经切开,要尽量多地合并元素
\(DP\)的第一维用来确认当前是合并了值为多少的两个数值段,DP的第二维来记住最后一次合并是合并了哪个位置的两个线段
\(DP[i][j]=\)对于值为\(1\)\(i+1\)的数值段, 最后一次合并为合并\(a[j]\)\(a[j+1]\)这两个元素,最多能合并的总次数
而相对应的转移方程就是:
\(DP[i][j] =max( DP[i-1][j']+1) (合并 a[j'] , a[j'+1] 不会与 合并a[j],a[j+1]冲突)\)
冲突是指合并\(a[i],a[i+1]\)的同时也合并\(a[j],a[j+1]\)会导致无法拼接成单调不降的序列,其充要条件是\(i+1=j\)且值为\(a[i+1]\)的数值段在原序列中出现了不止\(1\)次,

空间和时间复杂度都是\(O(n^2)\),期望得分\(40\)

期望\(100\)分解法
优化:
\(1.\)滚动数组优化空间为\(O(n)\)
\(2.\)因为对于每个\(i=x\),转移的时候只用考虑最大值和次大值,如果最大值和当前状态冲突,则用次大值更新

空间和时间复杂度都是\(O(n)\),期望得分\(100\)

#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
int n,a[100001],cnt,b[100001],tot,pre[100001],nxt[100001],h[100001],deg[100001];
pair<int,int>dp[2],DP[2];
map<int,int>mp;
void add(int x,int y){pre[++cnt]=y;nxt[cnt]=h[x];h[x]=cnt;deg[x]++;}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i];
	sort(b+1,b+n+1);
	for(int i=1;i<=n;i++)if(!mp[b[i]])mp[b[i]]=++tot;
	for(int i=1;i<=n;i++)a[i]=mp[a[i]];tot=0;
	for(int i=1;i<=n;i++)if(a[i]!=a[i+1])a[++tot]=a[i];
	for(int i=1;i<=tot;i++)add(a[i],i);
	for(int i=h[1];i;i=nxt[i])
		if(a[pre[i]+1]==2)
		{
			dp[1]=max(dp[1],make_pair(1,pre[i]));
			if(dp[1]>DP[1])swap(dp[1],DP[1]);
		}
	for(int i=2;i<cnt;i++)
	{
		dp[i&1]=dp[(i&1)^1];DP[i&1]=DP[(i&1)^1];
		for(int j=h[i];j;j=nxt[j])
			if(a[pre[j]+1]==a[pre[j]]+1)
			{
				if(DP[(i&1)^1].second+1!=pre[j]||deg[i]==1)dp[i&1]=max(dp[i&1],make_pair(DP[(i&1)^1].first+1,pre[j]));
				else dp[i&1]=max(dp[i&1],make_pair(dp[(i&1)^1].first+1,pre[j]));
				if(dp[i&1]>DP[i&1])swap(dp[i&1],DP[i&1]);
			}
	}
	printf("%d\n",tot-1-DP[(cnt-1)&1].first);
}
posted @ 2018-10-24 21:18  蒟蒻--lichenxi  阅读(250)  评论(0编辑  收藏  举报