Codeforces Round #599 (Div. 1) C. Sum Balance

题目链接

题目大意:

k k k个箱子,箱子 i i i n i n_i ni个数字,能否满足:
在每个箱子取一个数字,一共取出k个数字。
再把这k个数字重新分配给每个箱子,使得每个箱子的数字总和相等。
如果不能满足,输出no,如果可以满足,输出yes并打印方案(每个箱子取出哪个数字,放到哪个箱子)。
( k ≤ 15 , n ≤ 5000 , ∣ a i ∣ ≤ 1 e 9 ) (k\le 15,n \le 5000, |a_i| \le 1e9) (k15,n5000,ai1e9)保证所有数字两两不同

解题思路:

容易知道,如果可以满足,那么最终每个箱子的总和为 s u m / k sum/k sum/k,其中 s u m = ∑ a i sum = \sum a_i sum=ai.那么对于每个箱子的数字,可以直接算出如果从这个箱子取出这个数字,那么应该再放入的数字的值。那么可以建一个有向图:数字 a i a_i ai如果取出,算出来需要放入 a j a_j aj。那么加有向边 a i − > a j a_i->a_j ai>aj。这样如果找到一个环的集合,使得覆盖每个容器恰好一次,那么就可以构建出答案。
因为每个点只有一个出度,所以可以直接枚举起点找环。找到一个覆盖部分容器刚好一次的环,就记录下来。这样我们得到了所有覆盖某些容器恰好一次的环。
然后我们利用子集DP获取恰好覆盖所有容器的环的集合。复杂度 O ( 3 k ) O(3^k) O(3k)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 3e5 + 50;
const int inf = 0x3f3f3f3f;
map<int, int> mp;
struct node{
    vector<int> ned;
}q[maxn];
int tot = 0, n, a[maxn], cnt = 0;
ll sum;
ll D[16];
ll to(int x){
    int id = mp[x];
    return sum - (D[id]-x);
}
int dp[1<<16];
vector<int> path;
void dfs(ll x, int mask){
    if(x > inf) return;
    if(mp.find(x) == mp.end()) return;
    int id = mp[x];
    if((mask>>id)&1){//出现过
        int st = -1;
        for(int i = 0; i < path.size(); ++i){
            if(path[i] == x) st = i;
        }
        if(st == -1) return;//无环
        for(int i = 0; i < st; ++i) mask ^= (1<<mp[path[i]]);
        if(dp[mask]) return;//此环出现过
        ++tot;
        dp[mask] = -tot;
        for(int i = st; i < path.size(); ++i){
            q[tot].ned.push_back(path[i]);
        }
        return;
    }
    path.push_back(x);
    dfs(to(x), mask|(1<<id));
}
vector<int> ans;
void go(int mask){
    if(dp[mask] < 0){
        int id = -dp[mask];
        for(int i = 0; i < q[id].ned.size(); ++i){
            ans.push_back(q[id].ned[i]);
        }return;
    }
    go(dp[mask]); go(mask^dp[mask]);
}
int main()
{
	cin>>n;
	sum = 0;
	for(int i = 0; i < n; ++i){
        int k; scanf("%d", &k);
        while(k--){
            scanf("%d", &a[cnt]);
            mp[a[cnt]] = i;
            sum += a[cnt];
            D[i] += a[cnt];
            cnt++;
        }
	}
	if(sum%n != 0){
        printf("No\n"); return 0;
	}
	sum /= n;
	for(int i = 0; i < cnt; ++i){
        path.clear();
        dfs(a[i], 0);
	}
	for(int mask = 1; mask < (1<<n); ++mask){
        if(dp[mask]) continue;
        for(int sub = (mask-1)&mask; sub; sub = (sub-1)&mask){
            if(dp[sub] && dp[mask^sub]){

                dp[mask] = sub;
                break;
            }
        }
	}
	if(!dp[(1<<n)-1]) cout<<"No"<<endl;
	else{
        cout<<"Yes"<<endl;
        go((1<<n)-1);
        int pre[15], val[15];
        for(int i = 0; i < ans.size(); ++i){
            int x = ans[i];
            int id = mp[x];
            int v = to(x);
            val[id] = x;
            pre[mp[v]] = id;
        }
        for(int i = 0; i < n; ++i){
            cout<<val[i]<<" "<<pre[i]+1<<endl;
        }
	}
	//for(int i = 0; i < (1<<n); ++i) cout<<"i:"<<i<<" dp:"<<dp[i]<<endl;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值