• 【洛谷2469/BZOJ1927】[SDOI2010]星际竞速(费用流/最小路径覆盖)


    题目:

    洛谷2469

    分析:

    把题目翻译成人话:给一个带边权的DAG,求一个路径覆盖方案使路径边权总和最小。从点(i)开始的路径需要额外加上(A_i)的权值。
    回xian忆chang一xue下xi不带权DAG的最小路径覆盖用网络流是怎么做的:把点(u)拆成(u)(u')两个点,如果原图存在边((u,v))就在网络中连边((u,v')),然后源点(s)向所有(u)连边,所有(v)向汇点(t)连边,所有边容量均为(1),跑最大流。原图中点数(n)减去最大流就是最小路径覆盖。
    考虑这样做的原理:最小路径覆盖中每个点属于且仅属于一条路径。对于任意包含多于(1)个点的路径,有且只有一个点入度为(0),一个点出度为(0),其余所有点入度、出度皆为(1)。网络中若((s,u))有流量说明(u)出度为(1)(s)流到(u)后必然要流向某个点(v')),若((u',t))有流量说明(u)入度为(1)(必须有某个点流到(u')((u',t))才有流量)。那么最大流就是所有点入度之和,(n)减去入度之和就是入度为(0)的点数,即路径起点的数量,即路径数。如果边((u,v'))有流量说明原图中((u,v))这条边在最小路径覆盖中。
    那么给每条边加上权以后呢?自然能想到把原图中边((u,v))的权作为网络中((u,v'))的费用然后跑费用流

    然后你就gg了

    费用流全名叫“最小费用最大流”,是在保证流量最大的情况下的最小费用。在这道题中,就相当于首先要保证路径的条数最少(即流量最大),然后再使费用最小。此时要感谢某神犇T兔z崽z子给我说的我自己想出来的方法……先打个广告

    戳我进入兔崽子的博客

    (s)向每个(u')连边,这条边有流量说明(u)入度为(0)(因为((u',t))容量为(1)((s,u‘))有流量就说明肯定没有别的点会流到(u'))。这样无论如何路径覆盖最大流都是(n),就消除了路径条数的影响,求出最小费用即为答案。
    这种方案顺便解决了下一个问题:路径起点有额外权值。把(A_u)作为((s,u'))的费用即可。

    代码:

    #include <cstdio>
    #include <algorithm>
    #include <cstring>
    #include <iostream>
    #include <queue>
    using namespace std;
    namespace zyt
    {
    	typedef long long ll;
    	const ll INF = 0x3f3f3f3f3f3f3f3fLL;
    	const int N = 810, P = N * 2, M = (N * 3 + 15010) * 2;
    	int head[P], cnt, n, m, s, t;
    	struct edge
    	{
    		int to, w, c, next;
    	}e[M];
    	inline void add(const int a, const int b, const int c, const int d)
    	{
    		e[cnt] = (edge){b, c, d, head[a]};
    		head[a] = cnt++;
    	}
    	inline void addtw(const int a, const int b, const int c, const int d)
    	{
    		add(a, b, c, d);
    		add(b, a, 0, -d);
    	}
    	namespace EK
    	{
    		bool vis[P];
    		ll dis[P];
    		int pre[P];
    		bool SPFA()
    		{
    			queue<int>q;
    			memset(pre, -1, sizeof(pre));
    			memset(vis, 0, sizeof(vis));
    			memset(dis, INF, sizeof(dis));
    			q.push(s);
    			vis[s] = true;
    			dis[s] = 0;
    			while (!q.empty())
    			{
    				int u = q.front();
    				q.pop();
    				vis[u] = false;
    				for (int i = head[u]; ~i; i = e[i].next)
    				{
    					int v = e[i].to;
    					if (e[i].w && dis[v] > dis[u] + e[i].c)
    					{
    						dis[v] = dis[u] + e[i].c, pre[v] = i;
    						if (!vis[v])
    							vis[v] = true, q.push(v);
    					}
    				}
    			}
    			return dis[t] != INF;
    		}
    		ll EK()
    		{
    			ll ans = 0;
    			while (SPFA())
    			{
    				int i = pre[t];
    				int minn = n + 1;
    				while (~i)
    					minn = min(minn, e[i].w), i = pre[e[i ^ 1].to];
    				i = pre[t];
    				while (~i)
    					e[i].w -= minn, e[i ^ 1].w += minn, i = pre[e[i ^ 1].to];
    				ans += minn * dis[t];
    			}
    			return ans;
    		}
    	}
    	int work()
    	{
    		ios::sync_with_stdio(false);
    		memset(head, -1, sizeof(head));
    		cin >> n >> m;
    		s = n * 2 + 1, t = n * 2 + 2;
    		for (int i = 1; i <= n; i++)
    		{
    			int a;
    			cin >> a;
    			addtw(s, i, 1, 0);
    			addtw(s, i + n, 1, a);
    			addtw(i + n, t, 1, 0);
    		}
    		for (int i = 0; i < m; i++)
    		{
    			int a, b, c;
    			cin >> a >> b >> c;
    			if (a > b)
    				swap(a, b);
    			addtw(a, b + n, 1, c);
    		}
    		cout << EK::EK();
    		return 0;
    	}
    }
    int main()
    {
    	return zyt::work();
    }
    
  • 相关阅读:
    Eclipse下Tomcat插件的安装
    Eclipse插件的安装方法
    tomcat 详解五 tomcat页面设置访问权限
    W3C XML Schema 教程
    vb.net向Excel中写入值
    用VB操作Excel的方法
    怎样用VB自动更新应用程序
    VB读写INI文件的四个函数以及相关API详细说明
    oracle数据库元数据SQL查询
    javascript JTimer_2.0 时间日历控件使用方法
  • 原文地址:https://www.cnblogs.com/zyt1253679098/p/9614246.html
Copyright © 2020-2023  润新知