某次做dp題

1.石子合併

【問題描述】
在一圓形操場四周擺放N 堆石子, 現要將石子有次序地合併成一堆.規定每次只能選相鄰的
兩堆合併成一堆,並將新的一堆的石子數,記爲該次合併的得分。
編一程序,由文件讀入堆數N 及每堆石子數,
(1)選擇一種合併石子的方案,使得做N-1 次合併,得分的總和最少
(2)選擇一種合併石子的方案,使得做N-1 次合併,得分的總和最大
這裏寫圖片描述
【輸入】
第1 行:1 個整數n,表示石子的數量
第2 行:n 個用空格分開的整數,表示各堆石子的數量。
【輸出】
第1 行:1 個整數,表示最小的歸併代價
第2 行:1 個整數,表示最大的歸併代價
【輸入輸出樣例1】
3
13 7 8
43
49
【數據說明】
對於20%的數據,n ≤ 10
對於40%的數據,n ≤ 20
對於70%的數據,n ≤ 50
對於100%的數據,n ≤ 100,每堆石子數量不超過10000

分析:
很明顯經典的動態規劃題,只是這次是環形的而已,把數組加倍,變成線性的。
定義dp[i][j] 從 i 到 j 的最值
定義l 枚舉長度 2–2*n i 起始 j終止
則 1< i< 2*n-l-1 j=i+l-1
枚舉斷點k
dp[i][j]=max/min(dpmax[i][k]+dpmax[k+1][j]+sum[j]-sum[i-1])
sum 爲前綴和。

#include<cstdio>
#include<cstring>
#include<iostream>
#define mem(a,x) memset(a,x,sizeof(a))
using namespace std;
const int inf=0x3f3f3f3f;

int n;
int a[105],sum[205];
int dpmax[205][205],dpmin[205][205];

void init(){
  freopen("stone.in","r",stdin);
  freopen("stone.out","w",stdout);
}

void readdata(){
   scanf("%d",&n);
   for(int i=1;i<=n ;i++)
   {
     scanf("%d",&a[i]);
     sum[i]=sum[i-1]+a[i];//前綴和 
   }  
   //for(int i=1 ;i<=n;i++)
    // cout<<sum[i]<<" ";
   for(int i=n+1 ;i<=2*n-1 ;i++)
   sum[i] = sum[i-1] +sum[i-n]-sum[i-n-1];
}

void work(){
 for(int l=2;l<=2*n;l++)
  {
    for(int i=1 ;i<=2*n-l+1 ;i++ )
    {
      int j=i+l-1;
       int maxv=-inf,minv=inf;
       for(int k=i ;k<j; k++)
      {
        maxv=max(maxv,dpmax[i][k]+dpmax[k+1][j]+sum[j]-sum[i-1]);
        minv=min(minv,dpmin[i][k]+dpmin[k+1][j]+sum[j]-sum[i-1]);
      }
     dpmin[i][j]=minv;
     dpmax[i][j]=maxv;
    }  
  }
}

int main(){
  init();
  readdata();
  work();

  int ansmin=inf,ansmax=-inf;
  for(int i=1;i<=n;i++)
  {
     ansmin=min(ansmin,dpmin[i][i+n-1]);
     ansmax=max(ansmax,dpmax[i][i+n-1]);
  }

  printf("%d\n",ansmin);
  printf("%d",ansmax);

  return 0;
}

2.子序列

【問題描述】
在一場訓練賽中,姜神帶領他們隊AK 全場後,顯得特別無聊,無意中寫下n 個正整數,突
然靈機一動想出了一道美妙的題目。
姜神從這n 個正整數中選出一些數,組成一個非遞減的子序列,很容易就可以知道,有多少
個由正整數組成的長度跟這個子序列相等的序列比當前這個子序列小。
一個序列x = x1,x2,…xr 比另一個序列y = y1,y2,…yr 小,當且僅當,
對於任意的i ( 1 ≤ i ≤ r),xi ≤ yi
現在姜神想知道,對於所有不同的非遞減子序列,一共有多少個這樣的序列。
【輸入】
第1 行:1 個正整數n
第2 行:n 個用空格分開的整數
【輸出】
第1 行:1 個整數, 表示一共有多少個序列滿足要求,由於結果可能很大,輸出除以
1000000007 後的餘數。
【輸入輸出樣例1】
3
1 2 2

13

分析:
這題求數量當然也可以看做是一個動態規劃。
然而難點在於去重。
要全過還需要離散化。

#include <cstdio>
#include <algorithm>
using namespace std;
#define MAXN 100010
#define mo 1000000007
typedef long long LL;
int n;
LL Tree[MAXN],f[MAXN],dec[MAXN];
struct node
{
    int val,i,hash;
    bool operator<(const node &a) const{
        return i<a.i;
    }
}a[MAXN];
bool CMP(node a,node b)
{
    return a.val<b.val;
}
void Update(int i,LL val)
{
    for(;i<=n;Tree[i]=(Tree[i]+val)%mo,i+=i & -i);
}

LL Pretot(int i)
{
    LL res;
    for(res=0;i;res=(Tree[i]+res)%mo,i-=i & -i);
    return res;
}

void Solve()
{
    int i,res,ans=0;
    scanf("%d",&n);
    for(i=1;i<=n;++i){
        scanf("%d",&a[i].val);
        a[i].i=i;
    }

    sort(a+1,a+1+n,CMP);

    for(i=1;i<=n;++i){
        if(a[i].val==a[i-1].val)
            a[i].hash=a[i-1].hash;
        else a[i].hash=a[i-1].hash+1;
    }
    sort(a+1,a+1+n);

    for(i=1;i<=n;++i){
        res=(Pretot(a[i].hash)*a[i].val%mo+a[i].val)%mo;
        res-=dec[a[i].hash];
        res+= res<0?mo:0;
        dec[a[i].hash]=(dec[a[i].hash]+res)%mo;;
        ans=(0LL+ans+res)%mo;
        Update(a[i].hash,res);
    }
    printf("%d\n",ans);
}
int main()
{
    Solve();
    return 0;
}

3. Leego 的最長上升子序列

最長上升子序列 定義略。
有一天,Leego 拿到了一個序列,他知道一個序列的最長上升子序列可以有多個,所以它把
這個序列的每個下標i 分成了3 類:
1. i a 不屬於任何最長上升子序列
2. i a 屬於至少一個但非所有最長上升子序列
3. i a 屬於所有最長上升子序列
想在Leego 請你幫助他,寫一個程序,將所有下標i 分類。
【輸入】
第1 行:1 個整數n,表示序列的長度
第2 行:n 個用空格分開的整數,表示序列的元素
【輸出】
輸出一個含n 個字符的字符串,第i 個字符表示下標i 的類型。
【輸入輸出樣例1】
1
4

3
【輸入輸出樣例2】
4
1 3 2 5

3223
【輸入輸出樣例3】
4
1 5 2 3

3133

分析:
此題可以順着跑一遍 然後倒着跑一遍。
然後判斷是否同時出現過。

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