牛客九 J-Jam

牛客九 J-Jam

题意大概是一个十字路口,从每一个方向来的车会驶入其余的三个方向,总共有12种方向。给定每个方向车流的数量 c i , j c_{i,j} ci,j,问需要多少的单位时间可以使所有车辆驶过这个路口,并且要满足车流不能交错(如不能一边W->E一边N->S)。
数据范围: 0 < = c i , j < = 100 0<=c_{i,j}<=100 0<=ci,j<=100

题意不是很好理解,但是如果平时对于十字路口的信号灯有观察的话还是会大致明白这个题需要做什么。

在剔除了所有的右转之后,剩下八种车流,他们互相有着约束的关系,最后确定可以有十二种方案可以使两种车流同时通过。在比赛的时候,想的是一个整数规划,也就是以方案为变量,总共十二个变量,八个约束,但是整数规划不会,不知道怎么搞(或许可以先用单纯性然后找附近整点?)。乱搞了一个蒙特卡洛但是范围太大很难随机对,不是WA就是TLE。

打完后得知是用枚举的方式删掉两条边之后得到一个二分图,再跑网络流。

大概就是上面的这种图,可以发现,如果删掉7-8和3-4两条边,那么可以将左右两边分开,形成一个二分图。先枚举删掉的两条边的流量,将7,8两点和3,4两点分别减去之后,连接源点和汇点跑网络流,最后的最大流和之前枚举的两条边之和就是总共的可以两个方向的车辆同时通过的时间,用总的车流量去减,求最小值即为答案。

#include <bits/stdc++.h>
using namespace std;

const int N = 5, inf = 1e9;
int flow[N][N], b[N << 1], a[N << 2], num;
int head[20], s, t, dep[20], now[20];

struct edge
{
	int nxt, to, flo;
};
edge e[50];

void addEdge(int x, int y, int z)
{
	e[++ num].nxt = head[x];
	head[x] = num;
	e[num].to = y;
	e[num].flo = z;

	e[++ num].nxt = head[y];
	head[y] = num;
	e[num].to = x;
	e[num].flo = 0;
}

bool bfs()
{
	memset (dep, -1, sizeof (dep));
	dep[s] = 0;
	queue <int> q;
	q.push(s);

	while (!q.empty())
	{
		int x = q.front(); q.pop();
		for (int i = head[x]; i != -1; i = e[i].nxt)
		{
			int to = e[i].to;
			if (dep[to] == -1 && e[i].flo > 0)
			{
				dep[to] = dep[x] + 1;
				q.push(to);
			}
		}
	}

	if (dep[t] == -1)
		return false;
	return true;
}

int dfs(int x, int f)
{
	if (x == t)
		return f;

	for (int i = now[x]; i != -1; i = e[i].nxt)
	{
		now[x] = i;
		int to = e[i].to;
		if (e[i].flo > 0 && dep[to] == dep[x] + 1)
		{
			int val = dfs(to, min(f, e[i].flo));
			if (val > 0)
			{
				e[i].flo -= val;
				e[i ^ 1].flo += val;
				return val;
			}
		}
	}
	return 0;
}

int dinic(int x, int y)
{
	int ans = 0, val;
	s = x; t = y;
	while(bfs())
	{
		for (int i = 0; i <= 9; i ++)
			now[i] = head[i];

		while (val = dfs(s, inf))
		{
			ans += val;
		}
	}
	return ans;
}

int main()
{
	//freopen("in.txt", "r", stdin);
	ios::sync_with_stdio(false);
  	cin.tie(0);
  	srand(time(0));

	int T = 1;
	cin >> T;
	while (T --)
	{
		for (int i = 1; i <= 4; i ++)
			for (int j = 1; j <= 4; j ++)
				cin >> flow[i][j];
		
		b[1] = flow[1][3]; b[2] = flow[1][2];
		b[3] = flow[2][4]; b[4] = flow[2][3];
		b[5] = flow[3][1]; b[6] = flow[3][4];
		b[7] = flow[4][2]; b[8] = flow[4][1];

		int ans = inf, ans1 = max(max(flow[1][4], flow[2][1]), max(flow[3][2], flow[4][3]));
		int sum = 0;
		for (int i = 1; i <= 8; i ++)
			sum += b[i];

		for (int i = 0; i <= min(b[7], b[8]); i ++)
			for (int j = 0; j <= min(b[3], b[4]); j ++)
			{
				memset (head, -1, sizeof (head));
				num = -1;

				addEdge(0, 1, b[1]);
				addEdge(0, 6, b[6]);
				addEdge(0, 7, b[7] - i);
				addEdge(0, 8, b[8] - i);

				addEdge(2, 9, b[2]);
				addEdge(5, 9, b[5]);
				addEdge(3, 9, b[3] - j);
				addEdge(4, 9, b[4] - j);

				addEdge(1, 2, inf);
				addEdge(1, 4, inf);
				addEdge(1, 5, inf);
				addEdge(6, 2, inf);
				addEdge(6, 3, inf);
				addEdge(6, 5, inf);
				addEdge(7, 2, inf);
				addEdge(7, 3, inf);
				addEdge(8, 4, inf);
				addEdge(8, 5, inf);

				int val = dinic(0, 9);
				val += i + j;
				ans = min(ans, sum - val);
			}

		cout << max(ans, ans1) << "\n";
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值