[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\n", 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\n", Ans);
	return 0;
}

  

时间: 2024-08-07 13:39:14

[BZOJ 1221] [HNOI2001] 软件开发 【费用流 || 三分】的相关文章

BZOJ 1221: [HNOI2001] 软件开发(最小费用最大流)

不知道为什么这么慢.... 费用流,拆点.... -------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #include<queue> #define rep( i, n ) for( int

BZOJ 1221: [HNOI2001] 软件开发

1221: [HNOI2001] 软件开发 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 1459  Solved: 809[Submit][Status][Discuss] Description 某软件公司正在规划一项n天的软件开发计划,根据开发计划第i天需要ni个软件开发人员,为了提高软件开发人员的效率,公司给软件人员提供了很多的服务,其中一项服务就是要为每个开发人员每天提供一块消毒毛巾,这种消毒毛巾使用一天后必须再做消毒处理后才能使用.消

BZOJ 3280: 小R的烦恼 &amp; BZOJ 1221: [HNOI2001] 软件开发

3280: 小R的烦恼 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 399  Solved: 200[Submit][Status][Discuss] Description 小R最近遇上了大麻烦,他的程序设计挂科了.于是他只好找程设老师求情.善良的程设老师答应不挂他,但是要求小R帮助他一起解决一个难题. 问题是这样的,程设老师最近要进行一项邪恶的实验来证明P=NP,这个实验一共持续n天,第i天需要a[i]个研究生来给他搬砖.研究生毕竟也是人,

【bzoj1221】[HNOI2001] 软件开发 费用流

题目描述 某软件公司正在规划一项n天的软件开发计划,根据开发计划第i天需要ni个软件开发人员,为了提高软件开发人员的效率,公司给软件人员提供了很多的服务,其中一项服务就是要为每个开发人员每天提供一块消毒毛巾,这种消毒毛巾使用一天后必须再做消毒处理后才能使用.消毒方式有两种,A种方式的消毒需要a天时间,B种方式的消毒需要b天(b>a),A种消毒方式的费用为每块毛巾fA, B种消毒方式的费用为每块毛巾fB,而买一块新毛巾的费用为f(新毛巾是已消毒的,当天可以使用):而且f>fA>fB.公司经

【BZOJ1221】【HNOI2001】软件开发 [费用流]

软件开发 Time Limit: 10 Sec  Memory Limit: 162 MB[Submit][Status][Discuss] Description 某软件公司正在规划一项n天的软件开发计划,根据开发计划第i天需要ni个软件开发人员,为了提高软件开发人员的效率,公司给软件人员提供了很多的服务,其中一项服务就是要为每个开发人员每天提供一块消毒毛巾,这种消毒毛巾使用一天后必须再做消毒处理后才能使用.消毒方式有两种,A种方式的消毒需要a天时间,B种方式的消毒需要b天(b>a),A种消毒

BZOJ 2668 交换棋子(费用流)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2668 题意:有一个n行m列的黑白棋盘,你每次可以交换两个相邻格子中的棋子,最终达到目标状态.要求第i行第j列的格子只能参与m[i,j]次交换. 思路: 我们将1看做要移动的数字,将0看做空白.那么若1在始末状态个数不同则无解:如某个格子始末状态均有1则这个格子的1对结果无影响,可以将其都置为0.将每个格子拆为为个点p0,p1,p2: (1)若格子初始为1,则连边:<s,p0,1,0>

BZOJ 3171 循环格(费用流)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=3171 题意: 思路:若能构成循环,则每个格子的入度出度 均为1.因此将每个点拆成两个点x1,x2,分别作为出点和入点.出点向周围四个点的入点连边,流1,费用视该格子的字母而定.该格子的字母正好是这个方 向则费用为0否则为1.原点S向每个出点连边,流量1费用0:每个入点向汇点连边,流量1费用0.求最小费用最大流即可. struct node { int u,v,next,cost,cap

BZOJ1221 [HNOI2001] 软件开发 【费用流】

题目 某软件公司正在规划一项n天的软件开发计划,根据开发计划第i天需要ni个软件开发人员,为了提高软件开发人员的效率,公司给软件人员提供了很多的服务,其中一项服务就是要为每个开发人员每天提供一块消毒毛巾,这种消毒毛巾使用一天后必须再做消毒处理后才能使用.消毒方式有两种,A种方式的消毒需要a天时间,B种方式的消毒需要b天(b>a),A种消毒方式的费用为每块毛巾fA, B种消毒方式的费用为每块毛巾fB,而买一块新毛巾的费用为f(新毛巾是已消毒的,当天可以使用):而且f>fA>fB.公司经理正

【BZOJ 1221】 [HNOI2001] 软件开发

[链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] /* 设一个超级源点S和超级汇点T S和2*i-1各连一条容量为ni的边. 花费为0 表示每天都会产生ni条要洗的毛巾 S和2*i各连一条容量为INF的边 花费为f 表示新买毛巾用 2*i-1和2*(i+a)连容量为INF的边 花费为fa 2*i-1和2*(i+b)连容量为INF的边 花费为fb 表示用完的毛巾消毒. 当然.用完的毛巾还能不马上消毒. 所以 2*i-1和2*(i+1)-1连容量为INF的边.花费为0 然后对于2*