前置
赛时卡了我50分钟,而E题只花了我6分钟
题意简述
给定一个有 n n n 个点的无向完全图,并给定每两点之间边的权值,求在满足以下条件时,选择的边的权值之和最大为多少:
∙ \bullet ∙ 所选的边的端点两两不同(即,每个点只能作为某一条被选择的边的端点)。
解题思路
不难发现最多只能取 n / 2 n/2 n/2 条边,否则必有点在选择的边中出现大于一次。又观察到 n ≤ 16 n \leq 16 n≤16 ,于是考虑状压dp。对于每一个状态,可以 n 2 n^2 n2 枚举它是由哪一个状态转移而来的。举个例子:
n
=
6
n=6
n=6 时,对于状态 110011
,可以由以下状态转移而来: 110000 100001 000011 010001 100010 010010
。
也就是说,由当前状态去掉两个 1
的状态转移而来。去掉的这两个 1
就表示当前枚举的这条边连接点
x
,
y
x,y
x,y ,其中
x
,
y
x,y
x,y 分别为两个 1
从右往左数的位置。我的做法是用一个数组记录当前状态中每个 1
的位置,然后
n
2
n^2
n2 枚举。总复杂度
O
(
2
n
×
n
2
)
O(2^n \times n^2)
O(2n×n2) 。
代码示例
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,d[20][20],ans=0,dp[10000010];
signed main(){
cin>>n;
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++) cin>>d[i][j];
}
for(int i=0;i<(1<<n);i++){
int O[20],cnt=0,num=i,p=0,K[20];
while(num>0){
K[++cnt]=num%2;
num/=2;
}
//将当前状态转化为二进制
for(int i=1;i<=cnt;i++) if(K[i]==1) O[++p]=i;
//找到1的位置并记录
if(cnt<2) continue;
//没有1或只有1个1显然无法转移
for(int x=1;x<=p;x++){
for(int y=x+1;y<=p;y++){
//枚举当前边连接点x和点y
int X=i;
X&=(~(1<<(O[x]-1)));
X&=(~(1<<(O[y]-1)));
//得到转移前的状态
dp[i]=max(dp[i],dp[X]+d[O[x]][O[y]]);
//转移
}
}
ans=max(ans,dp[i]);
//取ans
}
cout<<ans<<endl;
return 0;
}