Tsinsen D471 奥妮的大楼

// 内部资料
传送门
考场上的思路

先把题意转换了。题目相当于是说给定 n 个二元组,第一维和第二维可以互相交换。现要求所有二元组的第一维互不相同,求第二维之和的最大值。

于是我们立即得到一个 O ( 2 n n log n ) 的做法:枚举每个二元组让哪个元素作为第一维即可。然后我这种弱智就什么都不会了……

思路

由于所有二元组都是二选一,因此我们可以立即想到网络流这种东西。当然像我这种弱智知道网络流也想不到,就不谈了。

可以用二分图匹配。令 X 部为二元组, Y 部为权值,每个二元组 ( a , b ) Y 部的 a 连一条权值为 b 的边,向 b 连一条权值为 a 的边,跑(最大)费用流即可,可以得到 40 分。


考虑抽象模型。我们将所有权值看作一个点,然后对于每个二元组 ( a , b ) ,在 a b 之间连一条无向边。现在我们将无向边定向,让起点作为底边,让终点作为竖边,那么所有点最多有一条出边

由于题目保证有解,因此最终的图只有两种情况:形成一棵树或者形成一棵环套树,否则总有至少一个结点出度不为 1

考虑最终答案是什么。发现答案是点权乘以入度之和。对于环套树来说,由于每个点只能有一条出边,因此环套树上的树是内向树,环套树上的每棵树对答案的贡献就是权值乘以度数减一(每个结点都有出边);而对环来说,无论边的方向如何,对答案的贡献都是一样的。因此对于环套树来说答案是确定的。

对于树来说,考虑对答案的贡献。对于不是根节点的点,贡献为权值乘以度数减一,对于根节点,贡献为权值乘以度数。我们让权值最大的那个结点成为根节点即可让贡献最大。实现时,只需要先计算出所有结点的权值乘以度数减一,再对于每棵树加上最大权值即可。时间复杂度 O ( n )

注意可能有重边,重边也算环。

扫描二维码关注公众号,回复: 1724511 查看本文章
参考代码
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstring>
#include <cassert>
#include <cctype>
#include <climits>
#include <ctime>
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <list>
#include <functional>
using LL = long long;
using ULL = unsigned long long;
using std::cin;
using std::cout;
using std::endl;
using INT_PUT = LL;
INT_PUT readIn()
{
    INT_PUT a = 0;
    bool positive = true;
    char ch = getchar();
    while (!(std::isdigit(ch) || ch == '-'))
        ch = getchar();
    if (ch == '-')
    {
        positive = false;
        ch = getchar();
    }
    while (std::isdigit(ch))
    {
        (a *= 10) -= ch - '0';
        ch = getchar();
    }
    return positive ? -a : a;
}
void printOut(INT_PUT x)
{
    char buffer[20];
    int length = 0;
    if (x < 0) putchar('-');
    else x = -x;
    do buffer[length++] = -(x % 10) + '0'; while (x /= 10);
    do putchar(buffer[--length]); while (length);
    putchar('\n');
}

const int maxn = int(2.5e5) + 5;
int n;
int a[maxn], b[maxn];
struct Graph
{
    struct Edge
    {
        int to;
        int next;
    } edges[maxn * 2];
    int i;
    int head[maxn * 2];
    int size[maxn * 2];
    Graph() : i(), size() { std::memset(head, -1, sizeof(head)); }
    void addEdge(int from, int to)
    {
        size[from]++;
        edges[i].to = to;
        edges[i].next = head[from];
        head[from] = i;
        i++;
    }
#define idx(G) idx_##G
#define wander(G, node) for (int idx(G) = G.head[node]; ~idx(G); idx(G) = G.edges[idx(G)].next)
#define DEF(G) const Graph::Edge& e = G.edges[idx(G)]; int to = e.to
};

#define RunInstance(x) delete new x
struct brute
{
    brute()
    {
        int U = 1 << n;
        LL ans = 0;
        for (int S = 0; S < U; S++)
        {
            int t[10];
            for (int i = 0; i < n; i++)
                if (S & (1 << i))
                    t[i] = a[i + 1];
                else
                    t[i] = b[i + 1];
            std::sort(t, t + n);
            if (std::unique(t, t + n) != t + n)
                continue;
            LL sum = 0;
            for (int i = 0; i < n; i++)
                if (S & (1 << i))
                    sum += b[i + 1];
                else
                    sum += a[i + 1];
            ans = std::max(ans, sum);
        }
        printOut(ans);
    }
};
struct work
{
    int bound;
    int disc[maxn * 2];
    void discretize()
    {
        for (int i = 1; i <= n; i++)
        {
            disc[++bound] = a[i];
            disc[++bound] = b[i];
        }
        std::sort(disc + 1, disc + 1 + bound);
        bound = std::unique(disc + 1, disc + 1 + bound) - (disc + 1);
        for (int i = 1; i <= n; i++)
        {
            a[i] = std::lower_bound(disc + 1, disc + 1 + bound, a[i]) - disc;
            b[i] = std::lower_bound(disc + 1, disc + 1 + bound, b[i]) - disc;
        }
    }

    Graph G;
    bool vis[maxn * 2]; 
    LL sum;
    int max;
    bool valid;
    void DFS1(int node, int parent)
    {
        max = std::max(max, disc[node]);
        sum += (LL)disc[node] * (G.size[node] - 1);

        vis[node] = true;
        wander(G, node)
        {
            if (parent == (idx(G) >> 1)) continue;
            DEF(G);
            if (!vis[to])
                DFS1(to, idx(G) >> 1);
            else
                valid = false;
        }
    }

    work() : bound(), vis()
    {
        discretize();
        for (int i = 1; i <= n; i++)
        {
            G.addEdge(a[i], b[i]);
            G.addEdge(b[i], a[i]);
        }

        sum = 0;
        for (int i = 1; i <= bound; i++)
            if (!vis[i])
            {
                max = 0;
                valid = true;
                DFS1(i, -1);
                if (valid) sum += max;
            }

        printOut(sum);
    }
};

void run()
{
    n = readIn();
    for (int i = 1; i <= n; i++)
    {
        a[i] = readIn();
        b[i] = readIn();
    }

    RunInstance(work);
}

int main()
{
#ifndef LOCAL
    freopen("building.in", "r", stdin);
    freopen("building.out", "w", stdout);
#endif
    run();
    return 0;
}
总结

从网络流来看,重点是要选对模型。这种二选一除了最小割还有二分图匹配这种东西可以考虑一下。对于正解,首先还是要想到图论建模。至于最后怎么想到正解的,那就得靠本事了,像我这种弱智怕是凉了。

猜你喜欢

转载自blog.csdn.net/lycheng1215/article/details/80735523