【Codeforces 1312E】Array Shrinking dp

題目鏈接:https://codeforces.ml/contest/1312/problem/E

題目大意:

給出一段序列,有一種操作可以將任意兩個相同的數字合併爲一個,最序列最後最少剩多少個數字?

題目思路:

下面介紹兩種解法,O(n^2)與O(n^3)

part_1 :

考慮用dp[i][k]表示最後的答案,那麼即有:

dp[i][k] = min(dp[i][k],dp[i][j]+dp[j+1][k]) (i<=j<k)

這個轉換是通用的,但是怎麼表示合併呢?

考慮到如果區間[i,k]可以合併的話,那最後必然是一個數字,無論有幾種合併方法,最後一個數字必然會相同,所以增加一個w[i][k]數組表示,w[i][k]合併的那個數,合併不可以即爲0。

那麼就可以增加判斷條件:

if(w[i][j]==w[j+1][k]) dp[i][k] = 1,w[i][k] = w[i][j]+1;

考慮到合併的數字最終狀態肯定相同所以w[i][k]不需要再加什麼特判了

Code_1:

/*** keep hungry and calm CoolGuang!***/
#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll INF=2e18;
const int maxn=1e6+6;
const int mod=1000000007 ;
const double eps=1e-15;
inline bool read(ll &num)
{char in;bool IsN=false;
    in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
ll num[maxn];
ll dp[505][505],w[505][505];
int main()
{
    read(n);
    for(int i=1;i<=n;i++)read(num[i]);
    for(int i=1;i<=n;i++){
        for(int k=1;k<=n;k++){
            if(i==k) dp[i][k] = 1,w[i][k] = num[i];
            else dp[i][k] = k-i+1;
        }
    }
    for(int len=1;len<=n;len++){
        for(int i=1;i+len<=n;i++){
            int e = i+len;
            for(int k=i;k<=e-1;k++){
                dp[i][e] = min(dp[i][e],dp[i][k]+dp[k+1][e]);
                if(dp[i][k]==1&&dp[k+1][e]==1&&w[i][k]==w[k+1][e]){
                    dp[i][e] = 1;
                    w[i][e] = w[i][k]+1;
                }
            }
        }
    }
    printf("%lld\n",dp[1][n]);
    return 0;
}

/**
01
**/

Part_2:

考慮dp[i]直接表示狀態,就會延伸出一個很常見的dp套路

dp[i] = min(dp[i],dp[k-1]+s)

s表示 [i,k]最終可以合併爲多少個

至於s的取值,直接用棧的思想去跑括號匹配即可

O(n^2)也就誕生:

Code_2

/*** keep hungry and calm CoolGuang!***/
#include <bits/stdc++.h>
#pragma GCC optimize(3)
//#pragma GCC optimize("Ofast","unroll-loops","omit-frame-pointer","inline")
#include<stdio.h>
#include<queue>
#include<algorithm>
#include<string.h>
#include<iostream>
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const ll INF=2e18;
const int maxn=1e6+6;
const int mod=1000000007 ;
const double eps=1e-15;
inline bool read(ll &num)
{char in;bool IsN=false;
    in=getchar();if(in==EOF) return false;while(in!='-'&&(in<'0'||in>'9')) in=getchar();if(in=='-'){ IsN=true;num=0;}else num=in-'0';while(in=getchar(),in>='0'&&in<='9'){num*=10,num+=in-'0';}if(IsN) num=-num;return true;}
ll n,m,p;
ll num[maxn];
ll dp[505];
int st[maxn];
int main()
{
    read(n);
    for(int i=1;i<=n;i++){
        read(num[i]);
        dp[i] = 1e8+7;
    }
    for(int i=1;i<=n;i++){
        int s = 0;
        for(int k=i;k>=1;k--){
            if(!s) st[++s] = num[k];
            else{
                ll now = num[k];
                while(s&&st[s]==now){
                    s--;
                    now++;
                }
                st[++s] = now;
            }
            dp[i] = min(dp[i],dp[k-1]+s);
        }
    }
    printf("%lld\n",dp[n]);
    return 0;
}

/**
01
**/

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章