06年的题目还是比较简单的……为防止查一道题时其他题被剧透而将题解部分调成白色了……
Day 1
超级英雄
将题目和锦囊分别视为二分图中两部分的点,一个题目向能用的锦囊连边,一边加边一边匹配当前题目,直到不能匹配的位置就是答案。由于每题只连出两条边,所以单次匹配可以优化到O(1)。不过裸的匈牙利也能过。
鬼谷子的钱袋
答案即log(n)取上整。
马步距离
这题其实有规律。如果对棋盘进行黑白染色,显然走奇数步只能走到与起点异色的点,偶数步只能走到同色点。假设走x步,那么终点到起点的曼哈顿距离应<=3x,且起点与终点的横坐标之差与纵坐标之差都应<=2x。那么一路枚举下去即可。注意特判走三步和走四步的一些特殊情况。
潘多拉的盒子
这题应该是最神的一道题,据说咒语机就是一个自动机……首先判断一个咒语机A是不是另一个B的升级有一种方法:
用二元组(x,y)表示当前状态处在A的x点与B的y点上,初始为(0,0)。转移时x和y一起走到加0和加1后的位置,即(px0,py0)和(px1,py1),如果已经访问过该状态就不转移。假设在某个状态中x为输出元而y不是,那么B就不能输出这条A能输出的咒语,即B不是A的升级。如果不存在这种状态,B就是A的升级。
用上面的方法对每两个咒语机都进行判断然后建图。建完之后缩强连通分量,得到的会是一个拓扑图。在这个拓扑图上DP即可。
Day 2
最短母串
最短的母串一定是将所有字串按一定顺序重叠排列。那么预处理出每两个串能重叠的最长长度,开始DP。记f[i][j]表示最短长度,其中i是二进制数,表示已经选择了哪些串,j是最后一个加入的串。转移时枚举每个串加入。答案即min{f[2^n-1][j]}。至于字典序最小,我的做法是保存每个f[i][j]对应的母串,转移时同时判断字典序。用字符数组几乎是卡着内存过的……用string会好一些,不过会慢一点……
公路修建问题
二分答案,用类似Kruskal的方法判断能够加入多少条一级公路,如果不少于k条再判断能加入的二级公路能否构成生成树。
花仙子的魔法
好像变成经典DP问题了……观察一维的情况:每加入一条线段最多增加两个区间。然后考虑二维的情况:已有两个相交的圆,现在加入第三个,可以发现如果把第三个圆展开成一条线段,其与其他两个圆相交的位置也对应到线段上,那么可以转化成一维两条线段的情况。由此得出DP:记f[i][j]为最多的区间数,其中i为维数,j为施法次数。方程:f[i][j]=f[i][j-1] + f[i-1][j-1]。
军机调度
有谁看懂了题求讲解……
代码:
超级英雄:
//BZOJ1191; Hero (HNOI2006); Bipartite Graph
#include <cstdio>
#include <cstdlib>
#define N 1000
#define M 1000
struct edge
{
int next, node;
}e[M << 1 | 1];
int n, m, x, y, head[N + 1], tot = 0, match[N + 1], ans = 0;
bool v[N + 1];
inline void addedge(int a, int b)
{
e[++tot].next = head[a];
head[a] = tot, e[tot].node = b;
}
bool find(int x)
{
for (int i = head[x]; i; i = e[i].next)
{
int node = e[i].node;
if (v[node]) continue;
v[node] = true;
if (!match[node] || find(match[node]))
{
match[node] = x;
return true;
}
}
return false;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= m; ++i)
{
scanf("%d%d", &x, &y);
++x, ++y;
addedge(i, x), addedge(i, y);
}
for (int i = 1; !ans && i <= m; ++i)
{
for (int j = 1; j <= n; ++j) v[j] = false;
if (!find(i)) ans = i;
}
ans = !ans ? m : ans - 1;
printf("%d\n", ans);
return 0;
}
鬼谷子的钱袋:
//BZOJ1192; 鬼谷子的钱袋 (HNOI2006);
#include <cstdio>
#include <cstdlib>
typedef long long ll;
ll n, x = 1LL;
int ans = 0;
int main()
{
scanf("%lld", &n);
while (x < n) x <<= 1LL, ++ans;
printf("%d\n", ans);
}
马步距离:
//BZOJ1193; 马步距离; Observation
#include <cstdio>
#include <cstdlib>
int x1, x2, y1_, y2, d, ans;
int abs(int x)
{ return x < 0 ? -x : x; }
int main()
{
scanf("%d%d%d%d", &x1, &y1_, &x2, &y2);
d = abs(x1 - x2) + abs(y1_ - y2);
for (ans = 0; ; ++ans)
{
if (((x1 + y1_ & 1) ^ (x2 + y2 & 1)) != (ans & 1)) continue;
if (d <= ans * 3 && abs(x1 - x2) <= ans * 2 && abs(y1_ - y2) <= ans * 2) break;
}
if (d == 1) ans = 3;
if (d == 4 && abs(x1 - x2) == 2 && abs(y1_ - y2) == 2)
ans = 4;
printf("%d\n", ans);
return 0;
}
潘多拉的盒子:
//BZOJ1194; Pandora (HNOI2006);
#include <cstdio>
#include <cstdlib>
#include <utility>
#include <algorithm>
#define S 50
#define N 50
#define INFI 12345678
typedef std::pair<int, int> pair;
#define pair(x, y) std::make_pair(x, y)
int s, n[N + 1], m[N + 1], p[S + 1][N + 1][2], x, y, ans = 0;
int cnt[N + 1], f[N + 1], ind[N + 1], v[N + 1][N + 1], ct = 0, Q[N + 1], h, t;
bool con[N + 1][N + 1], out[N + 1][N + 1];
pair cur, q[N * N + 1];
inline bool check(int a, int b)
{
++ct;
h = t = 0;
q[t++] = pair(0, 0);
while (h < t)
{
cur = q[h++];
if (out[a][cur.first] && !out[b][cur.second]) return false;
x = p[a][cur.first][0], y = p[b][cur.second][0];
if (v[x][y] != ct) v[x][y] = ct, q[t++] = pair(x, y);
x = p[a][cur.first][1], y = p[b][cur.second][1];
if (v[x][y] != ct) v[x][y] = ct, q[t++] = pair(x, y);
}
return true;
}
int main()
{
scanf("%d", &s);
for (int i = 1; i <= s; ++i)
{
scanf("%d%d", n + i, m + i);
for (int j = 1; j <= m[i]; ++j)
{
scanf("%d", &x);
out[i][x] = true;
}
for (int j = 0; j < n[i]; ++j)
scanf("%d%d", &p[i][j][0], &p[i][j][1]);
}
for (int i = 1; i <= s; ++i)
for (int j = 1; j <= s; ++j)
if (i != j && check(i, j))
con[i][j] = 1;
for (int i = 1; i <= s; ++i) cnt[i] = 1;
for (int i = 1; i <= s; ++i)
for (int j = 1; j < i; ++j)
if (cnt[j] && con[i][j] && con[j][i])
{
++cnt[j], cnt[i] = 0;
break;
}
for (int i = 1; i <= s; ++i)
for (int j = 1; j <= s; ++j)
if (cnt[i] && cnt[j] && con[i][j]) ++ind[j];
for (int i = 1; i <= s; ++i) f[i] = cnt[i];
h = t = 0;
for (int i = 1; i <= s; ++i)
if (!ind[i] && cnt[i]) Q[t++] = i;
while (h < t)
{
int cur = Q[h++];
for (int i = 1; i <= s; ++i)
if (con[cur][i] && cnt[i] && ind[i])
{
--ind[i];
f[i] = std::max(f[i], f[cur] + cnt[i]);
if (!ind[i]) Q[t++] = i;
}
}
for (int i = 1; i <= s; ++i)
ans = std::max(ans, f[i]);
printf("%d\n", ans);
return 0;
}
最短母串:
//BZOJ1195; 最短母串 (HNOI2006); State Compression DP
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <climits>
#include <algorithm>
#include <utility>
#include <queue>
#define N 12
#define LEN 50
typedef std::pair<int, int> pair;
#define pair(x, y) std::make_pair(x, y)
int n, l[N + 1], f[N + 1][1 << N], d[N + 1][N + 1], x, y, from[N + 1][1 << N];
int ans = INT_MAX, tq[N + 1], top;
char s[N + 1][LEN + 1], ts[N + 1][LEN * N + 1], str[N + 1][1 << N][LEN * N + 1], tmp[LEN * N + 1];
bool inq[N + 1][1 << N];
std::queue<pair> q;
pair cur;
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i) scanf("%s", s[i]);
for (int i = 1; i < n; ++i)
for (int j = i + 1; j <= n; ++j)
if (strcmp(s[i], s[j]) > 0)
{
strcpy(s[0], s[i]);
memset(s[i], 0, sizeof(char) * l[i]);
strcpy(s[i], s[j]);
memset(s[j], 0, sizeof(char) * l[j]);
strcpy(s[j], s[0]);
}
for (int i = 1; i <= n; ++i) l[i] = (int)strlen(s[i]);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
{
if (i == j) continue;
d[i][j] = l[i];
for (int k = std::max(0, l[i] - l[j]); k < l[i]; ++k)
if (!strncmp(s[i] + k, s[j], std::min(l[i] - k, l[j])))
{
d[i][j] = k;
break;
}
}
for (int i = 1; i <= n; ++i)
f[i][1 << i - 1] = 0, q.push(pair(i, 1 << i - 1)), inq[i][1 << i - 1] = true;
while (!q.empty())
{
y = q.front().first, x = q.front().second;
inq[y][x] = false;
q.pop();
for (int i = 1; i <= n; ++i)
if (!(x & (1 << i - 1)))
{
int nx = x | (1 << i - 1);
if (!f[i][nx] || f[i][nx] > f[y][x] + d[y][i])
{
f[i][nx] = f[y][x] + d[y][i];
from[i][nx] = y;
strcpy(str[i][nx], str[y][x]);
strncpy(str[i][nx] + f[y][x], s[y], d[y][i]);
if (!inq[i][nx])
q.push(pair(i, nx)), inq[i][nx] = true;
}
else if (f[i][nx] == f[y][x] + d[y][i])
{
strcpy(tmp, str[y][x]);
strncpy(tmp + f[y][x], s[y], d[y][i]);
tmp[f[y][x] + d[y][i] + 1] = 0;
if (strcmp(tmp, str[i][nx]) < 0)
strcpy(str[i][nx], tmp);
}
}
}
for (int i = 1; i <= n; ++i)
if (f[i][(1 << n) - 1] + l[i] < ans)
ans = f[i][(1 << n) - 1] + l[i], top = 1, tq[0] = i;
else if (f[i][(1 << n) - 1] + l[i] == ans)
tq[top++] = i;
for (int i = 0; i < top; ++i)
{
strcpy(ts[i], str[tq[i]][(1 << n) - 1]);
strcpy(ts[i] + f[tq[i]][(1 << n) - 1], s[tq[i]]);
}
x = 0;
for (int i = 1; i < top; ++i)
if (strcmp(ts[x], ts[i]) > 0) x = i;
printf("%s\n", ts[x]);
return 0;
}
公路修建问题:
//BZOJ1196; road (HNOI2006); Kruskal + Dichtomization
#include <cstdio>
#include <cstdlib>
#define N 10000
#define M 20000
#define MAX 30000
struct inedge
{
int a, b, w1, w2;
}ie[M + 1];
int n, m, f[N + 1], k;
int find(int x)
{ return x == f[x] ? x : f[x] = find(f[x]); }
inline bool check(int x)
{
int cnt = 0;
for (int i = 1; i <= n; ++i) f[i] = i;
for (int i = 1; cnt < n && i <= m; ++i)
if (ie[i].w1 <= x && find(ie[i].a) != find(ie[i].b))
++cnt, f[find(ie[i].a)] = find(ie[i].b);
if (cnt < k) return false;
for (int i = 1; cnt < n && i <= m; ++i)
if (ie[i].w2 <= x && find(ie[i].a) != find(ie[i].b))
++cnt, f[find(ie[i].a)] = find(ie[i].b);
if (cnt >= n - 1) return true;
return false;
}
int main()
{
scanf("%d%d%d", &n, &k, &m);
for (int i = 1; i <= m; ++i)
scanf("%d%d%d%d", &ie[i].a, &ie[i].b, &ie[i].w1, &ie[i].w2);
int l = 1, r = MAX;
while (l < r)
{
int mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
return 0;
}
花仙子的魔法:
//BZOJ1197; 花仙子的魔法 (HNOI2006); DP
#include <cstdio>
#include <cstdlib>
#define N 15
#define M 100
typedef long long ll;
int m, n;
ll f[N + 1][M + 1];
int main()
{
scanf("%d%d", &m, &n);
for (int i = 1; i <= m; ++i) f[1][i] = 2 * i;
for (int i = 1; i <= n; ++i) f[i][1] = 2;
for (int i = 2; i <= n; ++i)
for (int j = 2; j <= m; ++j)
f[i][j] = f[i - 1][j - 1] + f[i][j - 1];
printf("%lld\n", f[n][m]);
return 0;
}