【最小割+枚举】POJ-1815 Friendship

34 篇文章 0 订阅
30 篇文章 0 订阅
Friendship
Time Limit: 2000MS Memory Limit: 20000K
   

Description

In modern society, each person has his own friends. Since all the people are very busy, they communicate with each other only by phone. You can assume that people A can keep in touch with people B, only if 
1. A knows B's phone number, or 
2. A knows people C's phone number and C can keep in touch with B. 
It's assured that if people A knows people B's number, B will also know A's number. 

Sometimes, someone may meet something bad which makes him lose touch with all the others. For example, he may lose his phone number book and change his phone number at the same time. 

In this problem, you will know the relations between every two among N people. To make it easy, we number these N people by 1,2,...,N. Given two special people with the number S and T, when some people meet bad things, S may lose touch with T. Your job is to compute the minimal number of people that can make this situation happen. It is supposed that bad thing will never happen on S or T. 

Input

The first line of the input contains three integers N (2<=N<=200), S and T ( 1 <= S, T <= N , and S is not equal to T).Each of the following N lines contains N integers. If i knows j's number, then the j-th number in the (i+1)-th line will be 1, otherwise the number will be 0. 

You can assume that the number of 1s will not exceed 5000 in the input. 

Output

If there is no way to make A lose touch with B, print "NO ANSWER!" in a single line. Otherwise, the first line contains a single number t, which is the minimal number you have got, and if t is not zero, the second line is needed, which contains t integers in ascending order that indicate the number of people who meet bad things. The integers are separated by a single space. 

If there is more than one solution, we give every solution a score, and output the solution with the minimal score. We can compute the score of a solution in the following way: assume a solution is A1, A2, ..., At (1 <= A1 < A2 <...< At <=N ), the score will be (A1-1)*N^t+(A2-1)*N^(t-1)+...+(At-1)*N. The input will assure that there won't be two solutions with the minimal score. 

Sample Input

3 1 3
1 1 0
1 1 1
0 1 1

Sample Output

1
2
————————————————————分割线而已————————————————————
前言:本题要理解好最小割和最大流之间的互相转化。并且对于方案的输出要求比较严格。
思路:首先考虑建图过程。需要拆边,为什么呢?我们来看一个例子:
  2 --- 4 --- 6
  /      /  \       \
S --- 3   5 --- T
假如是4号发生了"bad things",最小割是1,显然就是4本身。
也就是说,本题的边没有权值,仅仅是点有权值。那么拆点之后,边权为1。若两点之间有边,那么权值INF。
剩下的是输出"Score"最小的方案。别被吓着了。看一下公式发现,不就是权重递减么。那就是字典序而已。
但是想要让“随意跑”的最大流输出一个字典序最小的方案就不容易了。
“发生bad things的人 字典序尽量小” --> “最小割割到的边 字典序尽量小”
最小割割到哪个边,说明少了这一个人。
按照字典序,枚举删掉和某个人相连的所有边(也就是重新建图),重新跑一次Dinic,最大流如果减小,就说明这个人参与了最小割。一直到没有可行流为止。
正好和题意一致。删了字典序最小的几个人的边,导致S和T无法联系。
NO ANSWER:当S和T直接可以联系的时候无论如何都不会丧失联系。
代码如下:
/*
ID: j.sure.1
PROG:
LANG: C++
*/
/****************************************/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <map>
#include <string>
#include <climits>
#include <iostream>
#define LL long long
using namespace std;
const int INF = 0x3f3f3f3f;
/****************************************/
const int N = 444, M = 2e5;
int tot, head[N], lev[N], cur[N], q[N], s[N], S, T;
struct Node {
	int u, v, cap;
	int next;
}edge[M];
int n, src, des, ans, per[N], cnt;
int G[N][N], mat[N][N];

void init()
{
	tot = 0; memset(head, -1, sizeof(head));
}

void add(int u, int v, int c)
{
	edge[tot].u = u; edge[tot].v = v; edge[tot].cap = c;
	edge[tot].next = head[u]; head[u] = tot++;
}

bool bfs()
{
	memset(lev, -1, sizeof(lev));
	int fron = 0, rear = 0;
	q[rear++] = S;
	lev[S] = 0;
	while(fron < rear) {
		int u = q[fron%N]; fron++;
		for(int i = head[u]; i != -1; i = edge[i].next) {
			int v = edge[i].v;
			if(edge[i].cap && lev[v] == -1) {
				lev[v] = lev[u] + 1;
				q[rear%N] = v; rear++;
				if(v == T) return true;
			}
		}
	}
	return false;
}

int Dinic()
{
	int ret = 0;
	while(bfs()) {
		memcpy(cur, head, sizeof(head));//可能会超时
		int top = 0, u = S;
		while(1) {
			if(u == T) {
				int mini = INF, loc;
				for(int i = 0; i < top; i++) {
					if(mini > edge[s[i]].cap) {
						mini = edge[s[i]].cap;
						loc = i;
					}
				}
				for(int i = 0; i < top; i++) {
					edge[s[i]].cap -= mini;
					edge[s[i]^1].cap += mini;
				}
				ret += mini;
				top = loc;
				u = edge[s[top]].u;
			}
			int &i = cur[u];
			for(; i != -1; i = edge[i].next) {
				int v = edge[i].v;
				if(edge[i].cap && lev[v] == lev[u] + 1)	break;
			}
			if(i != -1) {
				s[top++] = i;
				u = edge[i].v;
			}
			else {
				if(!top) break;
				lev[u] = -1;
				u = edge[s[--top]].u;
			}
		}
	}
	return ret;
}

void build()
{
	init();
	for(int i = 1; i <= n; i++) {
		if(i != src && i != des) {
			add(i, n+i, 1); add(n+i, i, 0);
		}//差点之后自己和自己连边表示一个人
		else {
			add(i, n+i, INF); add(n+i, i, 0);
		}//保证S和T不会发生糟糕的事
		for(int j = 1; j <= n; j++) if(i != j && G[i][j]) {
			add(n+i, j, INF); add(j, n+i, 0);//其余若存在联系方式则连边
		}
	}
}

void Enum()
{
	cnt = 0;
	for(int i = 1; i <= n; i++) if(i != src && i != des) {
		for(int j = 1; j <= n; j++) {
			for(int k = 1; k <= n; k++) {
				mat[j][k] = G[j][k];//保存原图
				if(j == i || k == i) G[j][k] = 0;//删除被选择的人的所有边
			}
		}
		build();
		int ret = Dinic();
		if(ret < ans) {//说明这个人参与了最小割
			ans--;
			per[cnt++] = i;
		}
		else memcpy(G, mat, sizeof(G));//恢复
		if(!ans) return ;
	}
}

int main()
{
#ifdef J_Sure
//	freopen("000.in", "r", stdin);
//	freopen(".out", "w", stdout);
#endif
	scanf("%d%d%d", &n, &src, &des);
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= n; j++) {
			scanf("%d", &G[i][j]);
		}
	}
	if(G[src][des]) puts("NO ANSWER!");
	else {
		S = src; T = n + des;
		build();
		ans = Dinic();
		printf("%d\n", ans);
		if(ans) {
			Enum();//枚举删去一个人之后得到的最小割,看是否会减小,如果减小,说明
			for(int i = 0; i < cnt-1; i++) {
				printf("%d ", per[i]);
			}
			printf("%d\n", per[cnt-1]);
		}
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值