2020杭电多校(二) New Equipments (离散化+费用流)

原题链接
题面
2020杭电多校(二) New Equipments (离散化+费用流)_第1张图片
题意
给定n个员工,每个员工配一台机器,每台机器都会产生不同的费用,费用由给定的函数产生。由函数图像可知,这个二元函数是一个开口向上的函数,并且位于y轴上方,因此最小值会在对称轴附近产生。如题中所给的m值范围在(-1e8 ~ 1e8)之间,因此必须要离散化才能存在下。
所以我们可以在对称轴附近找n个最接近的值,根据hall定理,自然能完全匹配。然后再将二分图的模型转换成网络流即可。

#include 
using namespace std;
typedef long long ll;
const int INF = 0x3f3f3f3f;
const int N = 3e3 + 10;
const int M = 5e5 + 10;
int h[N], last[N], pre[N], vis[N], flow[N];
ll ans[N], dis[N];

struct edge {
	int to, next, cap;
	ll cos;
}e[M<<1];

struct node {
	ll a, b, c;
}p[55];
vector<int> a[55], b;
int n, m, cnt, s, t, k;

void add(int u, int v, int cap, ll cos) {
	e[cnt].to = v;
	e[cnt].cap = cap;
	e[cnt].cos = cos;
	e[cnt].next = h[u];
	h[u] = cnt++;

	e[cnt].to = u;
	e[cnt].cap = 0;
	e[cnt].cos = -cos;
	e[cnt].next = h[v];
	h[v] = cnt++;
}

bool spfa() {
	queue<int> q;
	memset(pre, -1, sizeof pre);
	memset(vis, 0, sizeof vis);
	memset(dis, 0x3f, sizeof dis);
	dis[s] = 0;
	vis[s] = 1;
	flow[s] = INF;
	q.push(s);
	while (q.size()) {
		int u = q.front();
		q.pop();
		vis[u] = 0;
		for (int i = h[u]; ~i; i = e[i].next) {
			int v = e[i].to;
			if (e[i].cap && dis[v] > dis[u] + e[i].cos) {
				pre[v] = u;
				last[v] = i;
				dis[v] = dis[u] + e[i].cos;
				flow[v] = min(flow[u], e[i].cap);
				if (!vis[v]) {
					q.push(v);
					vis[v] = 1;
				}
			}
		}
	}
	return pre[t] != -1;
}

void MCMF() {
	while (spfa()) {
		ans[++k] = ans[k-1] + 1ll*flow[t] * dis[t];
		int now = t;
		while (now != s) {
			e[last[now]].cap -= flow[t];
			e[last[now] ^ 1].cap += flow[t];
			now = pre[now];
		}
	}
}

ll ff(int i, int j) {
	return (ll)(p[i].a * j * j + p[i].b * j + p[i].c);
}

int index(int m) {
	return lower_bound(b.begin(), b.end(), m) - b.begin() + 1;//取地址
}

void solve() {
	scanf("%d %d", &n, &m);
	memset(h, -1, sizeof h);
	memset(ans, 0, sizeof ans);
	cnt = 0;
	k = 0;
	for (int i = 1; i <= n; i++) a[i].clear();
	b.clear();

	for (int i = 1; i <= n; i++) scanf("%lld %lld %lld", &p[i].a, &p[i].b, &p[i].c);
	s = 0, t = N-2;
	for (int i = 1; i <= n; i++) {
		add(s, i, 1, 0);
		int pos = -(p[i].b / (2 * p[i].a));//对称轴
		pos = max(1, pos);//如果小于1,则从1开始往上取
		pos = min(pos, m);//如果大于m,就从m开始往下取
		for (int j = pos, tot = 0; j >= 1 && tot <= n; j--, tot++) a[i].push_back(j);
		for (int j = pos + 1, tot = 0; j <= m && tot <= n; j++, tot++) a[i].push_back(j);

		sort(a[i].begin(), a[i].end(), [&](int x, int y) { //排序
				return ff(i, x) < ff(i, y);
		});

		a[i].erase(a[i].begin() + n, a[i].end());   //保留n个数
		for (auto x : a[i]) b.push_back(x);//用于离散化处理
	}
	sort(b.begin(), b.end());
	b.erase(unique(b.begin(), b.end()), b.end());//离散化

	for (int i = 1; i <= b.size(); i++) {//所有机器向汇点连边
		add(i + n, t, 1, 0);
	}

	for (int i = 1; i <= n; i++) {
		for (auto x : a[i]) {
			add(i, index(x) + n, 1, ff(i, x));//员工和机器连边
 		}
	}

	MCMF();
	for (int i = 1; i <= k; i++) {
		if (i != k) printf("%lld ", ans[i]);
		else printf("%lld\n", ans[i]);
	}
}

int main() {
	int T;
	scanf("%d", &T);
	while (T--) {
		solve();
	}
}

你可能感兴趣的:(图论)