hihocoder 1252 Kejin Game(拆点+最小割)


    Nowadays a lot of Kejin games (the games which are free to get and play, but some items or characters
are unavailable unless you pay for it) appeared. For example, Love Live, Kankore, Puzzle & Dragon,
Touken Ranbu and Kakusansei Million Arthur (names are not listed in particular order) are very
typical among them. Their unbelievably tremendous popularity has become a hot topic, and makes
considerable profit every day.
    You are now playing another Kejin game. In this game, your character has a skill graph which decides
how can you gain skills. Particularly speaking, skill graph is an oriented graph, vertices represent skills,
and arcs show their relationship — if an arc from A to B exists in the graph (i.e. B has a dependency
on A), you need to get skill A before you are ready to gain skill B. If a skill S has more than one
dependencies, they all need to be got firstly in order to gain S. Note that there is no cycles in the skill
graph, and no two same arcs.
    Getting a skill takes time and energy, especially for those advanced skills appear very deep in the
skill graph. However, as an RMB player, you know that in the game world money could distort even
basic principles. For each arc in skill graph, you can “Ke” (which means to pay) some money to erase
it. Further, for each skill, you could even “Ke” a sum of money to gain it directly in defiance of any
dependencies!
    As you have neither so much leisure time to get skills nor sufficient money, you decide to balance
them. All costs, including time, energy or money, can be counted in the unit “TA”. You calculate costs
for all moves (gaining a skill in normal way, erasing an arc and gaining a skill directly). Note that all
costs are non-negative integers. Then, you want to know the minimum cost to gain a particular skill
S if you haven’t get any skills initially. Solve this problem to make your game life more joyful and . . .
economical.
Input
    The input consists of no more than 10 test cases, and it starts with a single integer indicating the
number of them.
    The first line of each test case contains 3 positive integers N (1 ≤ N ≤ 500), M (1 ≤ M ≤ 10000)
and S, representing the number of vertices and arcs in the skill graph, and the index of the skill you’d
like to get. Vertices are indexed from 1 to N, each representing a skill. Then M lines follow, and each
line consists of 3 integers A, B and C, indicating that there is an arc from skill A to skill B, and C
(1 ≤ C ≤ 1000000) TAs are needed to erase this arc.
The next line contains N integers representing the cost to get N skills in normal way. That means,
the i-th integer representing the cost to get the i-th skill after all its dependencies are handled. The last
line also contains N integers representing the cost to get N skills directly by “Ke”. These 2N integers
are no more than 1000000.
Output
    For each test case, output your answer, the minimum total cost to gain skill S, in a single line.
Sample Input
2
5 5 5
1 2 5
1 3 5
2 4 8
4 5 10
3 5 15
3 5 7 9 11
100 100 100 200 200
5 5 5
1 2 5
1 3 5
2 4 8
4 5 10
3 5 15
3 5 7 9 11
5 5 5 50 50
Sample Output
31
26

一、大致题意

  给你一棵需要学习的技能树,想要学习树上的某项技能需要先学习该技能的所有前置技能。作为人名币玩家的你可以选择花钱来无视某项技能的一些前置技能,或者你可以花费一定的金额来直接得到你想要的技能。现在询问你想学习指定的技能所需的最小花费。我们已知的是技能的个数和技能之间的前置关系。

 

二、大致思路

  一开始想着是将整棵树倒着倒着建立,从最前置的技能开始向上更新每个节点所需的花费的最小值。但是写完之后跑样例1就过不了。在纸上画了画发现对于一些交叉的点来说他们的花费是会被重复计算的,然后我也不会处理。一直到赛后都没想出来怎么改。

  正确的思路是最小割。对于图的建立参照如下:

1、首先将每个技能拆为两个点,记为 i 和 i+n。这两点之间的连线容量为直接花人名币购买该项技能的花费。

2、超级源点到每个技能 i 之间的容量为满足前置后正常学习该点所需的花费。

3、技能 i 与 j 之间若存在前置的关系。如想学 j 技能需先学 i 技能,则连一条 i+n 到 j 点的连线,容量为打断该联系所需的花费。

4、最后不能忘记你想学的那个技能的 S+n 点与汇点相连。容量自然为正无穷。

 

三、代码

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<set>
using namespace std;
typedef long long LL;
const int inf = 0x3f3f3f3f;

int T, n, m, S;
const int maxn = 1005;
struct Edge
{
	Edge() {}
	Edge(int from, int to, LL cap, LL flow) :from(from), to(to), cap(cap), flow(flow) {}
	int from, to;
	LL cap, flow;
};

struct Dinic
{
	int n, m, s, t;            //结点数,边数(包括反向弧),源点与汇点编号
	vector<Edge> edges;     //边表 edges[e]和edges[e^1]互为反向弧
	vector<int> G[maxn];    //邻接表,G[i][j]表示结点i的第j条边在e数组中的序号
	bool vis[maxn];         //BFS使用,标记一个节点是否被遍历过
	int d[maxn];            //从起点到i点的距离
	int cur[maxn];          //当前弧下标

	void init(int n, int s, int t)
	{
		this->n = n, this->s = s, this->t = t;
		for (int i = 0; i <= n; i++) G[i].clear();
		edges.clear();
	}

	void AddEdge(int from, int to, LL cap)
	{
		edges.push_back(Edge(from, to, cap, 0));
		edges.push_back(Edge(to, from, 0, 0));
		m = edges.size();
		G[from].push_back(m - 2);
		G[to].push_back(m - 1);
	}

	bool BFS()
	{
		memset(vis, 0, sizeof(vis));
		queue<int> Q;//用来保存节点编号的
		Q.push(s);
		d[s] = 0;
		vis[s] = true;
		while (!Q.empty())
		{
			int x = Q.front(); Q.pop();
			for (int i = 0; i<G[x].size(); i++)
			{
				Edge& e = edges[G[x][i]];
				if (!vis[e.to] && e.cap>e.flow)
				{
					vis[e.to] = true;
					d[e.to] = d[x] + 1;
					Q.push(e.to);
				}
			}
		}
		return vis[t];
	}

	int DFS(int x, LL a)
	{
		if (x == t || a == 0)return a;
		LL flow = 0, f;//flow用来记录从x到t的最小残量
		for (int& i = cur[x]; i<G[x].size(); i++)
		{
			Edge& e = edges[G[x][i]];
			if (d[x] + 1 == d[e.to] && (f = DFS(e.to, min(a, e.cap - e.flow)))>0)
			{
				e.flow += f;
				edges[G[x][i] ^ 1].flow -= f;
				flow += f;
				a -= f;
				if (a == 0) break;
			}
		}
		return flow;
	}

	LL Maxflow()
	{
		LL flow = 0;
		while (BFS())
		{
			memset(cur, 0, sizeof(cur));
			flow += DFS(s, inf);
		}
		return flow;
	}
}DC;

int main()
{
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d %d %d", &n, &m, &S);
		DC.init(n * 2 + 2, 0, n * 2 + 1);
		for (int i = 1; i <= m; i++)
		{
			int u, v;
			LL w;
			scanf("%d %d %lld", &u, &v, &w);
			DC.AddEdge(u + n, v, w);
		}
		for (int i = 1; i <= n; i++)
		{
			LL w;
			scanf("%lld", &w);
			DC.AddEdge(0, i, w);
		}
		for (int i = 1; i <= n; i++)
		{
			LL w;
			scanf("%lld", &w);
			DC.AddEdge(i, i + n, w);
		}
		DC.AddEdge(S + n, 2 * n + 1, inf);
		printf("%lld\n", DC.Maxflow());
	}
}
/*
2
5 5 5
1 2 5
1 3 5
2 4 8
4 5 10
3 5 15
3 5 7 9 11
100 100 100 200 200
5 5 5
1 2 5
1 3 5
2 4 8
4 5 10
3 5 15
3 5 7 9 11
5 5 5 50 50

*/

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值