GalaxyOJ-790 (状态压缩dp)

题目

Problem Description

玩dota的时候,使用祈求者这个英雄非常有趣。设该英雄有N个技能,每个技能只能放一次。每个技能有基础伤害Ai(0<=i<=N-1)。每个技能有Mi(0<=i<=N-1)条连招。每条连招包含两个整数Bj,Cj(1<=j<=Mi)以及Cj个技能id。连招的意义是,当放i技能之前,Cj个技能都已经放过了,则i技能的伤害会变成Bj。因此,技能的施放顺序会影响技能的伤害值。

我们的目的是找到最优的技能施放顺序使得造成的伤害和最大,请输出最大的伤害。

Input

第一行为一个整数N,表示共有N个技能,技能编号为0到N-1。接下来组数据描述每个技能的信息。

每组数据的第一行为两个整数Ai,Mi。表示第i个技能的基础伤害Ai,和第i个技能有Mi条连招。接下来Mi行给出连招的信息。每行先给出两个整数Bj,Cj,再给出Cj个整数IDk(1<=k<=Cj)。表示当i个技能在之前都放过,i的技能伤害为Bj。

对于所有数据,1<=Ai<Bj<=100000,1<=Cj<=N-1,0<=IDk<=N-1并且IDk不等于i。

Output

一个整数,表示最大伤害和。

Sample Input

2
50 1
60 1 1
70 1
90 1 0

Sample Output

140

Hint

若一个技能同时满足多个连招,则会取其中最大的一个伤害。

分析

  • 题目大意就是给了几个元素以及它的价值,每个元素有几个 其他元素的集合以及此集合对应的新价值,若排列中这个集合中的元素都在次元素之前,那么这个元素的价值就变成了这个新价值。
  • 看到 N 就是15,那么就可以状态压缩,把每个状态的最大价值存到 dp 数组里面。
  • 采取这样的 dp 方法:在已求出答案的状态的基础上再加一个元素,看看能不能用“连招”,若能使用就更新一下加了之后状态的答案。
  • 枚举一个状态 i,再取一个此状态中没选的元素 j ,枚举 j 的全部“连招集合”k,判断一下他是不是 i 的子集,是的话就说明可以使用连招,用此连招的价值dp[i]+B[i][k]与原本价值 dp[i|j] 比较一下并更新。
  • 最后输出所有元素都选上的最大价值 dp[(1<<n)-1] 即可。

程序

#include <cstdio>
#include <algorithm>
using namespace std;
long long i,j,k,J,C,o,n,dp[200000],A[20],M[20],B[20][100005],D[20][100005];

int main(){
    freopen("1.txt","r",stdin);
    scanf("%d",&n);
    for (i=0; i<n; i++)
        for (scanf("%lld%lld",&A[i],&M[i]),j=1; j<=M[i]; j++)
            for (scanf("%lld%lld",&B[i][j],&C); C; C--)
                D[i][j]+=1<<(scanf("%lld",&k),k);

    for (i=0,o=1<<n; i<o; i++)                          //讨论的招数状态 
        for (j=0,J=1; j<n; J=1<<(++j)){                 //使用连招的那一招 
            if ((i&J)==0){                              //要是 i 中没有这招 
                dp[i|J]=max(dp[i|J],dp[i]+A[j]);
                for (k=1; k<=M[j]; k++)                     //第 k 种连招方式 
                    if ((D[j][k]&i)==D[j][k])               //可以连 
                        dp[i|J]=max(dp[i|J],dp[i]+B[j][k]);
            }
        }
    printf("%lld",dp[(1<<n)-1]);
}


提示

  • 有一个地方要注意,枚举 i 的时候,应该保证讨论到当前状态时,之前的状态都是已经是最终答案的了,然后这里 i 从 1~1<<n 刚好满足,于是就可以直接这样弄了。
  • 昨天调程序的时候,发现一直Wa,弄了好久才发现原来dp数组开太小了,然后 c++ 又不会管数组溢出,开的具体大小应为状态数 2^n-1
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值