// 内部资料
传送门
考场上的思路
先把题意转换了。题目相当于是说给定 个二元组,第一维和第二维可以互相交换。现要求所有二元组的第一维互不相同,求第二维之和的最大值。
于是我们立即得到一个 的做法:枚举每个二元组让哪个元素作为第一维即可。然后我这种弱智就什么都不会了……
思路
由于所有二元组都是二选一,因此我们可以立即想到网络流这种东西。当然像我这种弱智知道网络流也想不到,就不谈了。
可以用二分图匹配。令 部为二元组, 部为权值,每个二元组 向 部的 连一条权值为 的边,向 连一条权值为 的边,跑(最大)费用流即可,可以得到 分。
考虑抽象模型。我们将所有权值看作一个点,然后对于每个二元组 ,在 和 之间连一条无向边。现在我们将无向边定向,让起点作为底边,让终点作为竖边,那么所有点最多有一条出边。
由于题目保证有解,因此最终的图只有两种情况:形成一棵树或者形成一棵环套树,否则总有至少一个结点出度不为 。
考虑最终答案是什么。发现答案是点权乘以入度之和。对于环套树来说,由于每个点只能有一条出边,因此环套树上的树是内向树,环套树上的每棵树对答案的贡献就是权值乘以度数减一(每个结点都有出边);而对环来说,无论边的方向如何,对答案的贡献都是一样的。因此对于环套树来说答案是确定的。
对于树来说,考虑对答案的贡献。对于不是根节点的点,贡献为权值乘以度数减一,对于根节点,贡献为权值乘以度数。我们让权值最大的那个结点成为根节点即可让贡献最大。实现时,只需要先计算出所有结点的权值乘以度数减一,再对于每棵树加上最大权值即可。时间复杂度 。
注意可能有重边,重边也算环。
![](/qrcode.jpg)
参考代码
#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;
}
总结
从网络流来看,重点是要选对模型。这种二选一除了最小割还有二分图匹配这种东西可以考虑一下。对于正解,首先还是要想到图论建模。至于最后怎么想到正解的,那就得靠本事了,像我这种弱智怕是凉了。