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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/luyehao1/article/details/84339972

题目链接:

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

猜你喜欢

转载自blog.csdn.net/luyehao1/article/details/84339972