POJ 2411 Mondriaan’s Dream 新解

Posted in PKU OJ on 二月 24th, 2011 by 纳米 – 2 Comments

上次说的方法太落后了,这次为大家带来更常见的轮廓线的算法。

题目大意:

给定一个n行m列的网格,用1*2的方块覆盖,问有多少种覆盖方案。

解题报告:

我们以n=6,m=5为例。

首先,定义轮廓线:

POJ_2411_1

这就是我们所说的轮廓线。上图的轮廓线,我们以(3, 3)表示。

有什么用呢?

POJ_2411_2

我们定义状态f(i, j, S)为,轮廓线(i, j)上一行格子状态为S(以下轮廓线直接代指这些格子),再往上的所有格子被覆盖情况下,可行的方案数。其中,S = {s1,s2,…,s_m},s_k表示第k列的轮廓线的状态,1为被覆盖,0为未被覆盖。

在这里开始的示意图,灰色表示已经覆盖并且不在轮廓线上;红色表示轮廓线上被覆盖的格子;绿色表示轮廓线上不被覆盖的格子;黄色表示不需要考虑是否被覆盖的格子。

转移存在三种情况:

第一,用一个横着的方块覆盖:

POJ_2411_3-0

必须从这样的轮廓线转移而来:

POJ_2411_3-1

而不能出现这样的情况:

POJ_2411_3-2

因为这是不合法的、不满足定义的(定义要求轮廓线以上全部被覆盖)

第二,用一个竖着的方块覆盖:

POJ_2411_4-0

必须从这样的轮廓线转移而来:

POJ_2411_4-1

而不能出现这样的情况:

POJ_2411_4-2

因为这是不合法的,一个格子只能被覆盖一次。

第三,当前位置暂不覆盖:

POJ_2411_5-0

必须从这样的轮廓线转移而来:

POJ_2411_5-1

而不能出现这样的情况:

POJ_2411_5-2

因为这是不合法的、不满足定义的(定义要求轮廓线以上全部被覆盖)

这里要分析一下方程怎么写,由于现在很困,所以下次补上

先贴代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <cstdio>
 
const int MaxS = 2048;
 
long long solve(int n, int m)
{
	long long f[3][MaxS];
	long long *f0 = f[(m - 1) % 3], *f1, *f2;
	int maxs = 1 << m, tot, w0, w1;
	for (int k = 0; k < maxs; ++k) {
		tot = 0;
		f0[k] = 1;
		for (int i = 0; i < m; ++i) {
			if (k & 1 << i) {
				++tot;
			} else if (tot & 1) {
				f0[k] = 0;
			} else {
				tot = 0;
			}
		}
		if (tot & 1)
			f0[k] = 0;
	}
	for (int i = 1; i < n; ++i)
		for (int j = 0; j < m; ++j) {
			f0 = f[(i * m + j) % 3];
			f1 = f[(i * m + j - 1) % 3];
			f2 = f[(i * m + j - 2) % 3];
			w0 = 1 << j;
			w1 = j > 0 ? 1 << (j - 1) : 0;
			for (int k = 0; k < maxs; ++k)
				f0[k] = k & w0 ? (f1[k & ~w0] + (k & w1 ? f2[k | w0 | w1] : 0)) : f1[k | w0];
		}
	return f0[maxs - 1];
}
 
int main()
{
	int n, m;
	while (scanf("%d%d", &n, &m), n && m)
		printf("%I64d\n", (n * m & 1) ? 0 : (n > m ? solve(n, m) : solve(m, n)));
	return 0;
}

这是一篇测试

Posted in 乱七八糟 on 二月 9th, 2011 by 纳米 – Be the first to comment
aa bb
cc dd

World Final 1998 Flight Planning

Posted in 乱七八糟 on 十二月 2nd, 2010 by 纳米 – 2 Comments

题目大意:
飞机飞行高度在[20000,40000],必须是1000的倍数。正常飞行高度是30000,每小时耗油量是2000。飞行高度与正常高度每相差1000,每小时就需要增加耗油量10。每爬升高度1000,需要耗油50。
现在要飞行n个区域,每个区域有给定的长度和高度20000时风速和高度40000时风速,风速和高度成线性关系。飞行速度是400+风速。求最小耗油量以及高度方案。

解题方法:
简单动态规划题,以区域和高度作为状态。

注意事项:
最后输出是整数,四舍五入吗?那你就悲剧了^^

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <cmath>
#include <cstdio>
#include <cstdlib>
 
const double INF = 1e10;
 
const int maxn = 101, maxh = 21;
int n, len[maxn], speed[maxn][2], table[maxn][maxh], ans_project[maxn];
double dp[maxn][maxh], answer;
 
int input() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
		scanf("%d%d%d", &len[i], &speed[i][0], &speed[i][1]);
	return 0;
}
 
int solve() {
	for (int i = 0; i <= n; ++i)
		for (int j = 0; j < maxh; ++j)
			dp[i][j] = INF;
	dp[0][0] = 1000;
	for (int i = 1; i <= n; ++i) {
		double unit = (speed[i][1] - speed[i][0]) / (maxh - 1.0);
		for (int j = 0; j < maxh; ++j) {
			double datum = (2000 + abs(j - 10) * 10) * (len[i] / (400 + speed[i][0] + unit * j));
			for (int k = 0; k < maxh; ++k) {
				double tmp = dp[i - 1][k] + datum + (j > k ? (j - k) * 50 : 0);
				if (tmp < dp[i][j]) {
					dp[i][j] = tmp;
					table[i][j] = k;
				}
			}
		}
	}
	answer = INF;
	for (int i = 0; i < maxh; ++i)
		if (dp[n][i] < answer) {
			answer = dp[n][i];
			ans_project[n] = i;
		}
	for (int i = n - 1; i > 0; --i)
		ans_project[i] = table[i + 1][ans_project[i + 1]];
	return 0;
}
 
int output(int t) {
	printf("Flight %d: ", t);
	for (int i = 1; i <= n; ++i)
		printf("%d ", ans_project[i] + 20);
	printf("%.0f\n", ceil(answer));
	return 0;
}
 
int main() {
	int testCases;
	scanf("%d", &testCases);
	for (int t = 1; t <= testCases; ++t) {
		input();
		solve();
		output(t);
	}
	return 0;
}

World Final 1998 Crystal Clear

Posted in World Final on 十二月 2nd, 2010 by 纳米 – Be the first to comment

好久没更新了,继续更新吧。不过这次也该改目录名了,我也不是OIer了,要转型ACMer了。

题目大意:
以坐标系上每个整数坐标的点为圆心,直径为1建立圆。给定一个多边形,多边形上每条边的端点坐标都是整数。求被覆盖的圆的有效面积。
有效面积是这样定义的:
1. 如果整个圆都在多边形内,那么它的面积都是有效的;
2. 如果多边形的边穿过圆心,那么在多边形内的扇形面积是有效的。

解题方法:
因为坐标范围都很小,并且多边形的顶点都是整数,所以可以对于可能有效的圆,分别计算。对于每一个可能有效的圆:
1. 判断圆心是不是跟多边形某个顶点重合。如果是,累加扇形面积,结束;否则,转到2。
2. 判断圆心是否在某条边上。如果是,累加半圆面积,结束;否则,转到3.
3. 判断圆是否在多边形内。如果是,累加圆面积,结束。
圆在多边形内,当且仅当圆心离每一条边的距离不小于0.5,且圆心在多边形内。

几何知识(感谢gXX同学):
以下用*表示叉积,@表示点积。
1. 叉积、点积、欧几里得距离。
2. 计算两向量夹角角度:利用余弦定理。
3. 点在线段上,点为p,线段端点p1、p2,v1、v2对应p到p1、p2的向量,下同:当且仅当v1 * v2 = 0,且v1 @ v2 < 0
4. 点到线段所在直线距离:d = |v1 * v2| / Distance(p1, p2)
5. 判断点在多边形内:射线法和转角法,参见《算法艺术与信息学竞赛》P381。代码用转角法实现。

注意事项:
用余弦定理和反三角函数只能算出锐角,但是扇形可能对应一个钝角。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include <cstdio>
#include <cmath>
 
const double pi = 3.1415926535897932384626433832795;
 
const int maxn = 27;
int n;
struct POINT {
	int x, y;
	POINT() {
		x = y = 0;
	}
	bool operator != (const POINT &value) {
		const POINT *that = &value;
		return this->x != that->x || this->y != that->y;
	}
	POINT operator -(const POINT &value) {
		const POINT *that = &value;
		POINT result;
		result.x = this->x - that->x;
		result.y = this->y - that->y;
		return result;
	}
} px[maxn];
 
bool input() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i)
		scanf("%d%d", &px[i].x, &px[i].y);
	return (bool) n;
}
 
inline int min(int x, int y) {
	return x < y ? x : y;
}
 
inline int max(int x, int y) {
	return x > y ? x : y;
}
 
inline int CrossProduct(POINT v1, POINT v2) {
	return v1.x * v2.y - v2.x * v1.y;
}
 
inline int DotProduct(POINT v1, POINT v2) {
	return v1.x * v2.x + v1.y * v2.y;
}
 
inline double Distance(POINT p1, POINT p2) {
	return sqrt((double) ((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)));
}
 
inline double Angle(POINT p0, POINT p1, POINT p2) {
	POINT v0, v1, v2;
	v1 = p1 - p0;
	v2 = p2 - p0;
	return acos(DotProduct(v1, v2) / (Distance(v0, v1) * Distance(v0, v2)));
}
 
inline bool PointOnLine(POINT p, POINT p1, POINT p2) {
	POINT v1, v2;
	v1 = p1 - p;
	v2 = p2 - p;
	return !CrossProduct(v1, v2) && DotProduct(v1, v2) < 0;
}
 
inline double PointToLineDistance(POINT p, POINT p1, POINT p2) {
	POINT v1, v2;
	v1 = p1 - p;
	v2 = p2 - p;
	return fabs(CrossProduct(v1, v2) / Distance(p1, p2));
}
 
inline bool PointInPolygon(POINT p, POINT px[], int n) {
	// px[0] = px[n];
	double AngleSum = 0;
	for (int i = 1; i <= n; ++i) {
		int cp = CrossProduct(px[i - 1] - p, px[i] - p);
		if (cp > 0) {
			AngleSum += Angle(p, px[i - 1], px[i]);
		} else if (cp < 0) {
			AngleSum -= Angle(p, px[i - 1], px[i]);
		}
	}
	return fabs(AngleSum) > pi;
}
 
double solve() {
	double result = 0;
	POINT minp = px[1], maxp = px[1], p;
	for (int i = 2; i <= n; ++i) {
		minp.x = min(minp.x, px[i].x);
		minp.y = min(minp.y, px[i].y);
		maxp.x = max(maxp.x, px[i].x);
		maxp.y = max(maxp.y, px[i].y);
	}
	px[0] = px[n];
	px[n + 1] = px[1];
	for (p.x = minp.x; p.x <= maxp.x; ++p.x)
		for (p.y = minp.y; p.y <= maxp.y; ++p.y) {
			bool processed = false;
			if (!processed) {
				int k = 1;
				while (k <= n && px[k] != p)
					++k;
				if (k <= n) {
					if (CrossProduct(px[k] - px[k - 1], px[k + 1] - px[k]) < 0)
						result += Angle(px[k], px[k - 1], px[k + 1]) / 8;
					else
						result += (2 * pi - Angle(px[k], px[k - 1], px[k + 1])) / 8;
					processed = true;
				}
			}
			if (!processed) {
				int k = 1;
				while (k <= n && !PointOnLine(p, px[k - 1], px[k]))
					++k;
				if (k <= n) {
					result += pi / 8;
					processed = true;
				}
			}
			if (!processed) {
				bool broken = false;
				for (int i = 1; i <= n && !broken; ++i)
					broken = PointToLineDistance(p, px[i - 1], px[i]) < 0.5;
				if (!broken && PointInPolygon(p, px, n)) {
					result += pi / 4;
					processed = true;
				}
			}
		}
	return result;
}
 
int main() {
	int Cases = 0;
	while (input())
		printf("Shape %d\ninsulating area = %.3f cm^2\n", ++Cases, solve());
	return 0;
}

2010网易编程挑战赛 技术分析

Posted in 乱七八糟 on 五月 31st, 2010 by 纳米 – Be the first to comment

第一场:

A:另类的异或
纯水题一道,N进制无进位加法。

B:有道搜索框
赤裸裸的Trie解决,首先把所有的单词添加道Trie树里面,然后进行dfs搜索,输出前8个满足前缀的。

C:最大和子序列
看到题目我首先考虑DP,然后没有什么直观思路,转而考虑贪心。WA了一次,JMH师兄:贪心显然是错的,提供一种解决方案:

f1[i] 表示[1, i]的最大子序列和
f2[i] 表示[i, n]的最大子序列和
f3[i] 表示[1, i]的最小子序列和
f4[i] 表示[i, n]的最小子序列和
“然后你就会做了”

实践过程中,发现利用f3 f4计算跨n – 1的最大值的时候,会出现f3 f4覆盖掉所有的情况。具体怎么解决,我是分别判断f3、f4所对应区域的和是否等于f3、f4的值,如果等于不要。不知道这种方法是否完备。

第二场:

A:有“道”难题
水题,不过我把数组开小了WA了一次。有两种解决方案:
1. 把字符串读入,转换,然后统计
2. 逐个字符读入,把当前字符和前一个字符拼在一起,用二进制检查,统计。

B:有道饭团
彻底的水题,就是统计下哪个数大而已。具体实现的问题就是对姓名的编号和排序,对姓名的编号提供3种解决方案:
1. 预读入,处理,然后再处理的时候二分查找
2. HASH
3. STL里面的map,我中途去学这个了

C:有道招聘
明显的DP。我发现我也几乎只会做这样的DP了。用f(i, j)表示第i个Leader最后一个录用的人为j。记p[i]为Leader i前一位应聘者的编号。显然,有pos[i] <= j <= pos[i+1]。我开始还搞错了,以为状态总数是O(n^2),实际是O(n)。转移:
f(i, j) = sum{ f(i-1, k) | [k+1, j]的员工满足Leader i的需要 },转移时间为O(nm),总时间复杂度为O(n^2*m)
对于每个(i, j),决策范围k显然是连续的。并且对于每一个,记f(i, j)决策时最大的k为K(i, j)。对于每一个固定i,K(i, j)是关于j单调的。那么,转移可以优化,新的均摊时间复杂度为O(m),所以总时间复杂度为O(nm)。

第三场:

A:选课的困惑
Problem A终于不那么水了(其实是因为我WA了一次才那样说的 – -)。
具体做法就是,每次挑选一个未选的选修科目,并且选中的科目,是所有可挑选的科目当中,使得平均分最大的。这样不断重复,直到平均分达到90分,或者所有科目都选择后仍然不能使得平均分达到90分。

B:X星球的身份证系统
好像和以前做过某道题类似,我们以一个特定的数据分析:BCDEF
由于需要回文,所以只有前三位的决策是有意义的,进行具体分析。
1. 第1位:如果选择A,那么第2、3位都是可以随便选择的,总数为1*26^2。如果选择B,考虑第2点:
2. 第2位:如果选择A、B,那么第3为是可以随便选择的,总数为2*26^1。如果选择C,考虑第3点:
3. 第3位:如果选择A、B、C,那么总数为3*26^0。如果选择D,考虑第4点:
4. 此时,1、2、3位都已经选定,我们只需要判断这个特定的回文是否超过了给定数据。没有超过就往总数上加1,超过就无视。这样就解决了。

C:火柴游戏
这道题开始分析就往数学方面考虑。没有做出来,晚上很愤怒地把黑书带了回去看数论。结果早上回来,看到JMH师兄说了一句(我昨晚问过):不就是DP么?
然后还是还无思路,于是去翻竞赛系统的讨论区。终于翻到了一段话:

用d[i,j]表示用了i根火柴棍后组成的数mod m的结果是j的数的个数
状态转移过程是,在[0,10)内枚举k,则状态d[i,(j*10+k)%m]可以转移到状态d[i-stick[k]][j]。其中stick[k] 表示k这个数需要多少根火柴棍。

然后好像明白了,实现之。不过又发现问题了,为什么(j*10+k)%m可以由j转移而来呢?然后突然想到昨晚黑书中看到的:

如果a1≡a2(mod m),b1≡b2(mod m),那么a1+b1≡a2+b2(mod m)

好了,这样就没问题了。具体还有就是状态储存问题,直接定义f[maxn][maxm]肯定要爆掉的。可以定义*f[maxn],然后动态开new int [m]。那样就没问题了。

BOI 2007 The Sound of Silence

Posted in 国外竞赛 on 五月 21st, 2010 by 纳米 – 3 Comments

题目大意:
给定n个数,求出所有满足以下条件片段的起始位置:片段长度为M、片段内最大数和最小数的差小于给定的c。

解题报告:
这道题是《1D/1D动态规划优化初步》里面的例题,其实跟动态规划没啥关系,只是用到了一个单调队列优化。这个问题实质就是求每一个片段中最大值和最小值。因为求最小值和最大值本质是一样的,所以以最小值为例:
维护一个单调不下降的队列,每次遇到一个数,首先在队列头,若存在位置在这个数m个位置之前的元素,删掉。然后,在队列尾检查,若存在比该数大的元素,删掉。再在队列尾加入该数。这样,我们就得到了一个单调不下降序列。容易知道,队列头的元素就是以该数结尾的片段中最小的数字。
求出了每个片段中的最大值和最小值,问题就解决了。每个数只可能进、出一次队列,所以时间复杂度为O(n)。

我很恶心地使用了Class来实现这个代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#include <stdio.h>
 
const char IN_FILE_NAME[] = "sound.in";
const char OUT_FILE_NAME[] = "sound.out";
 
const int maxn = 1000005, maxm = 10005;
int n, m, c, data[maxn];
class Queue {
	private:
		int num[maxn], pos[maxn], head, last;
	public:
		Queue() {
			head = 1;
			last = 0;
		}
		bool Empty() {
			return head > last;
		}
		int GetFirstNum() {
			return num[head];
		}
		int GetFirstPos() {
			return pos[head];
		}
		int GetLastNum() {
			return num[last];
		}
		void DelFirst() {
			++head;
		}
		void DelLast() {
			--last;
		}
		void Insert(int n, int p) {
			num[++last] = n;
			pos[last] = p;
		}
} qmin, qmax;
bool answer[maxn];
 
int input() {
	FILE *fin = fopen(IN_FILE_NAME, "r");
	fscanf(fin, "%d%d%d", &n, &m, &c);
	for (int i = 1; i <= n; ++i)
		fscanf(fin, "%d", &data[i]);
	fclose(fin);
	return 0;
}
 
int work() {
	for (int i = 1; i < m; ++i) {
		while (!qmin.Empty() && qmin.GetLastNum() > data[i])
			qmin.DelLast();
		qmin.Insert(data[i], i);
		while (!qmax.Empty() && qmax.GetLastNum() < data[i])
			qmax.DelLast();
		qmax.Insert(data[i], i);
	}
	for (int i = m, j = 1; i <= n; ++i, ++j) {
		while (!qmin.Empty() && qmin.GetFirstPos() < j)
			qmin.DelFirst();
		while (!qmin.Empty() && qmin.GetLastNum() > data[i])
			qmin.DelLast();
		qmin.Insert(data[i], i);
		while (!qmax.Empty() && qmax.GetFirstPos() < j)
			qmax.DelFirst();
		while (!qmax.Empty() && qmax.GetLastNum() < data[i])
			qmax.DelLast();
		qmax.Insert(data[i], i);
		answer[j] = qmax.GetFirstNum() - qmin.GetFirstNum() <= c;
	}
	return 0;
}
 
int output() {
	FILE *fout = fopen(OUT_FILE_NAME, "w");
	bool is_ok = false;
	for (int i = 1; i <= n; ++i)
		if (answer[i]) {
			fprintf(fout, "%d\n", i);
			is_ok = true;
		}
	if (!is_ok)
		fprintf(fout, "NONE\n");
	fclose(fout);
	return 0;
}
 
int main() {
	input();
	work();
	output();
	return 0;
}

APIO 2010 特别行动队 (commando)

Posted in APIO on 五月 19th, 2010 by 纳米 – 1 Comment

解题报告:
解法一:
设f(x)为1 – x所得最大分数,c[x]为第x人分数,sum[x]为1-x总分。
有方程:f(x) = max{f(i) + w[i+1, x] | 0 <= i < x},其中w[i, j] = a*(sum[j] – sum[i-1])^2 + b*(sum[j] – sum[i-1]) + c。

首先证明w[i, j] + w[i+1, j+1] >= w[i+1, j] + w[i, j+1]:
(1) w[i, j+1] – w[i, j]
=a*(sum[j] – sum[i-1] + c[j+1])^2 + b*(sum[j] – sum[i-1] + c[j+1]) + c – a*(sum[j] – sum[i-1])^2 – b*(sum[j] – sum[i-1]) – c
=a*(sum[j] – sum[i-1])^2 + a*c[j+1]^2 + 2*a*(sum[j] – sum[i-1])*c[j+1] + b*(sum[j] – sum[i-1]) + b*c[j+1] + c – a*(sum[j] – sum[i-1])^2 – b*(sum[j] – sum[i-1]) – c
=a*c[j+1]^2 + b*c[j+1] + 2*a*(sum[j] – sum[i-1])*c[j+1]
因为a*c[j+1]^2 + b*c[j+1]为常量,sum[j] – sum[i-1]关于i单调递减,a<0,c[j+1]>0,所以上述结果关于i单调递增。
(2) 同理,w[i, j] – w[i+1, j]关于j单调递减。
综上,w[i, j] + w[i+1, j+1] >= w[i+1, j] + w[i, j+1]。

时间复杂度O(n log2 n),最后两个点本机(Intel Pentium E2140 1.6GHz)1.2s通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <stdio.h>
 
const char IN_FILE_NAME[] = "commando.in";
const char OUT_FILE_NAME[] = "commando.out";
 
const int maxn = 1000005;
int n, _a, _b, _c, data[maxn], stack[maxn][3], last;
long long a, b, c, sum[maxn], dp[maxn], answer;
 
int input() {
	FILE *fin = fopen(IN_FILE_NAME, "r");
	fscanf(fin, "%d%d%d%d", &n, &_a, &_b, &_c);
	for (int i = 1; i <= n; ++i)
		fscanf(fin, "%d", &data[i]);
	fclose(fin);
	return 0;
}
 
int work_init() {
	sum[0] = 0;
	for (int i = 1; i <= n; ++i)
		sum[i] = sum[i-1] + data[i];
	a = _a, b = _b, c = _c;
	return 0;
}
 
long long calc_dp(int i, int j) {
	return dp[j] + a * (sum[i] - sum[j]) * (sum[i] - sum[j]) + b * (sum[i] - sum[j]) + c;
}
 
int work_find(int t0, int t1, int w0, int w1) {
	int left = w0, right = w1, res = w1 + 1;
	while (left <= right) {
		int mid = (left + right) >> 1;
		if (mid > t0 && calc_dp(mid, t0) >= calc_dp(mid, t1)) {
			res = mid;
			right = mid - 1;
		} else {
			left = mid + 1;
		}
	}
	return res;
}
 
int work_dp() {
	dp[0] = last = 0;
	stack[0][0] = 0;
	stack[0][1] = 1;
	stack[0][2] = n;
	int pw = 0;
	for (int i = 1; i <= n; ++i) {
		if (pw > last)
			pw = last;
		while (i > stack[pw][2])
			++pw;
		dp[i] = calc_dp(i, stack[pw][0]);
		while (stack[last][2] > i && calc_dp(stack[last][1], i) >= calc_dp(stack[last][1], stack[last][0]))
			--last;
		int w = work_find(i, stack[last][0], stack[last][1], stack[last][2]);
		if (w <= n) {
			stack[last][2] = w - 1;
			++last;
			stack[last][0] = i;
			stack[last][1] = w;
			stack[last][2] = n;
		}
	}
	answer = dp[n];
	return 0;
}
 
int output() {
	FILE *fout = fopen(OUT_FILE_NAME, "w");
	fprintf(fout, "%lld\n", answer);
	fclose(fout);
	return 0;
}
 
int main() {
	input();
	work_init();
	work_dp();
	output();
	return 0;
}

解法二:
f(n) = max{f(i) + a*(sum[n] – sum[i])^2 + b*(sum[n] – sum[i]) + c | 0 <= i < x}
= max{f(i) + a*sum[n]^2 + b*sum[i]^2 – 2*a*sum[n]*sum[i] + b*sum[n] – b*sum[i] + c | 0 <= i < x}
= max{f(i) + b*sum[i]^2 – 2*a*sum[n]*sum[i] – b*sum[i] | 0 <= i < x} + a*sum[n]^2 + b*sum[n] + c
不妨设a(i) = -2*a*sum[n], x(i) = sum[i], y(i) = f(i) + b*sum[i]^2 – b*sum[i]
原式 = max{a(i)*x(i) + y(i) | 0 <= i < x} + a*sum[n]^2 + b*sum[n] + c x(i)是单调递减的,-a(i)也是单调递减的(考虑P = ax + y => y = -ax + P,最大化截距P)。显然,所有决策点都在凸包上。所以,维护一个栈,进行决策点储存。具体见论文《1D/1D动态规划优化初步》。
时间复杂度为O(n)。最后两个数据点本机500ms通过。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <stdio.h>
 
const char IN_FILE_NAME[] = "commando.in";
const char OUT_FILE_NAME[] = "commando.out";
 
const int maxn = 1000005;
int n, _a, _b, _c, data[maxn], stack[maxn], last;
long long a, b, c, sum[maxn], stackp[maxn][2], dp[maxn], answer;
 
int input() {
	FILE *fin = fopen(IN_FILE_NAME, "r");
	fscanf(fin, "%d%d%d%d", &n, &_a, &_b, &_c);
	for (int i = 1; i <= n; ++i)
		fscanf(fin, "%d", &data[i]);
	fclose(fin);
	return 0;
}
 
int work_init() {
	sum[0] = 0;
	for (int i = 1; i <= n; ++i)
		sum[i] = sum[i-1] + data[i];
	a = _a, b = _b, c = _c;
	return 0;
}
 
long long calc(int i, int j) {
	return dp[j] + a * (sum[i] - sum[j]) * (sum[i] - sum[j]) + b * (sum[i] - sum[j]) + c;
}
 
void stack_insert(int t, long long x, long long y) {
	stack[++last] = t;
	stackp[last][0] = x;
	stackp[last][1] = y;
}
 
bool insert_ok(long long x2, long long y2) {
	long long x0 = stackp[last-1][0], y0 = stackp[last-1][1];
	long long x1 = stackp[last][0], y1 = stackp[last][1];
	return (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0) < 0;
}
 
int work_dp() {
	dp[0] = 0;
	dp[1] = calc(1, 0);
	last = -1;
	stack_insert(0, sum[0], dp[0]+a*sum[0]*sum[0]-b*sum[0]);
	stack_insert(1, sum[1], dp[1]+a*sum[1]*sum[1]-b*sum[1]);
	int pw = 0;
	for (int i = 2; i <= n; ++i) {
		pw = pw < last ? pw : last;
		while (pw < last && calc(i, stack[pw+1]) >= calc(i, stack[pw]))
			++pw;
		dp[i] = calc(i, stack[pw]);
		if (i < n) {
			while (last > 0 && !insert_ok(sum[i], dp[i]+a*sum[i]*sum[i]-b*sum[i]))
				--last;
			stack_insert(i, sum[i], dp[i]+a*sum[i]*sum[i]-b*sum[i]);
		}
	}
	answer = dp[n];
	return 0;
}
 
int output() {
	FILE *fout = fopen(OUT_FILE_NAME, "w");
	fprintf(fout, "%lld\r\n", answer);
	fclose(fout);
	return 0;
}
 
int main() {
	input();
	work_init();
	work_dp();
	output();
	return 0;
}

USACO Monthly March, 2008, Gold, Land Acquisition(土地购买)

Posted in USACO on 五月 19th, 2010 by 纳米 – Be the first to comment

解题报告:
首先,对所有的(x, y)(长、宽)排序。
然后,去除所有(x, y),当且仅当存在(x’, y’),使得x’>=x && y’>=y。这一步在排序后可以在线性时间内完成。
设f(x)为购买1-x块土地所需最少的钱。x[i]、y[i]分别为第i块土地的长、宽。
有方程:f(x) = min{f(i) + w[i+1, x] | 0 <= i < x},其中,w[i, j] = x[i] * y[j]。
首先证明,w[i, j] + w[i+1, j+1] <= w[i+1, j] + w[i, j+1]:
(1) w[i, j+1] – w[i, j]
=x[i] * y[j+1] – x[i] * y[j]
=x[i] * (y[j+1]-y[j])
因为x[i]关于i单调递增,y[j+1]-y[j] < 0,所以上述结果关于i单调递减。
(2) w[i, j] – w[i+1,j]
=x[i]*y[j] – x[i+1]*y[j]
=y[j] * (x[i] – x[i+1])
因为y[j]关于j单调递减,x[i] – x[i+1] < 0,所以上述结果关于j单调递增。
综上,w[i, j] + w[i+1, j+1] <= w[i+1, j] + w[i, j+1]。
解法一时间复杂度为O(n log2 n)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#include <stdio.h>
#include <stdlib.h>
 
const char IN_FILE_NAME[] = "acquire.in";
const char OUT_FILE_NAME[] = "acquire.out";
 
const int maxn = 50005;
int n, data[maxn][2], stack[maxn][3], last;
long long dp[maxn], answer;
 
int input() {
	FILE *fin = fopen(IN_FILE_NAME, "r");
	fscanf(fin, "%d", &n);
	for (int i = 1; i <= n; ++i)
		fscanf(fin, "%d%d", &data[i][0], &data[i][1]);
	fclose(fin);
	return 0;
}
 
int comp(int x[], int y[]) {
	if (x[0]==y[0] && x[1]==y[1])
		return 0;
	else if (x[0]>y[0] || (x[0]==y[0] && x[1]>y[1]))
		return -1;
	else
		return 1;
}
 
int work_init() {
	qsort(data[1], n, sizeof(int) << 1, (int(*) (const void*, const void*)) comp);
	int n0 = 0, maxy = 0;
	for (int i = 1; i <= n; ++i)
		if (data[i][1] > maxy) {
			maxy = data[i][1];
			++n0;
			data[n0][0] = data[i][0];
			data[n0][1] = data[i][1];
		}
	n = n0;
	return 0;
}
 
long long calc_dp(int i, int j) {
	return dp[j] + (long long)data[j+1][0] * (long long)data[i][1];
}
 
int work_find(int t0, int t1, int w0, int w1) {
	int left = w0, right = w1, res = w1 + 1;
	while (left <= right) {
		int mid = (left + right) >> 1;
		if (mid > t0 && calc_dp(mid, t0) <= calc_dp(mid, t1)) {
			res = mid;
			right = mid - 1;
		} else {
			left = mid + 1;
		}
	}
	return res;
}
 
int work_dp() {
	dp[0] = last = 0;
	stack[0][0] = 0;
	stack[0][1] = 1;
	stack[0][2] = n;
	int pw = 0;
	for (int i = 1; i <= n; ++i) {
		if (pw > last)
			pw = last;
		while (i > stack[pw][2])
			++pw;
		dp[i] = calc_dp(i, stack[pw][0]);
		while (stack[last][1] > i && calc_dp(stack[last][1], i) <= calc_dp(stack[last][1], stack[last][0]))
			--last;
		int w = work_find(i, stack[last][0], stack[last][1], stack[last][2]);
		if (w <= n) {
			stack[last][2] = w - 1;
			++last;
			stack[last][0] = i;
			stack[last][1] = w;
			stack[last][2] = n;
		}
	}
	answer = dp[n];
	return 0;
}
 
int output() {
	FILE *fout = fopen(OUT_FILE_NAME, "w");
	fprintf(fout, "%lld\n", answer);
	fclose(fout);
	return 0;
}
 
int main() {
	input();
	work_init();
	work_dp();
	output();
	return 0;
}

解法二:
f(n) = min{f(i) + W[i+1] * L[n] | 0 <= i < n},W、L分别为第一、二关键字。
设x(i) = f(i), b(i) = L[n], y(i) = W[i+1],则:
f(n) = min{x(i) + b(i) * y(i) | 0 <= i < n} x(i)是单调递增的,-1/b(i)也是单调递增的(考虑P=x + by => y = (-1/b)x + P/b,最小化截距P/b)。显然,决策点都在凸包上,所以,可以维护一个栈维护凸包,然后用一个指针进行决策。时间复杂度为O(n)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <stdio.h>
#include <stdlib.h>
 
const char IN_FILE_NAME[] = "acquire.in";
const char OUT_FILE_NAME[] = "acquire.out";
 
const int maxn = 50005;
int n, data[maxn][2], stack[maxn], last;
long long dp[maxn], stackp[maxn][2], answer;
 
int input() {
	FILE *fin = fopen(IN_FILE_NAME, "r");
	fscanf(fin, "%d", &n);
	for (int i = 1; i <= n; ++i)
		fscanf(fin, "%d%d", &data[i][0], &data[i][1]);
	fclose(fin);
	return 0;
}
 
int comp(int x[], int y[]) {
	if (x[0]==y[0] && x[1]==y[1])
		return 0;
	else if (x[0]>y[0] || (x[0]==y[0] && x[1]>y[1]))
		return -1;
	else
		return 1;
}
 
int work_init() {
	qsort(data[1], n, sizeof(int) << 1, (int(*) (const void*, const void*)) comp);
	int n0 = 1;
	for (int i = 2; i <= n; ++i)
		if (data[i][1] > data[n0][1]) {
			++n0;
			data[n0][0] = data[i][0];
			data[n0][1] = data[i][1];
		}
	n = n0;
	return 0;
}
 
long long calc(int i, int j) {
	return dp[j] + (long long)data[j+1][0] * (long long)data[i][1];
}
 
void stack_insert(int t, long long x, long long y) {
	stack[++last] = t;
	stackp[last][0] = x;
	stackp[last][1] = y;
}
 
bool insert_ok(long long x2, long long y2) {
	long long x0 = stackp[last-1][0], y0 = stackp[last-1][1];
	long long x1 = stackp[last][0], y1 = stackp[last][1];
	return (x1-x0)*(y2-y0)-(x2-x0)*(y1-y0) > 0;
}
 
int work_dp() {
	dp[0] = 0;
	dp[1] = calc(1, 0);
	last = -1;
	stack_insert(0, dp[0], data[1][0]);
	stack_insert(1, dp[1], data[2][0]);
	int pw = 0;
	for (int i = 2; i <= n; ++i) {
		pw = pw < last ? pw : last;
		while (pw < last && calc(i, stack[pw+1]) <= calc(i, stack[pw]))
			++pw;
		dp[i] = calc(i, stack[pw]);
		if (i < n) {
			while (last > 0 && !insert_ok(dp[i], data[i+1][0]))
				--last;
			stack_insert(i, dp[i], data[i+1][0]);
		}
	}
	answer = dp[n];
	return 0;
}
 
int output() {
	FILE *fout = fopen(OUT_FILE_NAME, "w");
	fprintf(fout, "%lld\n", answer);
	fclose(fout);
	return 0;
}
 
int main() {
	input();
	work_init();
	work_dp();
	output();
	return 0;
}

HNOI 2008 玩具装箱

Posted in 各省省选 on 五月 19th, 2010 by 纳米 – Be the first to comment

解题报告:
设f(x)为把玩具1至玩具x装箱好的费用,c[x]为玩具x压缩后的长度,有方程:
f(x) = min{f(i) + w[i+1, x] | 0 <= i < x},其中,w[i, j] = (j-i + sum{c[k] | i <= k <= j} – L)^2。
首先证明,w[i, j] + w[i+1, j+1] <= w[i+1, j] + w[i, j+1]:
假定j为常量, i为变量:
w[i, j+1] – w[i, j]
=(j+1-i + sum{c[k] | i <= k <= j}+c[j+1] – L)^2 – (j-i + sum{c[k] | i <= k <= j} – L)^2
=((j-i+sum{c[k] | i <= k <= j}-L) + (c[j+1]+1))^2 – (j-i+sum{c[k] | i <= k <= j}-L)^2
=2(j-i+sum{c[k] | i <= k <= j}-L)(c[j+1]+1)+(c[j+1]+1)^2
容易知道,上述结果关于i单调递减。
同理,w[i, j] – w[i+1, j]关于j单调递增。
所以,w满足w[i, j] + w[i+1, j+1] <= w[i+1, j] + w[i, j+1]

注意,因为我没有数据,此代码并不确保AC。严格来说是一定不能AC的,因为我没有写高精度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#include <stdio.h>
 
const int maxn = 50005;
int n, L, data[maxn], stack[maxn][3], last;
long long sum[maxn], dp[maxn], answer;
 
int input() {
	scanf("%d%d", &n, &L);
	for (int i = 1; i <= n; ++i)
		scanf("%d", &data[i]);
	return 0;
}
 
int work_init() {
	sum[0] = 0;
	for (int i = 1; i <= n; ++i)
		sum[i] = sum[i-1] + data[i];
	return 0;
}
 
long long calc_dp(int i, int j) {
	return dp[j] + (i-j-1 + sum[i]-sum[j] - L) * (i-j-1 + sum[i]-sum[j] - L);
}
 
int work_find(int t0, int t1, int w0, int w1) {
	int left = w0, right = w1, res = w1 + 1;
	while (left <= right) {
		int mid = (left + right) >> 1;
		if (mid > t0 && calc_dp(mid, t0) <= calc_dp(mid, t1)) {
			res = mid;
			right = mid - 1;
		} else {
			left = mid + 1;
		}
	}
	return res;
}
 
int work_dp() {
	dp[0] = last = 0;
	stack[0][0] = 0;
	stack[0][1] = 1;
	stack[0][2] = n;
	int pw = 0;
	for (int i = 1; i <= n; ++i) {
		if (pw > last)
			pw = last;
		while (i > stack[pw][2])
			++pw;
		dp[i] = calc_dp(i, stack[pw][0]);
		while (stack[last][1] > i && calc_dp(stack[last][1], i) <= calc_dp(stack[last][1], stack[last][0]))
			--last;
		int w = work_find(i, stack[last][0], stack[last][1], stack[last][2]);
		if (w <= n) {
			stack[last][2] = w - 1;
			++last;
			stack[last][0] = i;
			stack[last][1] = w;
			stack[last][2] = n;
		}
	}
	answer = dp[n];
	return 0;
}
 
int output() {
	printf("%lld\n", answer);
	return 0;
}
 
int main() {
	input();
	work_init();
	work_dp();
	output();
	return 0;
}

1D/1D动态规划优化

Posted in 知识讲解 on 五月 19th, 2010 by 纳米 – Be the first to comment

具体请看论文《1D/1D动态规划初步》,本文只是链接和学习笔记。

2010-05-18,学习模型一。

2010-05-19,学习,分析玩具装箱(例题1, 湖南省选2008)、土地购买(例题2, USACO Monthly, March, 2008, Gold)、特别行动队(APIO2010),证明其w函数性质,Code,测试。