洛谷 P3254——圆桌问题【最大流 & Dinic算法 + 优化 & 另类贪心解法】

题目传送门


题目描述

假设有来自m 个不同单位的代表参加一次国际会议。每个单位的代表数分别为ri (i =1,2,……,m)。

会议餐厅共有n 张餐桌,每张餐桌可容纳ci (i =1,2,……,n)个代表就餐。

为了使代表们充分交流,希望从同一个单位来的代表不在同一个餐桌就餐。试设计一个算法,给出满足要求的代表就餐方案。

对于给定的代表数和餐桌数以及餐桌容量,编程计算满足要求的代表就餐方案。


输入格式

第1 行有2 个正整数m 和n,m 表示单位数,n 表示餐桌数,1<=m<=150, 1<=n<=270。

第2 行有m 个正整数,分别表示每个单位的代表数。

第3 行有n 个正整数,分别表示每个餐桌的容量。


输出格式

如果问题有解,第1 行输出1,否则输出0。接下来的m 行给出每个单位代表的就餐桌号。如果有多个满足要求的方案,只要输出1 个方案。


输入

4 5
4 5 3 5
3 5 2 6 4

输出

1
1 2 4 5
1 2 3 4 5
2 4 5
1 2 3 4 5


题解

  • 比较清爽的最大流(奇水无比

  • 这题建图感觉还是很显然的

  • 首先可以看出一个明显的二分图模型, 单位是一种点, 餐桌是另一种点, 代表数量可以看做是流量.

  • s s 连到所有单位点, 容量为代表数量. 单位点和餐桌点之间两两连一条容量为 1 1 的边代表"同一个单位来的代表不能在同一个餐桌就餐"的限制. 餐桌点再向 t t 连一条容量为餐桌大小的边来限制这个餐桌的人数.

    这样的话, 每一单位流量都代表着一个代表. 它们流经的点和边就代表了它们的特征. 而容量就是对代表的限制…

  • 至于输出方案, 我们只要看从单位点到餐桌点的边有哪些满载了, 一条满载的边 ( u , v ) (u,v) 意义就是在最优方案中一个来自 u u 代表的单位的代表坐到了 v v 代表的餐桌上.

    当然如果最大流没有跑满 (最大流的值不等于代表数量之和) 的话肯定有代表没被分配出去, 判定无解.


有毒的贪心解法

** 贪心的策略:

  • 首先将桌子从大到小排个序,再讲单位的规模从大到小排个序。这样有什么用呢?因为单位规模越大就越难满足,所以我们优先考虑他们;

  • 而对于桌子你可以这样想,你桌子数量越多显然更容易满足题意,又因为小桌子很容易坐爆而导致不能用,所以我们优先坐大桌子。


AC-Code

  • Dinic解法:
#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);

const int inf = 0x3f3f3f3f;
const int maxn = 150 * 270 + 150 + 270;
int n, m;

struct Edge {
	int to;
	int next;
	int val;
}edge[maxn << 1]; // 双向边,开 2 倍数组
int head[maxn];
int cnt; // 边的数量,从 0 开始编号
int depth[maxn]; // 分层图标记深度
int cur[maxn]; // 当前弧优化,记录当前点 u 循环到了哪一条边
void add(int u, int v, int w) {
	edge[cnt].to = v;
	edge[cnt].next = head[u];
	edge[cnt].val = w;
	head[u] = cnt++;
}

// bfs分层图
bool bfs(int s, int t) {
	queue<int>q;
	memset(depth, 0, sizeof depth);
	depth[s] = 1; // 源点深度为 1
	cur[s] = head[s];
	q.push(s);
	while (!q.empty()) {
		int u = q.front();
		q.pop();
		for (int i = head[u]; ~i; i = edge[i].next) {
			int v = edge[i].to;
			if (edge[i].val > 0 && depth[v] == 0) { // 如果残量不为0,且 v 还未分配深度
				depth[v] = depth[u] + 1;
				cur[v] = head[v]; //------当前弧优化,注意在bfs里,这样做到了“按需赋值”,因为Dinic本来上界就松得一匹, BFS的过程中不连通的点根本就不用再管了...
				if (v == t) // -----分层图汇点优化:遇到汇点直接返回------
					return true;
				q.push(v);
			}
		}
	}
	return depth[t]; // 汇点深度为 0:不存在分层图,返回false;
					 //           非 0 :存在增广路,返回true
}

// dfs寻找增广路
int dfs(int u, int flow, int t) {
	if (u == t || flow <= 0) // 到达汇点
		return flow;
	int rest = flow;
	for (int i = cur[u]; ~i; i = edge[i].next) {
		int v = edge[i].to;
		if (depth[v] == depth[u] + 1 && edge[i].val != 0) { // 满足分层图、残量>0 两个条件
			int k = dfs(v, min(rest, edge[i].val), t); // 向下增广
			if (k < 0) // ------无用点优化-----
				depth[v] = 0;
			rest -= k;
			edge[i].val -= k; // 正向边减
			edge[i ^ 1].val += k; // 反向边加
			if (rest <= 0) //------剩余量优化:在进行增广的时候,如果该节点已经没有流量,直接退出------
				break;
		}
	}
	return flow - rest; // flow:推送量,rest:淤积量,flow - rest:接受量/成功传递量
}
int Dinic(int s, int t) {
	int ans = 0;
	while (bfs(s, t)) {
		ans += dfs(s, inf, t);
	}
	return ans;
}
int main() {
	ios;
	while (cin >> m >> n) {
		cnt = 0;
		memset(head, -1, sizeof head);
		int peoplenum = 0;
		for (int i = 1, k; i <= m; ++i) {
			cin >> k;
			peoplenum += k;
			add(0, i, k);
			add(i, 0, 0);
		}
		for (int i = 1, k; i <= n; ++i) {
			cin >> k;
			for (int j = 1; j <= m; ++j) {
				add(j, m + i, 1);
				add(m + i, j, 0);
			}
			add(m + i, m + n + 1, k);
			add(m + n + 1, m + i, 0);
		}
		if (Dinic(0, m + n + 1) == peoplenum) {
			cout << 1 << endl;
			for (int i = 1; i <= m; ++i) {
				for (int j = head[i]; ~j; j = edge[j].next) {
					int v = edge[j].to;
					if (v != 0 && !edge[j].val)
						cout << v - m << " ";
				}
				cout << endl;
			}
		}
		else
			cout << 0 << endl;
	}
}

  • 贪心解法
#include <bits/stdc++.h>
using namespace std;
const int maxn = 305, maxm = 205;
int d[maxn], z[maxn], wow[maxn];
int m ,n;
struct lpl
{
    int num, data;
}desk[maxn], people[maxm];
bool operator < (lpl a, lpl b)
{
    return a.data > b.data;
}
vector<int> ld[maxm];
inline void putit()
{
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= m; ++i)
    {
        scanf("%d", &d[i]);
        people[i].num = i; people[i].data = d[i];
    }    
    for(int i = 1; i <= n; ++i)
    {
        scanf("%d", &z[i]);
        desk[i].num = i; desk[i].data = z[i];
    }
}
inline void print()
{
    printf("1\n");
    for(int i = 1; i <= m; ++i)
    {
        for(int j = 0; j < ld[i].size(); ++j)
        {
            printf("%d ", ld[i][j]);
        }
        printf("\n");
    }
}
int main()
{
    putit(); 
    sort(desk + 1, desk + n + 1); 
    sort(people + 1, people + m + 1);
    for(int i = 1; i <= m; ++i)
    {
        int qwe = 1;
        for(int j = 0; j < people[i].data; ++j)
        {
            bool flag = false;
            for(int t = qwe; t <= n; ++t)
            {
                if(wow[ desk[t].num ] < desk[t].data)
                {
                    wow[ desk[t].num ]++; qwe = t + 1;
                    flag = true; ld[ people[i].num ].push_back(desk[t].num); 
                    break;
                }
            }
            if(flag == false) 
            {
                 printf("0");
                 return 0;
            }
        }
    }
    print();
    return 0;
}
发布了104 篇原创文章 · 获赞 60 · 访问量 5847

猜你喜欢

转载自blog.csdn.net/Q_1849805767/article/details/103605294
今日推荐