2019牛客暑期多校训练营(第二场)F

题目描述

来源

给定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

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值