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);
}

猜你喜欢

转载自www.cnblogs.com/lcxer/p/9846216.html
seq