【这次市选我挂得很惨……前3题全部爆0(骗分都米骗到)……就这种烂成绩还能进市队,可见合肥人之沙茶……】
最不该挂的是第一题。第一问就是个裸的最大闭合子图,关键就出在第二问上,要求最大闭合子图的最小容量。本沙茶后来才发现这竟然是PKU原题!(PKU2987),因为,最大流求出来的最大闭合子图一定是容量最小的!故第二问只要在求出最大流后来一次遍历,找到S可达的结点个数即可。
详细证明(转网上某神犇的):
———————————————————————————————————————————————————
最大权不可能是负数,当是0的情况,自然是一个点不选最优,下面探讨忽略0的情况。
1:首先我假设有两个闭合子图都拥有相同的最大权,并且没有共同点,很容易证明不会出现这种情况,因为如果我们把两个闭合子图都选择上,那么最大权会变大。
2:仍然假设有两个闭合子图都拥有相同的最大权,但是有共同点,即重叠的意思。根据闭合图的特点,这些共同点不是随意的,可以知道,只要有一个点相同,那么这个点的能到达的所有后续点都必定是共同点。所以会得出一个结论,两个闭合子图重叠,重叠的部分必然是1个或者多个不重叠闭合图。
然后我们考虑不重叠的部分,这部分的点权和可以证明一定是非负。我们可以假设非重叠部分的点权和是负数,那么假如我们删掉这部分,只选取重叠部分(因为重叠部分肯定是闭合图),那么最大权也会变大,矛盾。所以非重叠部分点权和一定是非负数。
下面继续探讨非重叠部分的性质。上面的证明已经得出他们的点权和一定是非负。下面先抛开点权和等于0的情况,先讨论正数的情况。
假设两个闭合子图的非重叠部分都是正数,那么把这两个部分加起来重新构成闭合图,最大权必然会变大,与假设的两个同为最大权的闭合图矛盾。固可以证明非重叠部分的点权和肯定是0。
探讨到这部分,我们已经可以初步得出一个结论,就是一个最大权闭合子图的点数多少问题只能受到一些0权和子图(所有点权加起来等于0的子图)的干扰。
重点来到这些0权和子图上。下面我们又来做一个假设,就是假设我们求出了一个最大权闭合子图,并且里面有包含到一些0权和子图。而我们这时候需要做的就是找到那些可以删去的0权和子图,当我们删去了所有的这些子图,那么点数就可以达到最小。
关键来了。到底哪些0权和子图是可以删去的,哪些0权和子图是不可以删去的呢?
根据闭合图的性质,我们要删除一个点,那么必须把所有能到达这个点的点删去。那么很清晰的看到要删除一个子图,必须保证在这个子图外没有任何一个点指向这个子图。也就是说这个子图是封闭的,只有出边,没有入边。
最后一步,假如我们能证明在求解最大权闭合图的过程中保证不会选上这些0权和子图,那么这个证明就可以结束了。
通过最大流求解最大权闭合子图,我们把正点权和源点建边,边权为点权值,负点权和汇点建边,边权为点权绝对值。仔细分析求解最大流的过程,会发现一些东西。
由于那些可选可不选的0权和子图是封闭的,没有入边的,那么在求解最大流的过程中,不可能有外部流补充,所以这些0权和子图的每个点与源或者汇的边都是满流的。
最后求最大权闭合子图的点,方法是从源点开始通过非满流边搜索一个联通图,图内的点(除了源点)就是求出来的最大权闭合子图的点。而上面证明到0权和子图的点和源汇都是满流,并且没有入边,出边也没有流,那么肯定无法从源点搜索到。那么在求解的过程中这些0权和子图的点是肯定没有选上的。
就此证毕。
结论:通过最大流求解最大权闭合子图的问题,求出来的闭合子图点数也是最少的。
———————————————————————————————————————————————————
代码:
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
#define re(i, n) for (int i=0; i<n; i++)
#define re1(i, n) for (int i=1; i<=n; i++)
const int MAXN = 10002, MAXM = 120000, INF = ~0U >> 2;
struct edge {
int a, b, f, next;
edge () {}
edge (int _a, int _b, int _f) : a(_a), b(_b), f(_f), next(-1) {}
} ed[MAXM + MAXM];
int n, m = 0, s, t, hd[MAXN], tl[MAXN], st[MAXN], lev[MAXN], pr[MAXN], hs[MAXN], q[MAXN], now, res = 0, res_num = 0;
bool vst[MAXN];
void add_edge(int a, int b, int f)
{
ed[m] = edge(a, b, f);
if (hd[a] != -1) tl[a] = ed[tl[a]].next = m++; else hd[a] = tl[a] = m++;
ed[m] = edge(b, a, 0);
if (hd[b] != -1) tl[b] = ed[tl[b]].next = m++; else hd[b] = tl[b] = m++;
}
void init()
{
freopen("profit.in", "r", stdin);
int n0, m0, a0, b0, x;
scanf("%d%d", &n0, &m0);
n = n0 + 2; s = 0; t = n - 1;
memset(hd, -1, n << 2); memset(tl, -1, n << 2);
re1(i, n0) {
cin >> x;
if (x > 0) {add_edge(s, i, x); res += x;}
if (x < 0) add_edge(i, t, -x);
}
re1(i, m0) {
scanf("%d%d", &a0, &b0);
add_edge(a0, b0, INF);
}
fclose(stdin);
}
void aug()
{
int z = hs[t], i = t, p;
while (i != s) {
hs[i] -= z; p = pr[i]; ed[p].f -= z; ed[p ^ 1].f += z; i = ed[p].a;
if (!ed[p].f) now = i;
}
res -= z;
}
bool dfs()
{
q[0] = s; memset(vst, 0, n); vst[s] = 1; lev[s] = 0;
int i, j, f0;
for (int front=0, rear=0; front<=rear; front++) {
i = q[front];
for (int p=hd[i]; p != -1; p=ed[p].next) {
j = ed[p].b; f0 = ed[p].f;
if (!vst[j] && f0) {vst[j] = 1; lev[j] = lev[i] + 1; q[++rear] = j;}
}
}
if (!vst[t]) return 0;
now = s; memset(vst, 0, n); hs[s] = INF;
re(i, n) st[i] = hd[i];
bool ff;
while (!vst[s]) {
if (now == t) aug();
ff = 0;
for (int p=st[now]; p != -1; p=ed[p].next) {
j = ed[p].b; f0 = ed[p].f;
if (lev[now] + 1 == lev[j] && !vst[j] && f0) {st[now] = pr[j] = p; hs[j] = hs[now] <= f0 ? hs[now] : f0; now = j; ff = 1; break;}
}
if (!ff) {
vst[now] = 1;
if (now != s) now = ed[pr[now]].a;
}
}
return 1;
}
void solve()
{
while (dfs()) ;
q[0] = s; memset(vst, 0, n); vst[s] = 1;
int i, j, f0;
for (int front=0, rear=0; front<=rear; front++) {
i = q[front];
for (int p=hd[i]; p != -1; p=ed[p].next) {
j = ed[p].b; f0 = ed[p].f;
if (!vst[j] && f0) {vst[j] = 1; res_num++; q[++rear] = j;}
}
}
}
void pri()
{
freopen("profit.out", "w", stdout);
printf("%d\n%d\n", res, res_num);
fclose(stdout);
}
int main()
{
init();
solve();
pri();
return 0;
}