zoukankan      html  css  js  c++  java
  • [BZOJ 1221] [HNOI2001] 软件开发 【费用流 || 三分】

    题目链接:BZOJ - 1221

    题目分析

    算法一:最小费用最大流

    首先这是一道经典的网络流问题。每天建立两个节点,一个 i 表示使用毛巾,一个 i' 表示这天用过的毛巾。

    然后 i 向 T 连 Ai (第 i 天需要的毛巾数)。从 S 向 i' 连 Ai ,这样这天新增的用过的毛巾就是 Ai 了。

    然后 i' 可以连向 (i+1)' ,表示留到下一天再处理,i' 还可以流向 i+p+1 和 i+q+1,表示洗了之后再次使用,这两种边是有费用的。

    还有就是新购买毛巾,从 S 向 i 连,费用就是买新毛巾的费用。

    这样,使用最小费用最大流就可以解决这道题了。

    算法二:三分

    然而这道题目有一个神奇的做法,速度远远快于费用流的解法。

    我发现提交记录里 faebdc 神犇的代码速度极其快,于是我就向他请教,他告诉我这道题是集训队作业里的一道..那道题目的数据范围是 n <= 10^5 ... 我只能Orzzz。

    在 faebdc 神犇的光辉照耀下,我终于似懂非懂写出了这个三分的代码..

    首先,如果已经确定要购买多少毛巾,一定一开始就先购买这些毛巾是最优的。然后之后的每天一定先使用没用过的,再使用花费少的洗涤方式,再使用花费多的洗涤方式。

    这样,使用一个队列就可以 O(n) 计算出购买 x 条毛巾时的最少花费了,记为 f(x) 。

    然后..重点是.. 可以分析出 f(x) 是一个单谷的函数,可以三分 x 。(怎么分析出的呢= = faebdc 神犇讲解了一下然后我太弱听不懂...

    然后就可以愉快地三分了.. 

    代码

    费用流代码:

    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #include <cstring>
    #include <cmath>
    #include <algorithm>
    #include <queue>
    
    using namespace std;
    
    const int MaxN = 1000 + 5, INF = 999999999;
    
    inline int gmin(int a, int b) {return a < b ? a : b;}
    inline int gmax(int a, int b) {return a > b ? a : b;}
    
    int n, p, q, f, fp, fq, S, T, Tot, MinCost, MaxFlow;
    int d[MaxN * 2];
    
    bool InQue[MaxN * 2];
    
    struct Edge
    {
    	int u, v, w, Ct;
    	Edge *Next, *Other;
    } E[MaxN * 12], *P = E, *Point[MaxN * 2], *Pre[MaxN * 2]; 
    
    inline void AddEdge(int x, int y, int z, int k)
    {
    	Edge *Q = ++P; ++P;
    	P -> u = x; P -> v = y; P -> w = z; P -> Ct = k;
    	P -> Next = Point[x]; Point[x] = P; P -> Other = Q;
    	Q -> u = y; Q -> v = x; Q -> w = 0; Q -> Ct = -k;
    	Q -> Next = Point[y]; Point[y] = Q; Q -> Other = P; 
    }
    
    queue<int> Q;
    
    bool Found()
    {
    	memset(d, 0x7f, sizeof(d));
    	memset(InQue, 0, sizeof(InQue));
    	while (!Q.empty()) Q.pop();
    	d[S] = 0; InQue[S] = true; Q.push(S);
    	int x;
    	while (!Q.empty())
    	{
    		x = Q.front(); InQue[x] = false; Q.pop();
    		for (Edge *j = Point[x]; j; j = j -> Next)
    			if (j -> w && d[x] + j -> Ct < d[j -> v])
    			{
    				d[j -> v] = d[x] + j -> Ct;
    				Pre[j -> v] = j;
    				if (!InQue[j -> v]) 
    				{
    					InQue[j -> v] = true;
    					Q.push(j -> v);
    				}
    			}
    	}
    	return d[T] < INF;
    }
    
    void Augment()
    {
    	int Flow = INF;
    	for (Edge *j = Pre[T]; j; j = Pre[j -> u]) Flow = gmin(Flow, j -> w);
    	for (Edge *j = Pre[T]; j; j = Pre[j -> u]) 
    	{
    		j -> w -= Flow;
    		j -> Other -> w += Flow;
    	}
    	MaxFlow += Flow;
    	MinCost += Flow * d[T];
    }
    
    int main()
    {
    	scanf("%d%d%d%d%d%d", &n, &p, &q, &f, &fp, &fq);
    	int Num;
    	Tot = n * 2; S = ++Tot; T = ++Tot;
    	for (int i = 1; i <= n; ++i) 
    	{
    		scanf("%d", &Num);
    		AddEdge(S, i, Num, f);
    		AddEdge(i, T, Num, 0);
    		AddEdge(S, n + i, Num, 0);
    	}
    	for (int i = 1; i < n; ++i)
    	{
    		AddEdge(n + i, n + i + 1, INF, 0);
    		if (i + p + 1 <= n) AddEdge(n + i, i + p + 1, INF, fp);
    		if (i + q + 1 <= n) AddEdge(n + i, i + q + 1, INF, fq);
    	}
    	while (Found()) Augment();
    	printf("%d
    ", MinCost);
    	return 0;
    }

    三分代码:

    #include <iostream>
    #include <cstdlib>
    #include <cstring>
    #include <cmath>
    #include <cstdio>
    #include <algorithm>
    
    using namespace std;
    
    inline void Read(int &Num)
    {
    	char c = getchar();
    	while (c < '0' || c > '9') c = getchar();
    	Num = c - '0'; c = getchar();
    	while (c >= '0' && c <= '9')
    	{
    		Num = Num * 10 + c - '0';
    		c = getchar();
    	}
    }
    
    const int MaxN = 1000 + 5, INF = 999999999;
    
    inline int gmin(int a, int b) {return a < b ? a : b;}
    
    int n, p, q, f, fp, fq, SumA, Head, Tail;
    int A[MaxN], Q[MaxN][2], Ans;
    
    int Calc(int x)
    {	
    	int ret, Cnt, Rest;
    	Head = 1; Tail = 0;
    	Rest = x;
    	ret = 0;
    	for (int i = 1; i <= n; ++i)
    	{
    		if (i - p - 1 > 0) 
    		{
    			Q[++Tail][0] = i - p - 1;
    			Q[Tail][1] = A[i - p - 1];
    		}
    		if (Rest >= A[i]) Rest -= A[i];
    		else
    		{
    			Cnt = A[i] - Rest;
    			Rest = 0;
    			while (Cnt > 0 && Head <= Tail)
    			{
    				if (Q[Head][0] <= i - q - 1)
    				{
    					if (Q[Head][1] > Cnt)
    					{
    						ret += Cnt * fq;
    						Q[Head][1] -= Cnt;
    						Cnt = 0;
    					}
    					else
    					{
    						ret += Q[Head][1] * fq;
    						Cnt -= Q[Head][1];
    						++Head;
    					}
    				}
    				else
    				{
    					if (Q[Tail][1] > Cnt)
    					{
    						ret += Cnt * fp;
    						Q[Tail][1] -= Cnt;
    						Cnt = 0;
    					}
    					else
    					{
    						ret += Q[Tail][1] * fp;
    						Cnt -= Q[Tail][1];
    						--Tail;
    					}
    				}
    			}
    			if (Cnt > 0) return INF;
    		}
    	}
    	ret += f * x;
    	return ret;
    }
    
    int main()
    {
    	scanf("%d%d%d%d%d%d", &n, &p, &q, &f, &fp, &fq);
    	for (int i = 1; i <= n; ++i) 
    	{
    		Read(A[i]);
    		SumA += A[i];
    	}
    	int l = 1, r = SumA, mid1, mid2;
    	while (r - l >= 3)
    	{
    		mid1 = l + (r - l) / 3;
    		mid2 = r - (r - l) / 3;
    		if (Calc(mid1) < Calc(mid2)) r = mid2 - 1;
    		else l = mid1 + 1;
    	}
    	Ans = INF;
    	for (int i = l; i <= r; ++i) Ans = gmin(Ans, Calc(i));
    	printf("%d
    ", Ans);
    	return 0;
    }	
    

      

  • 相关阅读:
    Path Sum
    Linked List Cycle II
    Linked List Cycle
    Single Number i and ii
    Binary Tree Preorder Traversal and Binary Tree Postorder Traversal
    Max Points on a Line
    Evaluate Reverse Polish Notation
    【leetcode】98 验证二叉搜索树
    【vivo2020春招】 02 数位之积
    【vivo2020春招】03 vivo智能手机产能
  • 原文地址:https://www.cnblogs.com/JoeFan/p/4424397.html
Copyright © 2011-2022 走看看