HNOI2006(BZOJ1191~1197)题解

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;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值