Google Code Jam 2008 Round 3 解题报告
Problem A: How Big Are the Pockets?
这题的意思是要求一个闭合的多边形外,有多少点的上和下或者左和右都有多边形的边界。
假设我们一行一行地来扫描这个多边形,我们从最左边开始,这时候我们的点在多边形外。
当我们第一次穿过多边形的一个边界的时候,很明显,我们就进入了多边形的内部,当再次遇到一条边界的时候我们又在多边形的外部了。通过这样的方法,我们可以发现当我们把某一行的边界按照坐标排序后第偶数条边界同第奇数条边界之间的所有点都是在多边形外部的,并且他们有左右两个边界。对于竖直方向上我们也可以用类似的方法处理。
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
14using namespace std;
15
16const int dir[4][2] = {{1,0}, {0,1}, {-1,0}, {0,-1}};
17
18vector<int> ver[10000], hor[10000];
19
20
21int 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的空间,显然这个方法不太好。在仔细想想,我们发现第一枪是可以随便打的,可是当我们打出第二枪的时候,我们就一定会朝着第二枪所打的方向一直走过去,并穿过那个传送门,应为如果不这样走的话,那么,那个第二枪打的是没有意义的。进一步思考,我们还会发现,第二枪一定可以是贴着墙打的,不是吗?
这样我们我们就需要记录到某个点坐标,以及到该点时所有可能有门的位置。然后当我们走到一面墙的时候,就可能到其他所有有门的位置了。
1/**//*
2 GCJ'08 Problem B
3 Author Nicholas
4*/
5#include <iostream>
6#include <string>
7#include <queue>
8
9using namespace std;
10
11const int dir[4][2] = {{-1,0}, {0, 1}, {1,0}, {0, -1}};
12
13struct state
14{
15 int x,y;
16 short int p[16];
17};
18
19struct pos
20{
21 int x, y;
22};
23
24char G[32][32];
25
26pos nw[32][32][4];
27int dp[32][32];
28
29
30
31int 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的
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
19using namespace std;
20
21typedef vector<int> vi;
22typedef vector<string> vs;
23typedef long long ll;
24typedef complex<double> pnt;
25typedef 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
31template<class T>
32void 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
39template<class T> T gcd(T a, T b) { return b ? gcd(b, a % b) : a; }
40
41static bool done[10000];
42static int back[10000];
43static vector<int> edges[10000];
44
45static 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
65int 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。别人告诉我的, 有谁知道怎么推导的麻烦告诉我一下子。
/**//*
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;
}