1. 程式人生 > >Codeforces Round #522 div2 C、E題解(DP)

Codeforces Round #522 div2 C、E題解(DP)

題目連結:

C. Playing Piano

題意:

給一個序列,讓你構造一個相等長度的序列,構造的序列中每個元素的取值範圍都為[1,5]。

構造要求:

1. 若原序列a[i]==a[i+1],那麼構造的序列b[i]!=b[i+1];

2. 若原序列a[i]>a[i+1],那麼構造的序列b[i]>b[i+1];

3. 若原序列a[i]<a[i+1],那麼構造的序列b[i]<b[i+1];

若答案存在,輸出任意一個,否則輸出-1。

思路:

暴力dp。開一個dp[N][5],若第 i 位是 k (1<=k<=5),且到第 i 位為止滿足要求,那麼dp[i][k]=1,否則dp[i][k]=0。由於要儲存路徑,所以把dp陣列弄成一個結構體,用pre變數記錄當前位上一位的答案的值。

code:

#include <bits/stdc++.h>
using namespace std;

typedef long long  ll;

const ll INF = 0x3f3f3f3f3f3f3f3f;
const int MAX = 1e5+100;

typedef struct{
    int val;
    int pre;
}Point;

int n;
int a[MAX];
Point dp[MAX][10];
int ans[MAX];

int main()
{
    scanf("%d",&n);
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
    }
    if(n==1){
        printf("1\n");
        return 0;
    }
    for(int i=1;i<=5;i++){
        dp[0][i].val=1;
    }
    for(int  i=1;i<n;i++){
        for(int j=1;j<=5;j++){
            if(dp[i-1][j].val){
                if(a[i]>a[i-1]){
                    for(int k=j+1;k<=5;k++){
                        dp[i][k].val=1;
                        dp[i][k].pre=j;
                    }
                }
                else if(a[i]==a[i-1]){
                    for(int k=1;k<=5;k++){
                        if(k==j)    continue;
                        dp[i][k].val=1;
                        dp[i][k].pre=j;
                    }
                }
                else if(a[i]<a[i-1]){
                    for(int k=1;k<j;k++){
                        dp[i][k].val=1;
                        dp[i][k].pre=j;
                    }
                }
            }
        }
    }
    int cnt=0;
    int pos=-1;
    //判斷是否有滿足條件的解
    for(int i=1;i<=5;i++){
        if(dp[n-1][i].val){
            ans[cnt++]=i;
            pos=dp[n-1][i].pre;
            ans[cnt++]=pos;
            break;
        }
    }
    if(pos==-1){
        printf("-1\n");
        return 0;
    }
    for(int i=n-2;i>=1;i--){
        ans[cnt++]=dp[i][pos].pre;
        pos=dp[i][pos].pre;
    }
    for(int i=cnt-1;i>=0;i--){
        if(i==cnt-1)  printf("%d",ans[i]);
        else  printf(" %d",ans[i]);
    }
    printf("\n");
    return 0;
}

題目連結:

E. The Unbearable Lightness of Weights

題意:

有 n 個砝碼,給定它們的重量,但對應關係不知道(即不知道第幾個砝碼重多少,只知道它的重量肯定是給的數字中的一個)。但你的朋友是知道對應關係的,你可以問他一個問題(只能問一次),你只能問 k 個砝碼重量為 m(k、m的值你自己隨便定),他會給你 k 個砝碼,這些砝碼的重量恰好為 m,如果有多種情況,他會隨便給你一種(給你的 k 個砝碼的重量對應關係也是不知道的)。問你最多能確定多少個砝碼的重量。

思路:

題目雖然很短,但題意理解了半天......

由於問完之後朋友返回給你的那些砝碼重量的對應關係也是不知道的,所以只有在那 k 個砝碼的重量相等時才能確定他們各自的重量

。但因為 k 個砝碼重量為 m 的情況可能不止一種,比如3個砝碼:4、4、4,總重為12,1、3、9,總重也為12。所以當情況不止一種時,就算 k 個砝碼重量一樣也還是不能確定的(因為你不知道朋友給你的是不是恰好是重量一樣的那種)。

所以題目轉變為要判斷一個序列中,任意k個數的和等於m的情況數。用dp可解:

dp[num][sum][2]:num表示有num個數相加,sum表示num個數的和,0表示當前狀態,1表示可轉移到的狀態。

為什麼要用2個狀態去計算呢?因為只用一個狀態的話,當前的a[i]可能會多加很多次。

那麼轉移方程為:

當dp[num][sum][0]存在時,dp[num+1][sum+a[i]][1]+=dp[num][sum][0];

到這問題基本就已經解決了,但還要注意題目的一些細節

比如有4、4、4,但只能取2個數(假設有1、3、9),這3個 4 本身能組成 3 個 8,我們要去找的是除了這3個 4 之外還存不存在2個數和為 8 的情況。此外,如果總共只有2種重量的砝碼,且能把其中的一種全部確定,那剩下的那種也能全部確定,比如1、1、2、2、2、2,問k = 4 , m = 8,就能把重量為2的砝碼全部確定,那麼剩下兩個重量肯定都只能為 1 。

code:

#include <bits/stdc++.h>
using namespace std;

typedef long long  ll;

const ll INF = 0x3f3f3f3f3f3f3f3f;
const int MAX = 1e5+100;

int n;
int a[110];
ll dp[110][10010][3];
ll comb[120][120]; //組合數

void init(){
    for(int i = 0; i < 120; i ++){
        comb[i][0] = comb[i][i] = 1;
        for(int j = 1; j < i; j ++){
            comb[i][j] = comb[i-1][j] + comb[i-1][j-1];
        }
    }
}

int main()
{
    init();
    scanf("%d",&n);
    int Max=0;
    for(int i=0;i<n;i++){
        scanf("%d",&a[i]);
        Max+=a[i];
    }
    dp[0][0][0]=1;
    dp[1][a[0]][0]=1;
    dp[0][0][1]=1;
    dp[1][a[0]][1]=1;
    for(int i=1;i<n;i++){
        for(int j=0;j<=i;j++){
            for(int k=0;k<=Max;k++){
                if(dp[j][k][0]){
                    dp[j+1][k+a[i]][1]+=dp[j][k][0];
                }
            }
        }
        //將當前狀態進行轉移
        for(int j=0;j<=i+1;j++){
            for(int k=0;k<=Max;k++){
                dp[j][k][0]=dp[j][k][1];
            }
        }
    }
    sort(a,a+n);
    a[n]=-1;
    int cnt=1; //表示重量相同的砝碼的個數
    int ans=0;
    int num=0;
    int fg=0;
    for(int i=1;i<=n;i++){
        if(a[i]!=a[i-1]){
            num++;
            for(int j=cnt;j>0;j--){
                if(ans>=j)  break;
                //comb[cnt][j]為C(cnt,j),表示這相同的幾個數能組成和為j*a[i-1]的情況數
                if(dp[j][j*a[i-1]][0]==comb[cnt][j]){
                    ans=j;
                    if(j==cnt) fg=1;
                    else fg=0;
                    break;
                }
            }
            cnt=1;
        }
        else{
            cnt++;
        }
    }
    //若只有2種重量的砝碼且一種能全部確定
    if(num==2&&fg==1){
        ans=n;
    }
    printf("%d\n",ans);
    return 0;
}