[Codeforces 1242C]Sum Balance

Description

题库链接

给你 \(k\) 个盒子,第 \(i\) 个盒子中有 \(n_i\) 个数,第 \(j\) 个数为 \(x_{i,j}\)。现在让你进行 \(k\) 次操作,第 \(i\) 次操作要求从第 \(i\) 个盒子中取出一个元素(这个元素最开始就在该盒子中),放入任意一个你指定的盒子中,要求经过 \(k\) 次操作后

  • 所有盒子元素个数和最开始相同;
  • 所有盒子元素总和相等

询问是否存在一种操作方式使之满足,若存在,输出任意一种方案即可。

\(1\leq k\leq 15,1\leq n_i\leq 5000,|x_{i,j}|\leq 10^9\)

Solution

由题,容易发现,对于任意一个盒子,会从其中拿出一个数,再从别处(或自己拿出的)添加一个数进来。

我们将数的拿出放入关系抽象成边,即从第 \(i\) 个盒子中拿出的数要放入 \(j\) 中,那么建边 \(i\rightarrow j\)。

因为这张图要求每个节点入度和出度均为 \(1\),显然这张图只能是若干个无相交的环构成的。

现在,我们考虑所有的拿出放入关系:

假设我要从第 \(i\) 个盒子中拿出元素 \(x\),那么要使得这个盒子满足最终条件,应该被放入的元素为 \(S-sum_i+x\),其中 \(S\) 为最终每个盒子的元素总和,\(sum_i\) 表示第 \(i\) 个盒子最初的元素总和。

那么我们建边 \(x\rightarrow S-sum_i+x\)(注意:此时图与之前建的图不同)。我们需要在这张图中找到所有满足下列条件的环:

  • 环上每个元素属于不同盒子;
  • 环上每种盒子只出现一次

用 \(dfs\) 找到这些环之后我们可以将盒子状压。具体地,令 \(f_i\) 表示状态 \(i\) 中所有的盒子构成的满足条件的图是否存在。转移枚举子集 \(dp\)。

若 \(f_{2^k-1}=1\) 即有解。注意另开数据记录转移关系,方便输出方案。

Code

#include <bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
const int N = 5000*15+5, B = (1<<15)+5;

map<ll, int> mp;
int k, n[20], id[N], kp[N], tot;
int bin[20], x[16][5005], f[B], ok[B], p[B], vis[N], s[N], top;
ll sum[20], S;
vector<int> to[N], re[B];
int l[20], r[20];

void dfs(int u, int st) {
    if (vis[u]) {
        int now = 0;
        for (int i = top; i; i--) {
            now |= bin[id[s[i]]-1];
            if (u == s[i]) break;
        }
        if (!ok[now]) {
            ok[now] = 1;
            for (int i = top; i; i--) {
                re[now].pb(s[i]);
                if (u == s[i]) break;
            }
        }
        return;
    }
    if (st&bin[id[u]-1]) return;
    st |= bin[id[u]-1], vis[u] = 1, s[++top] = u;
    for (auto v : to[u]) dfs(v, st);
    vis[u] = 0, --top;
}
int main() {
    bin[0] = 1;
    for (int i = 1; i <= 15; i++) bin[i] = bin[i-1]<<1;
    scanf("%d", &k);
    for (int i = 1; i <= k; i++) {
        scanf("%d", &n[i]);
        for (int j = 1; j <= n[i]; j++)
            scanf("%d", &x[i][j]), mp[x[i][j]] = ++tot,
            kp[tot] = x[i][j], id[tot] = i, sum[i] += x[i][j];
        S += sum[i];
    }
    if (S%k) {puts("No"); return 0; }
    S /= k;
    for (int i = 1; i <= k; i++)
        for (int j = 1; j <= n[i]; j++)
            if (mp.count(S-sum[i]+x[i][j])) to[mp[x[i][j]]].pb(mp[S-sum[i]+x[i][j]]);
    for (int i = 1; i <= tot; i++)
        dfs(i, 0);
    f[0] = 1;
    for (int i = 0; i < bin[k]; i++)
        if (f[i]) {
            int C = i^(bin[k]-1);
            for (int j = C; j; j = (j-1)&C)
                if (ok[j])
                    f[i|j] = 1, p[i|j] = i;
        }
    if (!f[bin[k]-1]) {puts("No"); return 0; }
    int x = bin[k]-1;
    while (x) {
        int U = x-p[x];
        for (auto i : re[U]) {
            l[id[mp[S-sum[id[i]]+kp[i]]]] = S-sum[id[i]]+kp[i],
            r[id[mp[S-sum[id[i]]+kp[i]]]] = id[i];
        }
        x = p[x];
    }
    puts("Yes");
    for (int i = 1; i <= k; i++)
        printf("%d %d\n", l[i], r[i]);
    return 0;
}

原文地址:https://www.cnblogs.com/NaVi-Awson/p/11822935.html

时间: 2024-07-31 05:35:09

[Codeforces 1242C]Sum Balance的相关文章

codeforces 1242C/1243 E . Sum Balance

弄了一整晚 各种bug ...最后超时,差点放弃... 然后...没指望地 随意剪了个枝.,然后......居然做出来了... E. Sum Balance 题目大意:有k个箱子(1<=k<=15),在第i个箱子里有ni(1<=n<=5000)个整数 ( |aij|<=1e9).所有整数都是不同的.对 每个箱子 都  进行这样的操作:从每个箱子中取出一个整数,并放入 另一个或原本的 箱子中.问能否进行这样子的操作使得 操作结束后,每个箱子的数字的和相等. 赛后补题,看了cod

codeforces 85D. Sum of Medians

二次联通门 : codeforces 85D. Sum of Medians /* codeforces 85D. Sum of Medians 正解线段树或是平衡树 结果用vector暴力卡过去了 */ #include <algorithm> #include <iostream> #include <cstdio> #include <vector> using namespace std; void read (int &now) { reg

Codeforces Round #599 E - Sum Balance

tarjan缩点,枚举子集. 首先avg即平均值是可以直接求出来的,我们假设第i个盒子给出去a,那么平衡需要的是,avg-sumi+a,我们把所有数字开一个map保存下来,如果有该数字,连一条有向边,从a到avg-sumi+a. 可以看到,因为题目说了unique ,所以一个点的出度是固定的,即1.现在要求所有盒子平衡,且每个盒子必须拿出来一个值.那么我们从a连出了一条有向边,如果这是一个可行解的话,那么a会处在一个环中,即a肯定也会被需要,而且还处在当前这条链中.因出度为1,环是简单环,是一条

Codeforces 85D Sum of Medians(线段树)

85D Sum of Medians 题目链接 题意:一个集合有添加,删除元素,每次查询输出集合位置为i % 5 == 3的位置和 思路:线段树,线段树记录下% 5 == 0, 1, 2, 3, 4的和,并且记录一个mov表示右移多少,每次添加一个值的时候,就当前位置之后的一整段位置都要右移一个单位,这样去搞线段树维护一下即可 代码: #include <cstdio> #include <cstring> #include <cstdlib> #include <

Codeforces 920F. SUM and REPLACE

题目大意: 一个数列 支持两种操作 1 把区间内的数变成他们自己的约数个数 2 求区间和 思路: 可以想到每个数最终都会变成2或1 然后我们可以线段树 修改的时候记录一下每段有没有全被修改成1或2 是的话就不修改了 不是就暴力修改 因为每个数被修改的次数很小 1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstdlib> 5 #include<cstring&g

CodeForces - 920F SUM and REPLACE (线段树)

题意:给N个数M次操作,(1<=N,M<=3e5, 1<=ai<=1e6),1是使[L,R]中的每个元素变成其因子的个数之和:2是求[L,R]区间之和 分析:看上去就很线段树的一题,但是却思考了很久.发现1和2即使对其,也不会改变二者的值.而且一个大于2的数进行多次1操作,也最终会退化到2. 先预处理筛出1e6以内各数的质因子个数和.在线段树的节点中维护两个值:区间和以及区间最大值.在update函数中,如果该区间的最大值不超过2,那么该区间没有更新的必要:若超过2,则递归向下找到

codeforces CF920F SUM and REPLACE 线段树 线性筛约数

$ \Rightarrow $ 戳我进CF原题 F. SUM and REPLACE time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard input output: standard output Let $ D(x) $ be the number of positive divisors of a positive integer $ x $ . For example, $

cf1242C Sum Balance 【图 + 状压】

题目链接 cf1242C 题解 题意:有K个组,每组有若干个数[所有数互异],现在从每个组取出一个数,然后再将这些数分别放入一个组中,是否存在方案使得操作结束后每个组数字的和相等 最后相等的和是固定的,我们可以求出每个组距离结果的差值,对于这个组每个数,如果要将其取出,那么放入的一定就是这个数再减去这个差值的数值,而所有数互异,这样的值最多一个.这样每个数都可以找到一个这样匹配的数,向其连边,这样这张图中一个环就代表了一个轮换.如果我们能找到一组环,使得每个组都被包含一次,那么就是答案. 由于每

CodeForces 1299C Water Balance

首先,$\mathcal O(n^2)$ 的思路十分好想. 大概就是说,我们发现答案序列是单调不降的,所以倒着来,对于每个位置 $i$,我们找一个它后面的位置 $j$,使得 $\frac{\sum_{k=i}^{j} a_k}{j-i+1}$ 是最小的,然后把这一段全部赋值为这个值.这样,我们就在优先确保前面的值更优时,得到了一个可靠的贪心策略. 因为 $10^6$ 肯定要更好的方法,所以考虑优化.当处理位置 $i$ 的时候,$(i, n]$ 已经处理好了,成为了一个不降的,若干个连续段组成的数