Archive for November 28th, 2010

NOIP2010 解题报告


28 Nov

闲话不说。

第一题:

水题一道,怎麽做都可以,当然我觉得最方便最快捷的就是用队列模拟了。

因为0 < N <= 1000,0 < M <= 100 所以直接读数,读进来以后看看是不是在队列中,若不在就把它加入其中并把答案加一。并且随时保持队列长度不超过M(一旦加入元素后队长超过M就将头指针右移)

第二题:

注意乌龟棋子只有1-4四种所以很容易想到四维状态的DP,即用f[a][b][c][d]表示用去 a张1 , b张2 , c张3 , d张4 所能得到最大分数(不包含起始位置的分数)。很明显这是满足最优子问题的。我们还发现,无论卡片的使用顺序如何,最终用完abcd后,乌龟会停在a + b * 2 + c * 3 + d *4 + 1 这个位置上。也就是说f[a][b][c][d] 能唯一确定乌龟停在位置,这个状态是完整的。

所以转移方程是:

f[a][b][c][d] =  max{f[a - 1][b][c][d] (a > 0),

f[a][b - 1][c][d] (b > 0),

f[a][b][c - 1][d] (c > 0),

f[a][b][c][d - 1] (d > 0)} + Score[a + b * 2 + c * 3 + d * 4 + 1]

最后答案为:f[C[1]][C[2]][C[3]][C[4]] + Score[1]

(Score[i] 表示 i 位置上的分数 C[i]表示卡片i有多少张)

第三题:

题意是说把点分成两个集合,然后使得两个集合各自内部的最大边尽量小

因为是跟最大边直接的关系,我们可以想到,如果整个图中的最大边不被“架在”两个集合之间被删掉的话,最大值就一定是它了。答案要求最大边尽量小,因此它的两个端点必须在两个不同的集合中。这样才能保证最大值至多不是它的值那么大。那么依此类推,我再找剩下边中的最大边,然后把两端结点放在不同集合中。但是这样下去可能出现矛盾,例如1-4,2-4 , 1-2。1和4不在一个集合中,2和4也不在一个集合中,那么1,2一定在同一个集合中,于是和1-2产生矛盾。

我们是先处理1-4,2-4,它们边权都比1-2大,所以此时要保证逻辑正常,必须舍弃1-2边,不删除1-2,把1-2放在同集合中。假设我们偏要采用1-2,即删去1-2边,那么1-4,2-4中必须放弃某一条,无论放弃哪一条都会导致答案更差(答案直接变成我们放弃删除的那条边权值)。

那么如何检测这种矛盾呢?例如我们开始可能并不知道1-2,3-4,1和3是否在一个集合,直到出现1-3 或者 1-4 ,才能得以确定。

我们可以先选择逃避。

反过来想,如果给定一个答案Ans,我们可以知道至少要删除>= Ans的所有边,于是可以看看这些被删的边是否能够组成一个二分图,如果能,则Ans是可以办到的。所以我们不妨进行二分,二分那个答案,如果不能成立,就向大于那个答案的方向继续二分,反之向更小的方向二分。(先排序再二分,注意答案为0的情况!)于是我们得到了一个O(MlogM + MlogM) -> O(MlogM)的算法。

鲁迅曾说:“真的勇士,敢于直面惨淡的人生,敢于正视淋漓的鲜血”。从正面思考,我们可以采用并查集(类似NOI2001食物链)的方法来处理。

令a -> b 表示a到b的关系,0表示a,b同属于一个题中的集合,1表示a,b不属于同一集合。如果用基于模2的运算的话

该关系恰似向量的运算法则:

a ->  c = a -> b + b -> c (mod 2)

a -> b = – (b -> a)  (mod 2)

我们令Relate[a] 表示a到a的父亲的关系

那么我们就可以维护一个关系森林 (其实就是并查集)

对于接受一个新关系R的树的合并:

father(x) = S1,father(y) = S2

将S1并到S2上去

那么Relate[S1] = (Relate[y]  - Relate[x] + R + 2) mod 2

因增加关系而合并树

于是我们可以通过查询两个点到公共祖先各自的Relate来计算它们相对的关系,如果还没有公共祖先说明他们还没有确定关系,通过“并”来添加新关系

复杂度为O(MlogM + M * alpha(M)),可以认为是 O(MlogM), 其实要比二分法更理想,因为二分法判断二分图时常数较大(尤其是清空数组的时间代价较大)

至此问题有了令人满意的解决方案

第四题:

题目大意是说,在第一行选择一些点,然后从这些点放水从高到低流,能把最后一行流满。

对问题进行简化。我们考察每个(1,i) 看能流到哪些 (n,j),并建立一个二分图,如样例:

问题的转化示意

题目转化为在一个二分图的“上边”选择一些点,然后用这些点的所有边把另一边的点全部盖住。

如果一个点它连的对面的点是连续的,也许就可以直接DP了,但是可能出现如下情况:

3 5 3

2 6 2

1 1 1

(从5出发,中间的1无法流到)

似乎这个问题非裸搜或者状态压缩DP不能解决(像是NP的),但是肯定是不现实的。

这里就陷入了一个分析的僵局(我临场的时候也是卡在这儿了,最后选择裸搜)。

如果我们再仔细分析一下那些导致不连续的反例,我们会惊奇地发现,那些虽然存在,但是他们都是属于题中的无法覆盖完的情况!

我们猜想:一个能按规则覆盖完的二分图每个“上边”的点对应相连的点一定是连续的区域。

证明:反证,如果存在不连续的区域,也就是出现空缺,那么空缺的必然至少要其他点的边能够到,那么在原地图中从那个旁边点过来的路线一定会和原点的某路径交叉,也就是说从原点是可以流到那个空缺处的,与假设矛盾。

连续性的证明

于是此题就转化为求每个“上边”点在“下边”的管辖区域,然后选择尽量少的区域将整个1..m覆盖,可以DP:

f[i]表示1..i覆盖完需要的最少区域数

因此有f[i] = min(f[st[j] – 1]) + 1  (1 <= j <= m 且st[j] <= i <= ed[j])

预处理每个(1,i)流到的点(n,j)时如果直接裸搜 需要O(N * M ^ 2) 难以接受

因为只需要知道最左端和最右端,可以分两次来计算。

从(n,j)取反推上游,逆流触及的(1,i)的st值就为j 因为j单增,所以如果在某次反推时碰到已经走过的结点,没有必要继续沿老路了,因为这样带给(1,i)的st要比目前的st大

类似地可以算出ed[i]。

至此,问题解决,时间复杂度为O(M(N + M))

Code:

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
#include <cstdio>
#include <cstring>
#include <cstdlib>
 
#define MAXQ 2000
 
int Que[MAXQ],N,M,Ans;
 
int main() {
 
	freopen("translate.in","r",stdin);
	freopen("translate.out","w",stdout);
 
	scanf("%d %d",&M,&N);
 
	for (int l = 0,r = -1,i = 1,tmp; i <= N; i++)
	{
		scanf("%d",&tmp);
		bool succ = 0;
		for (int t = l; t <= r; t++)
			if (Que[t] == tmp) {succ = 1; break;}
		if (!succ) {Ans++; Que[++r] = tmp;}
		if (r - l >= M) l++;
	}
	printf("%d\n",Ans);
	return 0;
}
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
//并查集
#include <cstdio>
#include <cstring>
#include <cstdlib>
 
#define MAXN 20001
#define MAXM 100001
 
struct Edge {
 
	int st,ed,c;
} Edges[MAXM];
 
int cmp(const void *a,const void *b) {
 
	return ((Edge*)b) -> c - ((Edge*)a) -> c;
}
 
int F[MAXN],Rank[MAXN],Relate[MAXN],N,M;
 
int Father(int x) {
 
	int Res = x,tmp,tmp2,R = 0;
	while (F[Res] != Res) {R += Relate[Res]; Res = F[Res];}
	while (x != Res)
	{
		tmp = F[x]; tmp2 = Relate[x];
		F[x] = Res; Relate[x] = R &= 1;
		x = tmp; R = R - tmp2 + 2;
	}
	return Res;
}
 
void Union(int x,int y,int R) {
 
	int S1 = Father(x),S2 = Father(y);
	R = (Relate[y] - Relate[x] + R + 2) & 1;
	if (Rank[S1] < Rank[S2]) {Relate[S1] = R; F[S1] = S2;}
	else
	{
		Relate[S2] = (2 - R) & 1;
		F[S2] = S1;
		if (Rank[S1] == Rank[S2]) Rank[S1]++;
	}
}
 
int Ans = 0;
 
int main() {
 
	freopen("prison.in","r",stdin);
	freopen("prison.out","w",stdout);
 
	scanf("%d %d",&N,&M);
	for (int i = 1; i <= M; i++)
	{
		Edge &Now = Edges[i];
		scanf("%d %d %d",&Now.st,&Now.ed,&Now.c);
	}
	qsort(Edges + 1,M,sizeof(Edge),cmp);
	for (int i = 1; i <= N; i++) F[i] = i;
	for (int i = 1; i <= M; i++)
	{
		Edge &Now = Edges[i];
		int f1 = Father(Now.st),f2 = Father(Now.ed);
		if (f1 == f2)
		{
			if (!((Relate[Now.st] - Relate[Now.ed] + 2) & 1) && Now.c > Ans) Ans = Now.c;
		}
		else
			Union(Now.st,Now.ed,1);
	}
	printf("%d\n",Ans);
	return 0;
}
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
//二分
#include <cstdio>
#include <cstring>
#include <cstdlib>
 
#define MAXN 20001
#define MAXM 200001
 
struct Edge {
 
	int st,ed,c,Id1,Id2;
} Edges[MAXM];
 
int fir[MAXN],next[MAXM],end[MAXM],ECnt,col[MAXN],N,M;
bool conn[MAXM];
 
int AddEdge(int st,int ed) {
 
	next[++ECnt] = fir[st];
	fir[st] = ECnt;
	end[ECnt] = ed;
	return ECnt;
}
 
int cmp(const void *a,const void *b) {
 
	return ((Edge*)a) -> c - ((Edge*)b) -> c;
}
 
bool succ;
 
void check(int pos,bool c) {
 
	if (col[pos] != -1)
	{
		if (col[pos] != c) succ = 0;
		return;
	}
	col[pos] = c;
 
	for (int now = fir[pos]; now; now = next[now])
		if (conn[now]) check(end[now],c ^ 1);
}
 
 
int main() {
 
	freopen("prison.in","r",stdin);
	freopen("prison.out","w",stdout);
 
	scanf("%d %d",&N,&M);
	for (int i = 1; i <= M; i++)
	{
		Edge &Now = Edges[i];
		scanf("%d %d %d",&Now.st,&Now.ed,&Now.c);
		Now.Id1 = AddEdge(Now.st,Now.ed);
		Now.Id2 = AddEdge(Now.ed,Now.st);
	}
	qsort(Edges + 1,M,sizeof(Edge),cmp);
 
	int l,r;
	for (l = -1,r = M; r - l > 1;)
	{
		int Mid = (l + r) >> 1;
		memset(col,-1,sizeof(col));
		for (int i = Mid + 1; i <= M; i++) 
			conn[Edges[i].Id1] = conn[Edges[i].Id2] = 1;
		succ = 1;
		for (int i = Mid + 1; i <= M; i++) if (col[Edges[i].st] == -1) check(Edges[i].st,0);
		if (succ) r = Mid; else l = Mid;
		for (int i = Mid + 1; i <= M; i++) 
			conn[Edges[i].Id1] = conn[Edges[i].Id2] = 0;
	}
	printf("%d\n",Edges[r].c);
	return 0;
}
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
#include <cstdio>
#include <cstdlib>
#include <cstring>
 
#define MAXN 600
#define MAXM 600
 
int N,M,H[MAXN][MAXM],dp[MAXM],Ans0;
bool vis[MAXN][MAXM];
 
struct Seg {
 
	int st,ed;
} Range[MAXM];
 
const int Det[4][2] = {{-1,0},{0,-1},{0,1},{1,0}};
 
#define IN_RANGE(x,y) (1 <= (x) && (x) <= N && 1 <= (y) && (y) <= M)
 
void Fill(int x,int y) {
 
	if (vis[x][y]) return;
	vis[x][y] = 1;
	if (x == N) Ans0++;
	for (int d = 0; d < 4; d++)
	{
		int nx = x + Det[d][0],ny = y + Det[d][1];
		if (IN_RANGE(nx,ny) && H[nx][ny] < H[x][y])
			Fill(nx,ny);
	}
} // check if it's possible to cover all
 
void Mark(int x,int y,int src,bool Type) {
 
	if (vis[x][y]) return;
	vis[x][y] = 1;
	if (x == 1) (Type ? Range[y].ed : Range[y].st) = src;
	for (int d = 0; d < 4; d++)
	{
		int nx = x + Det[d][0],ny = y + Det[d][1];
		if (IN_RANGE(nx,ny) && H[nx][ny] > H[x][y])
			Mark(nx,ny,src,Type);
	}
}
 
int main() {
 
	freopen("flow.in","r",stdin);
	freopen("flow.out","w",stdout);
 
	scanf("%d %d",&N,&M);
	for (int i = 1; i <= N; i++)
		for (int j = 1; j <= M; j++)
			scanf("%d",&H[i][j]);
 
	for (int i = 1; i <= M; i++) 
		Fill(1,i);
 
	if (Ans0 < M) printf("0\n%d\n",M - Ans0);
	else
	{
		memset(vis,0,sizeof(vis)); for (int i = 1; i <= M; i++) Mark(N,i,i,0);
		memset(vis,0,sizeof(vis)); for (int i = M; i >= 1; i--) Mark(N,i,i,1);
		memset(dp,127,sizeof(dp));
		dp[0] = 0;
		for (int i = 1; i <= M; i++)
			for (int j = Range[i].st,tmp; j <= Range[i].ed; j++)
				if ((tmp = dp[Range[i].st - 1] + 1) < dp[j]) dp[j] = tmp;
		printf("1\n%d\n",dp[M]);
	}
	return 0;
}

NOIP Summary


28 Nov

一年一度的NOIP又结束了,我还清楚地记得NOIP2009考试时的那些情形。我坐在靠后的一个位置上,被第二题所纠结,最后在不解和遗憾中,以仅做了第一题而告终。无巧不成书,NOIP2010,我依然在快速敲完第一题后又被第二题卡住,最后在无奈之下裸搜了2,3,4题,只得到了裸搜的理论最大值——210分(100 + 30 + 30 + 50)。

从表面上来分析,乌龟棋那道题是因为我过于紧张,读着读着就把条件——“走的步数只可能有1-4”给忘了,实际上要深究紧张的原因,跟自己给自己潜在的心理负担有关。因为我总想着放弃高考,逃避它,所以就无形之中给自己了一个“得不到一等就一切都完了”的强烈暗示,在这种暗示下,以前考试的教训和经验,例如“NOIP大部分题的方法不会太难”,在考场早已忘得九霄云外。这一点就是极大的失败。尤其是当做第二题做不动时,立刻觉得这套题比较难;等浏览到第三题(乍看不简单)便把这种心理放大到了极致。所以无论是什么考试首先要抛开一切,放开去考。无论考试或易或难,或小型或重大,都认真然而又不过分在意地去做,按照道家的“无为而无不为”,定会发挥出来。

这次虽然完全没有发挥出自己的水平,但是还是有些地方是值得肯定的,例如裸搜的正确性。看到能拿多少分就裸到多少分,这种能力在以前我是完全不具备的,从这一点可以看出进步。

今后的打算是先等到分数线,然后做决定。

目前的计划是:选择性地停课(根据和张老还有班主任的协商结果),准备4月的省选。计划把各省的省选题都做一下,尽管是个不小的挑战,但是我相信我能完成,这关键的一拼。另外再夹杂些较为简单的题目(从POJ 或者MIPT上选),巩固基础和增强代码准确率。

Teddy

Studies,OI and Love