题意:有n道菜,每道菜一个权值,有k个条件,表示第y道菜在第x道后马上吃有c的附加值。求从中吃m道菜的最大权值。
本题详解可看:
https://www.cnblogs.com/real-l/p/8597827.html
作为还在入门状压DP的萌新,这里就分析一下怎么推出DP状态。
首先,n的范围比较小,且每道菜有个吃与不吃操作,可用二进制1,0表示,故不难相处可用状压DP;其次,n道菜就有1<<n种不同的选择,因此DP状态中一定要有一维是[1<<n]。最后,因为吃菜的顺序不同可能还有加成,因此我们还要记录一下上次吃的最后一道菜是什么。于是DP状态差不多就出来了:
状态:dp[i,j]表示在状态i的情况下,最后一道菜是吃j时所能达到的最大权值,1<=i<(1<<n),1<=j<=n。
转移方程:
for( i <- 1 ~ 1<<n) //枚举状态
.----for( j <- 1 ~ n) //枚举在i状态下,最后吃的菜
.--------if( i & ( 1<< (j-1) ) )
.-----------for( k <- 1 ~ n) //枚举在i状态下,还没吃过的菜
.----------------if ( ! ( i & ( 1<< (k-1) ) )
.---------------------dp[i|(1<<(k-1),k]=max(dp[i|(1<<(k-1),k], dp[i,j]+a[k]+c[x,y]) //更新
不过因为题意有个限制,就是最终是要吃m道菜,显然,我们不可能再在DP状态中再加一维了,那样就太大了,如何解决这个问题会在代码中体现。
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long ll;
const int maxn=1<<18;
ll dp[maxn][20];
ll a[20];
ll map[20][20];
int main()
{
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>a[i];
memset(map,0,sizeof(map));
for(int i=1;i<=k;i++)
{
int x,y,c;
cin>>x>>y>>c;
map[x][y]=c;
}
memset(dp,0,sizeof(dp));
ll ans=0;
for(int i=1;i<=n;i++) dp[1<<(i-1)][i]=a[i];
for(int i=1;i<(1<<n);i++)
{
int cnt=0; //我们知道i在二进制下是个01串,有1就说明某道菜有吃,cnt就是来记录i状态吃了多少菜的
for(int j=1;j<=n;j++)
{
if(i&(1<<(j-1))) //如果j这道菜有吃
{
cnt++; //显然cnt++
for(int k=1;k<=n;k++)
{
if(!(i&(1<<(k-1))))
dp[i|(1<<(k-1))][k]=max(dp[i|(1<<(k-1))][k],dp[i][j]+a[k]+map[j][k]);
}
}
}
if(cnt==m) //判断此时i是否吃满m道菜了,是的话更新ans
{
for(int j=1;j<=n;j++) //再进行一次枚举
ans=max(ans,dp[i][j]);
}
}
cout<<ans<<endl;
return 0;
}