Codeforces Round 599 (Div. 1+2)___E. Sum Balance —— 子集状压DP

题目链接:点我啊╭(╯^╰)╮

题目大意:

     k k k 组数,每组数有 n i n_i ni 个,所有数互不相同
    从每组数拿出一个放到自己或其他的组
    要求最后每组数的个数与原来的相同
    并且每组数的和相同

解题思路:

    设 t o t tot tot 为每组数最后的和,则枚举每一组数的每一个值 a [ i ] [ j ] a[i][j] a[i][j]
    若将这个数字拿出,则这组数字需要补上 t o t − s u m [ i ] + a [ i ] [ j ] tot-sum[i]+a[i][j] totsum[i]+a[i][j]
    也就是从其他组找到上述这个数,补到第 i i i
    若将 a [ i ] [ j ] a[i][j] a[i][j] t o t − s u m [ i ] + a [ i ] [ j ] tot-sum[i]+a[i][j] totsum[i]+a[i][j] 连边,代表需要补上的值
    由于每个数都只出现一次,所以每个数的出度都为1
    所以枚举 a [ i ] [ j ] a[i][j] a[i][j],根据其出度 t o t − s u m [ i ] + a [ i ] [ j ] tot-sum[i]+a[i][j] totsum[i]+a[i][j],可以找到其对应的组
    若 a [ i ] [ j ] a[i][j] a[i][j] 是个可行值,则由 a [ i ] [ j ] a[i][j] a[i][j] 出发连的边,最后形成一个环,回到 a [ i ] [ j ] a[i][j] a[i][j]
    也就是最后一个数拿出后要补上 a [ i ] [ j ] a[i][j] a[i][j]
    这个过程由于环上的点的出度都是一定的,所以环上的点可以只访问一次
    时间复杂度 O : ( ∑ n ) O:( \sum n) O(n)


    在上述枚举 a [ i ] [ j ] a[i][j] a[i][j] 的过程时,设 s s s 为处理完的组的状态
     f [ s ] f[s] f[s] 记录 s s s 这个状态,环上所有点的组编号与出度点
    那么最后要求出 f [ ( 1 < < k ) − 1 ] f[(1<<k)-1] f[(1<<k)1] 的答案
    这个过程可以用子集 D P DP DP,对于一个状态 s s s
    枚举 s s s 的所有子集 i i i,若 i i i 可行,且 s ⨁ i s \bigoplus i si 可行
    则 s s s 的状态可以由 i i i s ⨁ i s \bigoplus i si 表示

    枚举一个状态 s : C k i s:C_k^i sCki,其子集的个数为 C i 0 + C i 1 + C i 2 + . . . + C i i = 2 i C_i^0 +C_i^1 + C_i^2+...+C_i^i=2^i Ci0+Ci1+Ci2+...+Cii=2i
    时间复杂度: O ( C k 0 × 2 0 + C k 1 × 2 1 + C k 2 × 2 2 + . . . + C k k × 2 k ) O(C_k^0 \times 2^0 + C_k^1 \times 2^1 + C_k^2 \times 2^2 + ... + C_k^k \times 2^k) O(Ck0×20+Ck1×21+Ck2×22+...+Ckk×2k)
    上式 = O ( C k 0 × 2 0 × 1 k + C k 1 × 2 1 × 1 k − 1 + C k 2 × 2 2 × 1 k − 2 + . . . + C k k × 2 k × 1 0 ) = ( 1 + 2 ) k = O(C_k^0 \times 2^0 \times1^{k} + C_k^1 \times 2^1 \times1^{k-1} + C_k^2 \times 2^2 \times1^{k-2} + ... + C_k^k \times 2^k \times1^{0})= (1+2)^k =O(Ck0×20×1k+Ck1×21×1k1+Ck2×22×1k2+...+Ckk×2k×10)=1+2k


     总时间复杂度: O ( ∑ n + 3 k ) O( \sum n + 3^k) O(n+3k)

核心:子集DP

PS:这里由于 k k k 值不大,不标记环上的点复杂度为 O ( k ∑ n + 3 k ) O(k \sum n + 3^k) O(kn+3k),不大影响

#include<bits/stdc++.h>
#define rint register int
#define deb(x) cerr<<#x<<" = "<<(x)<<'\n';
using namespace std;
typedef long long ll;
using pii = pair <ll,ll>;
const int maxn = 5e3 + 5;
int k, a[16][maxn], cnt[16];
ll sum[16], tot;
unordered_map <ll, ll> mp1, mp2;
vector <pii> f[1<<16], tmp, ans[16];
 
int main() {
	scanf("%d", &k);
	for(int i=1; i<=k; i++){
		scanf("%d", cnt+i);
		for(int j=1; j<=cnt[i]; j++){
			scanf("%d", &a[i][j]);
			sum[i] += a[i][j];
			mp1[a[i][j]] = i;
		}
		tot += sum[i];
	}
	if(tot % k) {
		puts("No");
		return 0;
	}
	tot /= k;
	for(int i=1; i<=k; i++)
		for(int j=1; j<=cnt[i]; j++)
			if(mp1[ tot-sum[i]+a[i][j] ]) mp2[a[i][j]] = tot-sum[i]+a[i][j];
			else mp2[a[i][j]] = 9e9;
			
	for(int i=1; i<=k; i++)
		for(int j=1; j<=cnt[i]; j++){
			ll s = 1 << (i - 1), x = mp2[a[i][j]];
			tmp.clear(); tmp.push_back({i, x});
			while(x!=9e9 && !(s&(1<<mp1[x]-1)))
				s |= (1<<mp1[x]-1), tmp.push_back({mp1[x], mp2[x]}), x = mp2[x];
			if(x == a[i][j] && !f[s].size()) f[s] = tmp;
		}
		
	for(int msk=0; msk<1<<k; msk++){
		if(f[msk].size()) continue;
		for(int i=msk; i; i=(i-1)&msk)
			if(f[i].size() && f[i^msk].size()){
				for(auto j : f[i]) f[msk].push_back(j);
				for(auto j : f[i^msk]) f[msk].push_back(j);
				break;
			}
	}
	if(!f[(1<<k)-1].size()) {
		puts("No");
		return 0;
	}
	puts("Yes");
	for(auto i : f[(1<<k)-1]) ans[mp1[ i.second ]].push_back(i);
	for(int i=1; i<=k; i++) printf("%lld %lld\n", ans[i][0].second, ans[i][0].first);
}
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值