题目描述
给定2N个人,您需要将他们中的每一个分配到红队或白队中,这样每个团队都完全由N个人组成,并且总竞争价值是最大化的。总竞争价值是对不同团队中每对人的竞争价值的总和
输入
下面的2n行是用2n个空格分隔的整数vij, vij是i-th行的j-th值,它指示了Person i和Person j的竞争价值。和自己的竞争价值为0。
1<=N<=14
0<=vij<=1e9
vij=vji
输出
输出一行,其中包含一个整数,表示最大可能的竞争总值。
输入样例
1
0 3
3 0
输出样例
3
solution1
- 比如说有四个人,1 2 3 4 ,分配1 2 一个队伍,3 4 一个队伍,则竞争力的计算为13+14+23+24
- 设分为AB两队,因为总人数和每队人数是确定的,因此只需要知道A的序列之后就相当于唯一确定了B的序列
- 因为我们只考虑枚举A集合的状态,且最开始A集合的状态为1,那么,之后的过程必定是要选取B集合中的某一个数加到A集合中,
- 假设我们现在要将原本在B集合中的ai加到A集合中。
- 那么显然,我们只需要将ai原来跟A集合中的所有点点贡献删除,并增加现在在B集合中的点跟ai的贡献。故此时,我们直接可以用 O(n) 的时间复杂度进行更新答案。
AC代码
#include <bits/stdc++.h>
#define maxn 40
using namespace std;
typedef unsigned long long ll;
ll res=0;
int n,v[maxn][maxn];
//sta是状压了一个2*n位的二进制位,代表当前A集合选取的状态,若当前位为1则代表选1
//cnt代表A选取了的状态,A中元素个数
//pre代表当前已经选了前pre个点
//cost代表贡献
void dfs(int sta,int cnt,int pre,ll cost){
if(cnt==n/2){ //两边集合(元素个数)相同,更新答案
res=max(res,cost);
return;
}
//cnt-pre-1< -n/2
//pre+1-cnt > n/2//所选元素中,且A中的元素个数超过n/2,+1是因为pre是从0开始的
if(n-pre-1+cnt < n/2) return; //剪枝,如果发现A集合的数量大于B集合的数量,直接终止
for(int i=pre+1;i<n;i++){ //枚举当前需要将第i个点加到A集合中
ll cur=cost;
for(int j=0;j<n;j++){
if(sta&(1<<j)) cur-=v[i][j]; //如果在原来的状态下第j位是处在A集合中,现在加入了i,它们到了统一个集合中,减去它们原来的竞争值
else cur+=v[i][j]; //如果在原来的状态下第j位是处在B集合中,现在i在A集合,加上二者的竞争值
}
dfs(sta|(1<<i),cnt+1,i,cur);
}
}
int main()
{
scanf("%d",&n);
n<<=1;//将2N的规模压缩为N
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
scanf("%d",&v[i][j]);
for(int i=0;i<n;i++){//选了第一个元素到A中
res+=v[0][i];//第一个点和其他所有点的竞争值之和
}
dfs(1,1,0,res);//第一个数选了,A中有一个元素,
printf("%llu\n",res);
return 0;
}
总结
这题是一个dfs的灵活运用,以及时间优化上只计算状态转移差值,而不重新计算整个状态来减少对每个状态的计算量
solution2
参考
遍历所有情况C(14,28) = = 18!/(14!*14!) = 40,116,600约4e7
判断每个情况O(n2), O(C(n,2n)n 2)约8e9超时
可以考虑一个O(C(n,2n)n)的算法
考虑如何惰性计算这个竞争力总和,也就是说从状态AA转移到状态BB的时候根据状态的变化来计算贡献,这是一个常用的思路
代码(含注释)
/*
C(14,28) = 18!/(14!*14!) = 40,116,600
约4e7
*/
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
long long n, again, ans[55], m[30][30], sum[30], a[15], b[15], ab[30];
vector<int> seca, secb;
//看当前分组还能不能交换出竞争值更大的分组,时间复杂度O(n^3)
bool can(const int ti) {
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
int u = a[i], v = b[j];
/*
m[u][v]:u和v的竞争值
sum[u]:u和所有人的竞争值
sum[v]:v和所有人的竞争值
uv交换前:ab[u]+ab[v]
ab[u]:u和b队所有成员的竞争值(包括v)
ab[v]:v和a对所有成员的竞争值(包括u)
ab[u]-m[u][v]:u和b队所有成员的竞争值(不包括v)
ab[v]-m[u][v]:v和a对所有成员的竞争值(不包括u)
uv交换后:
sum[u]-ab[u]:u和所有a队成员的竞争值(不包括v)
sum[v]-ab[v]:v和所有b队成员的竞争值(不包括u)
sum[u]-ab[u]+m[u][v]:u和所有a队成员的竞争值(包括v)
sum[v]-ab[v]+m[u][v]:v和所有b队成员的竞争值(包括u)
an:交换后与交换前的竞争值差
*/
long long an = 2 * m[u][v] + sum[u] + sum[v] - 2 * ab[u] - 2 * ab[v];
if(an > 0) {//竞争值增加了,交换u,v
a[i] = v, b[j] = u;
ans[ti] += an;
ab[u] = sum[u] - ab[u] + m[u][v], ab[v] = sum[v] - ab[v] + m[v][u];
//更新组a除v外其他成员和b队的竞争值
for(int k = 0; k < n; k++) if(a[k] != v) ab[a[k]] += (m[a[k]][u] - m[a[k]][v]);
//更新组b除u外其他成员和a队的竞争值
for(int k = 0; k < n; k++) if(b[k] != u) ab[b[k]] += (m[b[k]][v] - m[b[k]][u]);
return true;//交换成功
}
}
}
return false;//怎么两两交换都没有更大的竞争值了
//但是还存在多个交换有更大的竞争值得情况,所以后面还是要dfs
}
void dfs(const int player) {//player小于等于27
if(again > 25) return;
//不满足条件的直接return,return的分支时间可以忽略不计
//减枝
if(seca.size() > n || secb.size() > n) return;
//满足条件的分法为什么小于25个
if(seca.size() == n && secb.size() == n) {
again++;
for(int i = 0; i < n; i++) a[i] = seca[i], b[i] = secb[i];
for(int i = 0; i < n; i++) {
ab[a[i]] = 0, ab[b[i]] = 0;
//ab[i]表示i和另外一个组的所有成员的竞争值总和
for(int j = 0; j < n; j++) ab[a[i]] += m[a[i]][b[j]], ab[b[i]] += m[a[j]][b[i]];
}
for(int i = 0; i < n; i++) ans[again] += ab[a[i]];//当前分组后两只队伍的总竞争值
while(can(again)) {}
return;
}
//对于每个数可以分到a也可以分到b,共2^28总分法,约2.7e8, 满足n,n分的情况有4e7种
seca.push_back(player);//将0分到组a
dfs(player+1);//接着分0+1=1
seca.pop_back();
secb.push_back(player);//将0分到组b
dfs(player+1);
secb.pop_back();
}
int main() {
cin >> n;
for(int i = 0; i < 2*n; i++) for(int j = 0; j < 2*n; j++) cin >> m[i][j];
//sum[i]表示i和所有其他人的竞争值总和(和它自己的是0)
for(int i = 0; i < 2*n; i++) for(int j = 0; j < 2*n; j++) sum[i] += m[i][j];
//先按顺序从中间划分,a,b存两个组的成员
for(int i = 0; i < n; i++) a[i] = i, b[i] = i + n;
//计算竞争值总和
//ab[i]表示i和另外一个组的所有成员的竞争值总和
//前一个ab[i]属于组a,i[0,n-1],组a的成员i和组b的所有成员[n,2n-1]的竞争值总和
//后一个ab[i]属于组b,i[n,2n-1],组b的成员i和组a的所有成员[0,n-1]的竞争值总和
for(int i = 0; i < n; i++) for(int j = 0; j < n; j++) ab[i] += m[i][j+n], ab[i+n] += m[i+n][j];
for(int i = 0; i < n; i++) ans[0] += ab[i];//当前分组后两只队伍的总竞争值
while(can(0)) {}//
again = 0;
dfs(0);
long long maxans = -1;
for(int i = 0; i <= again; i++) maxans = max(maxans, ans[i]);
cout << maxans << endl;
return 0;
}