![](https://i-blog.csdnimg.cn/direct/9460816208244fd3b1e37fe4dad60687.png)
![](https://i-blog.csdnimg.cn/direct/3cd8cabc81e3464994c9546fa5c7c011.png)
//由于这道题的数据范围是非常小的,只到18,所以考虑状压dp
//第一维一般是状态,而第二位一般是划分的重要依据
//由于只要知道最后一次通关的游戏,我们就可以计算下一步选任意一个游戏的收益,而之前选了什么游戏是完全不重要的,
//所以第二维只需要记录下最后一次选了什么游戏就可以了
//状态表示:f[i][j]表示的是状态为i且最后一次选了第j号游戏的全部方案,属性为获得分数的最大值
//状态计算:集合划分,按照倒数第二次选了什么游戏进行划分
// f[i][j] = max(f[i][j] , f[i/j][k]) k是任意一个和j不同的游戏
#include<iostream>
#include<algorithm>
using namespace std;
const int N = 20 , M = 1<<N;
typedef long long LL; //由于最后的结果可能很大,所以开个long long
LL f[M][N]; //存的是全部状态的属性值
int w[N]; //存每个关卡的通关分数
int g[N][N]; //存全部的联动关系和分数
int main()
{
int n,m,K;
cin >> n >> m >> K;
for(int i=0 ; i<n; i ++) //读入全部的通关分数
{
cin >> w[i];
}
for(int i=0 ; i<K ; i ++) //读入全部的联动关系
{
int x,y,c;
cin >> x >> y >> c;
g[x-1][y-1] = c;
}
//初始化,意思是初始化所有只打一关的方案
for(int i=0 ; i<n; i ++) f[1<<i][i] = w[i];
//开始遍历全部的状态
for(int i=0 ; i< 1 << n ; i ++)
{
for(int j=0 ; j<n; j ++) //遍历全部的关卡
{
if(i >> j & 1) //表示的是这个状态是合法的,即i这个状态是包含了j这一关卡的 (1打着一关 ,0 不打)
{
for(int k=0 ; k<n; k ++) //进行状态计算的过程
{
if(k != j && (i >> k & 1)) //表示的i/j的状态中是包含k这个关卡的合法状态
f[i][j] = max(f[i][j] , f[i- (1<<j)][k] + g[k][j] + w[j]);
}
}
}
}
//然后需要遍历全部的状态找到所有合法的满足恰好打过m关的所有状态,然后找到最后的最大值,直接输出
LL res = 0;
for(int i=0 ; i< 1<<n ; i ++)
{
int cnt = 0;
for(int k=0 ; k<n; k ++)
{
if(i>>k & 1) cnt++;
}
if(cnt == m)
{
for(int j=0 ; j<n; j ++)
{
res = max(res , f[i][j]);
}
}
}
cout << res << endl;
return 0;
}