题目
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 0Sample 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
。