【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
**/

猜你喜欢

转载自blog.csdn.net/qq_43857314/article/details/106956148