CF#599 Div.1 C. Sum Balance//子集状压+建图+遍历环

题目

题意

k k k个box,每个box放了 x i x_i xi个整数(所有数distinct)。可以操作一次:从每个box拿出一个数,然后打乱再放回每个box一个数,问是否可以使得每个box数的和相同,并输出路径。(k<15)

思路

考虑建图:
对于每一个物品 a i j a_{ij} aij,考虑将他拿出来,必然有另一个物品 a k , z a_{k,z} ak,z放回这个物品所在的box。
如果 a k , z a_{k,z} ak,z合理,那么就从 a i j a_{ij} aij a k , z a_{k,z} ak,z引出一条有向边。
然后问题就转换为是否可以找出一个或多个环,使得每个数出现一次。
考虑求出所有环:
可以发现,每个点最多一个出边,那么暴力遍历每一个点,每个点最多dfs 15次。就可以求出所有的环。
考虑状压dp:
d p [ i ] : i dp[i]:i dp[i]i状态是否存在, d p [ i ] dp[i] dp[i]可以通过 d p [ j ] dp[j] dp[j]增加一个环转移过来。那么就需要枚举子集。
考虑输出路径:
dp倒着转移回去,并把每个环暴力dfs。
这里有个很坑的爆int:map查询的时候,传进的参数爆int。
代码映射写的比较丑。。。。。

/*
   Author: Rshs
   Time:   2019-11-09-14.35
*/
#include<bits/stdc++.h>
using namespace std;
#define  FI    first
#define  SE    second
#define  LL    long long
#define  MP    make_pair
#define  PII   pair<int,int>
#define  SZ(a) (int)a.size()
const double pai = acos(-1);
const double eps = 1e-8;
const LL     mod = 1e9+7;
const int    MXN = 1e6+5;
map<LL,PII>binid;
map<int,int>id3;
vector<int>a[20],g[MXN];
int st[MXN],id1[MXN],id2[MXN];
int dp[(1<<16)];
int ok[(1<<16)];
int ff[(1<<16)];
int in[20];
int main(){
    //cout<<-6%2;
    int k;cin>>k;
    LL sum=0;
    for(int i=1;i<=k;i++){
        int cc;scanf("%d",&cc);
        st[i+1]=st[i]+cc;
        for(int j=1;j<=cc;j++) {
            int sa;scanf("%d",&sa);a[i].push_back(sa);
            id1[st[i]+j]=i;id2[st[i]+j]=sa;id3[sa]=i;
            binid[sa]=MP(i,j);sum+=(LL)sa;
        }
    }

    if(sum%k!=0) return puts("No"),0;
    LL ave=sum/k;
    for(int i=1;i<=k;i++){
        LL ss=0;
        for(auto j:a[i]){
            ss+=(LL)j;
        }
        LL xx=ave-ss;
        for(int j=0;j<SZ(a[i]);j++){
            LL need=xx+(LL)a[i][j];
            //if(need<0) continue;
            if(need==a[i][j]) {g[st[i]+j+1].push_back(st[i]+j+1);continue;}
            if(binid.find(need)==binid.end()) continue;
            PII zz=binid[need];
            if(zz.FI!=i)
                g[st[i]+j+1].push_back(st[zz.FI]+zz.SE);
        }
    }
    for(int i=1;i<=st[k]+SZ(a[k]);i++){
        int S=0,now=i;set<int>v;
        while(1){
            if(SZ(g[now])==0) break;
            if(v.find(now)!=v.end()){
                if(now==i) ok[S]=1,ff[S]=i;
                break;
            }
            S=S+(1<<(id1[now]));
            v.insert(now);
            now=g[now][0];
        }
    }
    dp[0]=1;
    for(int i=1;i<(1<<(k+1))-1;i++){
        for(int S=i;S!=0;S=(S-1)&i){
            if(dp[i-S]&&ok[S]) {dp[i]=1;break;}
        }
    }
    int now=(1<<(k+1))-2;
    if(dp[now]==0) return puts("No"),0;
    puts("Yes");
    while(1){
        if(now==0) break;
        for(int S=now;S!=0;S=(S-1)&now){
            if(ok[S]&&dp[now-S]){
                now=now-S;
                int cur=g[ff[S]][0];
                in[id1[ff[S]]]=id2[cur];
                while(cur!=ff[S]){
                    in[id1[cur]]=id2[g[cur][0]];
                    cur=g[cur][0];
                }
                break;
            }
        }
    }
    vector< pair<int,pair<int,int > > >ans;
    for(int i=1;i<=k;i++){
        ans.push_back(MP(id3[in[i]],MP(in[i],i)));
    }
    sort(ans.begin(),ans.end());
    for(auto i:ans) cout<<i.SE.FI<<' '<<i.SE.SE<<'\n';
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值