Problem A: How Big Are the Pockets?
这题的意思是要求一个闭合的多边形外,有多少点的上和下或者左和右都有多边形的边界。
假设我们一行一行地来扫描这个多边形,我们从最左边开始,这时候我们的点在多边形外。
当我们第一次穿过多边形的一个边界的时候,很明显,我们就进入了多边形的内部,当再次遇到一条边界的时候我们又在多边形的外部了。通过这样的方法,我们可以发现当我们把某一行的边界按照坐标排序后第偶数条边界同第奇数条边界之间的所有点都是在多边形外部的,并且他们有左右两个边界。对于竖直方向上我们也可以用类似的方法处理。
Code
1
/**//*
2
GCJ'08 Problem A
3
Author Nicholas
4
*/
5
6
#include <iostream>
7
#include <set>
8
#include <vector>
9
#include <algorithm>
10
11
12
13
14
using namespace std;
15
16
const int dir[4][2] =
{
{1,0},
{0,1},
{-1,0},
{0,-1}};
17
18
vector<int> ver[10000], hor[10000];
19
20
21
int main()
22

{
23
freopen("in.txt","r", stdin);
24
freopen("out.txt", "w", stdout);
25
int T;
26
scanf("%d", &T);
27
28
for (int ctr = 1; ctr <= T; ctr++)
29
{
30
int L;
31
scanf("%d", &L);
32
int x = 5000, y = 5000, nd = 0;
33
34
for (int i = 0; i < 10000; i++)
35
ver[i].clear(), hor[i].clear();
36
37
for (int i = 0; i < L; i++)
38
{
39
char s[1000];
40
int t;
41
scanf("%s%d", s, &t);
42
for (int rep = 0; rep < t; rep++)
43
{
44
for (int j = 0; j < strlen(s); j++)
45
{
46
47
int nx, ny;
48
switch (s[j])
49
{
50
case 'R' : nd = (nd + 1) & 3; break;
51
case 'L' : nd = (nd + 3) & 3; break;
52
case 'F' :
53
nx = x + dir[nd][0], ny = y + dir[nd][1];
54
if (y != ny)
55
hor[min(y, ny)].push_back(x);
56
else
57
ver[min(x, nx)].push_back(y);
58
x = nx, y = ny;
59
break;
60
}
61
62
}
63
64
}
65
}
66
67
set<int> pockets[10000];
68
69
for (int i = 0; i < 10000; i++)
70
sort(ver[i].begin(), ver[i].end()), sort(hor[i].begin(), hor[i].end());
71
72
73
for (int x = 0; x < 10000; x++)
74
for (int j = 1; j + 1 < ver[x].size(); j+=2)
75
{
76
int y1 = ver[x][j], y2 = ver[x][j+1];
77
for (int y = y1; y < y2; y++)
78
pockets[x].insert(y);
79
}
80
81
82
83
for (int y = 0; y < 10000; y++)
84
for (int j = 1; j + 1 < hor[y].size(); j+=2)
85
{
86
int x1 = hor[y][j], x2 = hor[y][j+1];
87
88
for (int x = x1; x < x2; x++)
89
pockets[x].insert(y);
90
}
91
92
int ret = 0;
93
for (int i = 0; i < 10000; i++)
94
ret += pockets[i].size();
95
96
printf("Case #%d: %d\n", ctr, ret);
97
}
98
99
return 0;
100
}
101
Probles B: Portal
题目的意思就是在一个迷宫内从一个点以最短的步数走到另一个点,不过我们可以使用传送门。
基本的方法是BFS。关键在于怎么记录状态,起初我是想记录下当前人所在的坐标以及两个门的坐标以及门的方向,这样需要 17^6×8的空间,显然这个方法不太好。在仔细想想,我们发现第一枪是可以随便打的,可是当我们打出第二枪的时候,我们就一定会朝着第二枪所打的方向一直走过去,并穿过那个传送门,应为如果不这样走的话,那么,那个第二枪打的是没有意义的。进一步思考,我们还会发现,第二枪一定可以是贴着墙打的,不是吗?
这样我们我们就需要记录到某个点坐标,以及到该点时所有可能有门的位置。然后当我们走到一面墙的时候,就可能到其他所有有门的位置了。
Code
1
/**//*
2
GCJ'08 Problem B
3
Author Nicholas
4
*/
5
#include <iostream>
6
#include <string>
7
#include <queue>
8
9
using namespace std;
10
11
const int dir[4][2] =
{
{-1,0},
{0, 1},
{1,0},
{0, -1}};
12
13
struct state
14

{
15
int x,y;
16
short int p[16];
17
};
18
19
struct pos
20

{
21
int x, y;
22
};
23
24
char G[32][32];
25
26
pos nw[32][32][4];
27
int dp[32][32];
28
29
30
31
int main()
32

{
33
int T;
34
freopen("in.txt", "r", stdin);
35
freopen("out.txt", "w", stdout);
36
scanf("%d", &T);
37
for (int ctr = 1; ctr <= T; ctr++)
38
{
39
int R, C;
40
memset(dp, 63, sizeof(dp));
41
42
scanf("%d%d", &R, &C);
43
for (int i = 0; i <= C +1; i++)
44
{
45
G[0][i] = G[R+1][i] = '#';
46
}
47
48
for (int i = 1; i <= R; i++)
49
{
50
G[i][0] = '#';
51
scanf("%s", G[i]+1);
52
G[i][C+1] = '#';
53
}
54
queue<state> Q;
55
state start;
56
bool flag = true;
57
for (int i = 1; i <= R && flag; i++)
58
for (int j = 1; j <= C; j++)
59
if (G[i][j] == 'O')
60
{
61
start.x = i;
62
start.y = j;
63
memset(start.p, 0, sizeof(start.p));
64
flag = false;
65
break;
66
}
67
Q.push(start);
68
dp[ start.x ][ start.y ] = 0;
69
70
for (int i = 1; i <= R; i++)
71
for (int j = 1; j <= C; j++)
72
if (G[i][j] != '#')
73
{
74
for (int d = 0; d < 4; d++)
75
{
76
nw[i][j][d].x = i + dir[d][0];
77
nw[i][j][d].y = j + dir[d][1];
78
while ( G[ nw[i][j][d].x ][ nw[i][j][d].y ] != '#' )
79
nw[i][j][d].x += dir[d][0], nw[i][j][d].y += dir[d][1];
80
nw[i][j][d].x -= dir[d][0];
81
nw[i][j][d].y -= dir[d][1];
82
}
83
84
}
85
while (!Q.empty())
86
{
87
state cs = Q.front();
88
Q.pop();
89
int &x = cs.x, &y = cs.y;
90
if (G[x][y] == 'X') break;
91
short int *p = cs.p;
92
for (int d = 0; d < 4; d++)
93
{
94
int wx = nw[x][y][d].x;
95
int wy = nw[x][y][d].y;
96
if ( ! ((1<<wy)&p[wx]) )
97
{
98
p[wx] |= (1<<wy);
99
}
100
}
101
for (int d = 0; d < 4; d++)
102
{
103
int nx = x + dir[d][0];
104
int ny = y + dir[d][1];
105
if (G[nx][ny] != '#')
106
{
107
if (dp[nx][ny] > dp[x][y] + 1)
108
{
109
state temp = cs;
110
temp.x = nx;
111
temp.y = ny;
112
dp[nx][ny] = dp[x][y] + 1;
113
Q.push(temp);
114
}
115
}
116
else
117
{
118
for (int i = 1; i <= R; i++)
119
for (int j = 1; j <= C; j++)
120
if ( ((1<<j)&p[i]) && dp[i][j] > dp[x][y] + 1)
121
{
122
state temp = cs;
123
temp.x = i, temp.y = j;
124
dp[i][j] = dp[x][y] + 1;
125
Q.push(temp);
126
}
127
}
128
}
129
130
131
}
132
133
int ex = 0, ey;
134
for (int i = 1; i <= R && !ex; i++)
135
for (int j = 1; j <= C; j++)
136
if (G[i][j] == 'X')
137
{
138
ex = i, ey = j;
139
break;
140
}
141
142
printf("Case #%d: ", ctr);
143
if (dp[ex][ey] < 1000000) printf("%d\n", dp[ex][ey]);
144
else
145
printf("THE CAKE IS A LIE\n");
146
}
147
return 0;
148
}
Problem C: No Cheating
这题是很经典的一个问题,比赛的时候我竟然还不知道。
将这个图上的点染成黑白两色,奇数列点染成黑色,偶数列点染成白色。所有冲突的点之间连一条边,我们发现只有黑色点和白色点之间有边。于是问题就转化成了求一个二分图的最大独立集。类似的匹配问题有很多,如PKU 3020 (最少边点覆盖), PKU 3041(最少顶点覆盖)。
我的代码忘了存了,这是bmerry的
Code
1
#include <string>
2
#include <vector>
3
#include <map>
4
#include <cstdlib>
5
#include <cstring>
6
#include <cassert>
7
#include <set>
8
#include <iostream>
9
#include <sstream>
10
#include <cstddef>
11
#include <algorithm>
12
#include <utility>
13
#include <iterator>
14
#include <numeric>
15
#include <list>
16
#include <complex>
17
#include <cstdio>
18
19
using namespace std;
20
21
typedef vector<int> vi;
22
typedef vector<string> vs;
23
typedef long long ll;
24
typedef complex<double> pnt;
25
typedef pair<int, int> pii;
26
27
#define RA(x) (x).begin(), (x).end()
28
#define FE(i, x) for (typeof((x).begin()) i = (x).begin(); i != (x).end(); i++)
29
#define SZ(x) ((int) (x).size())
30
31
template<class T>
32
void splitstr(const string &s, vector<T> &out)
33

{
34
istringstream in(s);
35
out.clear();
36
copy(istream_iterator<T>(in), istream_iterator<T>(), back_inserter(out));
37
}
38
39
template<class T> T gcd(T a, T b)
{ return b ? gcd(b, a % b) : a; }
40
41
static bool done[10000];
42
static int back[10000];
43
static vector<int> edges[10000];
44
45
static bool augment(int x)
46

{
47
if (x == -1)
48
return true;
49
else if (done[x])
50
return false;
51
done[x] = true;
52
53
for (int i = 0; i < SZ(edges[x]); i++)
54
{
55
int y = edges[x][i];
56
int old = back[y];
57
back[y] = x;
58
if (augment(old))
59
return true;
60
back[y] = old;
61
}
62
return false;
63
}
64
65
int main()
66

{
67
int cases;
68
cin >> cases;
69
for (int cas = 0; cas < cases; cas++)
70
{
71
int R, C;
72
int id[100][100];
73
int pool[2] =
{0, 0};
74
cin >> R >> C;
75
for (int i = 0; i < R; i++)
76
{
77
string l;
78
cin >> l;
79
for (int j = 0; j < C; j++)
80
{
81
if (l[j] == '.')
82
id[i][j] = pool[j & 1]++;
83
else
84
id[i][j] = -1;
85
}
86
}
87
for (int i = 0; i < 10000; i++)
88
edges[i].clear();
89
for (int i = 0; i < R; i++)
90
for (int j = 0; j < C; j += 2)
91
if (id[i][j] != -1)
92
{
93
int x = id[i][j];
94
if (j > 0 && id[i][j - 1] != -1)
95
edges[x].push_back(id[i][j - 1]);
96
if (j < C - 1 && id[i][j + 1] != -1)
97
edges[x].push_back(id[i][j + 1]);
98
if (i > 0)
99
{
100
if (j > 0 && id[i - 1][j - 1] != -1)
101
edges[x].push_back(id[i - 1][j - 1]);
102
if (j < C - 1 && id[i - 1][j + 1] != -1)
103
edges[x].push_back(id[i - 1][j + 1]);
104
}
105
if (i < R - 1)
106
{
107
if (j > 0 && id[i + 1][j - 1] != -1)
108
edges[x].push_back(id[i + 1][j - 1]);
109
if (j < C - 1 && id[i + 1][j + 1] != -1)
110
edges[x].push_back(id[i + 1][j + 1]);
111
}
112
}
113
114
memset(back, -1, sizeof(back));
115
int ans = pool[0] + pool[1];
116
for (int i = 0; i < pool[0]; i++)
117
{
118
memset(done, 0, sizeof(done));
119
if (augment(i))
120
ans--;
121
}
122
printf("Case #%d: %d\n", cas + 1, ans);
123
}
124
return 0;
125
}
126
Problem D: Endless Knight
题目是一个马,走日字行,不能回头,问从一个棋盘的左上角走到右下角有多少种方法,另外棋盘中还有R(R <= 10)个点是不能走得。
首先我们考虑没有不能走的点的情况,从(1,1)走到(H,W),总共需要走的步数是 Step = (H-1 + W-1) / 3,当然如果不能整除的话,就无法走到终点。如果能走到的话方案总数就是 C[step][H-1-step] (C[i][j] 为组合数)。然后我们要做的就是去除掉这么多方案中所有经过了R中1个或多个点的情况,这里我需要用到容斥原理,具体方法见代码。
最后还有个需要解决的问题就是如何计算 C[m][n] % p,m,n的范围达到10^8。
公式是 C[m][n] % p = C[m/p][n/p] * C[m%p][n%p] % p。别人告诉我的, 有谁知道怎么推导的麻烦告诉我一下子。
Code

/**//*
GCJ'08 Problem D
Author Nicholas
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <cstdlib>
#include <sstream>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

const double PI = acos(-1.0);
const int MAXINT = 0x7FFFFFFF;
typedef long long LL;



const int MOD = 10007;
struct pos


{
int x, y;
};

LL C[MOD+1][MOD+1];


pos forb[16];
int H, W, R;


LL Calc2(int m, int n)


{
if (m > MOD || n > MOD)
return Calc2(m / MOD, n / MOD) * Calc2(m % MOD, n % MOD) % MOD;
return C[m][n];
}

LL Calc(int x, int y)


{
int step = (x + y) / 3;
if (step * 3 != x + y) return 0;
if (step > x) return 0;
if (3 * step - x != y) return 0;

return Calc2(step, x-step);
}

bool cmp(const pos a, const pos b)


{
if (a.x != b.x) a.x < b.x;
return a.y < b.y;

}

LL Solve(int x)


{
LL ret = 1;
vector<pos> loc;
loc.clear ();
pos temp;
temp.x = 1, temp.y = 1;
loc.push_back(temp);
temp.x = H, temp.y = W;
loc.push_back(temp);
for (int i = 0; i <= R; i++)
if ((1<<i) & x)
temp.x = forb[i].x, temp.y = forb[i].y, loc.push_back(temp);

sort(loc.begin(), loc.end(), cmp);

for (int i = 0; i + 1 < loc.size(); i++)
ret *= Calc(loc[i+1].x - loc[i].x, loc[i+1].y - loc[i].y), ret %= MOD;

return ret;
}

int Count(int x)


{
int ret = 1;
for (int i = 0; i < R; i++)
if (x & (1<<i))
ret = - ret;
return ret;
}

int main()


{
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
int T;
scanf("%d", &T);

for (int i = 0; i < MOD; i++)
for (int j = 0; j <= i; j++)

{
if (i == 0 || i == j) C[i][j] = 1;
else
C[i][j] = (C[i-1][j] + C[i-1][j-1]) % MOD;
}


for (int ctr = 1; ctr <= T; ctr++)

{

scanf("%d%d%d", &H, &W, &R);
for (int i = 0; i < R; i++)
scanf("%d%d", &forb[i].x, &forb[i].y);

LL ret = 0;
for (int i = 0; i < (1<<R); i++)
ret += Count(i) * Solve(i), ret %= MOD;

ret += MOD;
ret %= MOD;


printf("Case #%d: %I64d\n", ctr, ret);

}

return 0;
}