洛谷P3947 肝活动【状压DP】

题目描述

Yume 最近在玩一个名为《LoveLive! School idol festival》的音乐游戏。他之所以喜欢上这个游戏,是因为这个游戏对非洲人十分友好,即便你脸黑到抽不出好卡,还可以通过在每个月举办的两次活动中达成一定的目标来获得奖励。

Yume 很喜欢这一期活动奖励卡的卡面,于是他决定要肝这一期的活动,拿到活动奖励。这一期的活动规则很特殊,玩家需要在活动规定的结束时间前,完成所有指定的歌曲(每首歌曲只能打一次),并获得一定的分数,就可以拿到活动奖励。如果在规定的时间前没有完成所有的歌曲,或者分数不够奖励的分数线,则不能领取活动奖励。每首歌有一个限定的奖励开放时间,玩家如果在这段时间内完成了这首歌,便可以获得一定的分数(获得的分数 = 开放时间 - 当前已用的总时间)。如果超出了这段时间之后再完成这首歌,就不能获得分数了。

这样的规则对 Yume 这样的老玩家来说本应是轻而易举,但不巧的是 Yume 把活动的结束时间记成了活动的开始时间,以至于当他上线跃跃欲试的时候,惊恐地发现活动已经快要结束了。现在他想知道,在剩余的时间之内,他能否完成所有的歌、达成奖励的分数线拿到活动卡。为了节省时间,他把这个问题交给了你来解决。请你根据给定的数据,帮他计算出能否在剩余的时间内达成目标。如果能,请告诉他完成每首歌曲的顺序。

输入格式:

输入的第一行是三个整数 n, m, t,分别表示规定完成
的歌曲数目、获得奖励需要达到的最低分数和距离活动结束剩余的时间。

接下来有 n 行,第 i 行有一个字符串 Si 和两个整数 Ti 和 Mi,表示第 i 首歌的歌名为 Si,完成第 i 首歌所需要的时间为 Ti,第 i 首歌的奖励开放时间剩余 Mi。保证 Ti ≤ Mi. 其中数据已按 Si 的字典序给出。

输出格式:

如果在活动结束前 Yume 可以完成指定的目标拿到奖励,则在第一行输出一个整数 C,表示在获得奖励的前提下,所能够获得的分数的最大值;接下来的 n 行中,按照完成歌曲的顺序输出第 i 首歌的歌名。如果有多种可能性,则输出字典序最小的情况。

如果在活动结束前 Yume 不能完成所有的歌曲,输出 No Answer .
No Answer

说明

对于 0% 的数据,与测试数据完全相同。
对于 20% 的数据,满足 n ≤ 5。
对于 40% 的数据,满足 n ≤ 9。
对于 70% 的数据,满足 n ≤ 15。
对于 100% 的数据,满足 n ≤ 22,Si 的长度不超过 50. 保证 m, t 和 Ti, Mi 以及其相加的结果都在 int 的最大范围内。
另有 10% 的数据满足 Sigma(T1, T2, …, Tn) < t.


题目分析

看数据范围一眼就是状压吧
每个状态中1表示已完成的歌曲,0表示未完成
那么对于每个状态i
d p [ i ] dp[i] dp[i]到达状态i能得到的最大分数

对于每个状态依次枚举n个歌曲
若当前歌曲还未完成(该位为0)
则计算出在该状态下完成这个歌曲后的状态j
以及当前状态下完成这个歌曲能得的分数 s c = M i − T i − c u r [ i ] sc=M_i-T_i-cur[i] sc=MiTicur[i]
其中 c u r [ i ] cur[i] cur[i]表示到达当前状态共花了多少时间

然后更新 d p [ j ] = m a x ( d p [ j ] , d p [ i ] + s c ) dp[j]=max(dp[j],dp[i]+sc) dp[j]=max(dp[j],dp[i]+sc)
同时再开一个数组记录路径就好了


#include
#include
#include
#include
#include
#include
using namespace std;

int read()
{
    int f=1,x=0;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return f*x;
}

const int N=55;
const int mx=5000010;
int n,m,t;
int tim[N],lft[N];
char name[N][N];
int curt[mx],dp[mx];
int pre[mx],song[mx];
int sum;

void print(int x)
{
    if(!x) return;
    print(pre[x]);
    printf("%s\n",name[song[x]]);
}

int main()
{
    n=read();m=read();t=read();
    for(int i=1;i<=n;i++)
    {
        scanf("%s",&name[i]);
        tim[i]=read(); lft[i]=read(); sum+=tim[i];
    }
    if(sum>t){ printf("No Answer"); return 0;}//不能完成全部歌曲
    
    int maxn=(1<<n)-1;//最终状态
    memset(dp,-1,sizeof(dp));
    dp[0]=0;
    
    for(int i=0;i<=maxn;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if( (1<<j-1)&i ) continue;//二进制下从第0位开始,所以要j-1
            int nxt=i|(1<<j-1);//完成后的状态
            int sc=lft[j]-tim[j]-curt[i];//所得分数
            if(sc<0) sc=0;//如果超过时间则不得分
            if(dp[nxt]<dp[i]+sc)
            {
                dp[nxt]=dp[i]+sc;
                curt[nxt]=curt[i]+tim[j];//更新dp值及当前所用时间
                pre[nxt]=i; song[nxt]=j;//记录路径
            }
        }
    }
    if(dp[maxn]<m){printf("No Answer"); return 0;}
    printf("%d\n",dp[maxn]);
    print(maxn);
    return 0;
}

你可能感兴趣的:(动态规划--状压DP,位运算)