「2019纪中集训Day1」解题报告


T1、水叮当的舞步

水叮当得到了一块五颜六色的格子形地毯作为生日礼物,更加特别的是,地毯上格子的颜色还能随着踩踏而改变。

为了讨好她的偶像虹猫,水叮当决定在地毯上跳一支轻盈的舞来卖萌~~~

地毯上的格子有 \(n\) \((n \le 8)\) 行 \(n\) 列,每个格子用一个 \(0\) ~ \(5\) 之间的数字代表它的颜色。

水叮当可以随意选择一个 \(0\) ~ \(5\) 之间的颜色,然后轻轻地跳动一步,地毯左上角的格子所在的联通块里的所有格子就会变成她选择的那种颜色。这里连通定义为:两个格子有公共边,并且颜色相同。

由于水叮当是施展轻功来跳舞的,为了不消耗过多的真气,她想知道最少要多少步才能把所有格子的颜色变成一样的。

\(Sol\) :由于 \(n \le 8\),考虑搜索。加上迭代和一个最优性剪枝(若当前深度加上未处理的块的颜色数量大于深度限制,就不继续搜)基本就能过了。

代码如下:

#include 
#include 
#include 

const int N = 11;

int n, mp[N][N], d[N][N];

int t[7];
int calc() {
	for (int i = 0; i < 6; ++i)
		t[i] = 0;
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			if (!~d[i][j])
				t[mp[i][j]] = 1;
	for (int i = 1; i < 6; ++i)
		t[0] += t[i];
	return t[0];
}

bool chk() {
	for (int i = 1; i <= n; ++i)
		for (int j = 1; j <= n; ++j)
			if (mp[i][j] != mp[1][1])
				return 0;
	return 1;
}

struct node {
	int x, y;
} q[N * N];
int dx[] = {0, 1, -1, 0};
int dy[] = {1, 0, 0, -1};
bool vis[N][N];
bool find(const int cl) {
	int l = 0, r = 0;
	memset(vis, 0, sizeof(vis));
	q[r++] = (node){1, 1}, vis[1][1] = 1;
	node u, v;
	while (l < r) {
		u = q[l++];
		for (int i = 0; i < 4; ++i) {
			v = u;
			v.x += dx[i], v.y += dy[i];
			if (mp[v.x][v.y] == mp[1][1] && !vis[v.x][v.y])
				q[r++] = v, vis[v.x][v.y] = 1;
			else if (mp[v.x][v.y] == cl)
				return 1;
		}
	}
	return 0;
}

void color(const int cl, const int id, const int k) {
	int l = 0, r = 0;
	memset(vis, 0, sizeof(vis));
	q[r++] = (node){1, 1}, vis[1][1] = 1;
	node u, v;
	while (l < r) {
		u = q[l++];
		for (int i = 0; i < 4; ++i) {
			v = u;
			v.x += dx[i], v.y += dy[i];
			if (mp[v.x][v.y] == mp[1][1] && !vis[v.x][v.y])
				q[r++] = v, vis[v.x][v.y] = 1;
		}
	}
	if (!k)
		for (int i = 0; i < r; ++i)
			if (d[q[i].x][q[i].y] < id)
				mp[q[i].x][q[i].y] = cl;
			else
				d[q[i].x][q[i].y] = -1;
	else {
		for (int i = 0; i < r; ++i)
			mp[q[i].x][q[i].y] = cl;
		l = 0;
		while (l < r) {
			u = q[l++];
			if (!~d[u.x][u.y])
				d[u.x][u.y] = id;
			for (int i = 0; i < 4; ++i) {
				v = u;
				v.x += dx[i], v.y += dy[i];
				if (mp[v.x][v.y] == mp[1][1] && !vis[v.x][v.y])
					q[r++] = v, vis[v.x][v.y] = 1;
			}
		}
	}
}

bool dfs(const int dep, const int lim) {
//	printf("%d  %d:\n", lim, dep);
//	for (int i = 1; i <= n; ++i) {
//		for (int j = 1; j <= n; ++j)
//			printf("%d ", mp[i][j]);
//		puts("");
//	}
//	puts("");
	if (dep >= lim)
		return chk();
	if (calc() + dep > lim)
		return 0;
	int tmp = mp[1][1];
	for (int i = 0; i < 6; ++i) {
		if (i != tmp && find(i)) {
			color(i, dep + 1, 1);
			if (dfs(dep + 1, lim))
				return 1;
			color(tmp, dep + 1, 0);
		}
	}
	return 0;
}

inline void init() {
	int l = 0, r = 0;
	memset(vis, 0, sizeof(vis));
	q[r++] = (node){1, 1}, vis[1][1] = 1;
	node u, v;
	while (l < r) {
		u = q[l++];
		d[u.x][u.y] = 0;
		for (int i = 0; i < 4; ++i) {
			v = u;
			v.x += dx[i], v.y += dy[i];
			if (mp[v.x][v.y] == mp[1][1] && !vis[v.x][v.y])
				q[r++] = v, vis[v.x][v.y] = 1;
		}
	}
}	

int main() {
//	freopen("floodit1.in", "r", stdin);
//	freopen("in", "r", stdin);
	while (scanf("%d", &n) && n) {
		memset(mp, -1, sizeof(mp));
		memset(d, -1, sizeof(d));
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j)
				scanf("%d", &mp[i][j]);
		init();
		int i;
		for (i = 0; ; ++i)
			if (dfs(0, i))
				break;
		printf("%d\n", i);
	}
	return 0;
}

T2、Vani和Cl2捉迷藏

vani和cl2在一片树林里捉迷藏……

这片树林里有 \(n\) \((n \le 200)\) 座房子,\(m\) \((m \le 30000)\) 条有向道路,组成了一张有向无环图。

树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。如果从房子 \(A\) 沿着路走下去能够到达 \(B\),那么在 \(A\) 和 \(B\) 里的人是能够相互望见的。

现在 \(cl2\) 要在这 \(n\) 座房子里选择K座作为藏身点,同时 \(vani\) 也专挑 \(cl2\) 作为藏身点的房子进去寻找,为了避免被 \(vani\) 看见,\(cl2\) 要求这 \(K\) 个藏身点的任意两个之间都没有路径相连。

为了让 \(vani\) 更难找到自己,\(cl2\) 想知道最多能选出多少个藏身点?

\(Sol\):DAG最长反链,根据 \(Dilworth\) 定理,最长反链=最长可相交最小链覆盖= \(n\) - 最大匹配。

代码如下:

#include 
#include 
#include 
int in() {
	int x = 0; char c = getchar(); bool f = 0;
	while (c < '0' || c > '9')
		f |= c == '-', c = getchar();
	while (c >= '0' && c <= '9')
		x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
	return f ? -x : x;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 205;

int n, m, mat[N], link[N], a[N][N];
int vis[N];

bool dfs(int u, int tim) {
    for(int v=1;v<=n;++v)
        if(a[u][v]) {
            if(vis[v]==tim) continue;
            vis[v] = tim;
            if(!mat[v] || dfs(mat[v], tim)) {
                mat[v]=u;
                return true;
            }
        }
    return false;
}

int Hungary() {
    int ret=0;
    for (int i=1;i<=n;++i)
        if(dfs(i, i))
			++ret;
	return ret;
}

void floyd() {
	for (int k = 1; k <= n; ++k)
		for (int i = 1; i <= n; ++i)
			for (int j = 1; j <= n; ++j)
				a[i][j] |= a[i][k] & a[k][j];
}

int main() {
	n = in(), m = in();
	for (int i = 1; i <= m; ++i)
		a[in()][in()] = 1;
	floyd();
	printf("%d\n", n - Hungary());
	return 0;
}

T3、粉刷匠

赫克托是一个魁梧的粉刷匠,而且非常喜欢思考= =

现在,神庙里有N根排列成一直线的石柱,从 \(1\) 到 \(n\) 标号,长老要求用油漆将这些石柱重新粉刷一遍。赫克托有 \(k\) \((k \le 15)\) 桶颜色各不相同的油漆,第 \(i\) 桶油漆恰好可以粉刷 \(a_i\) \((a_i \le 6)\) 根石柱,并且 \(\sum a_i = n\)。长老为了刁难赫克托,要求相邻的石柱颜色不能相同。

喜欢思考的赫克托不仅没有立刻开始粉刷,反而开始琢磨一些奇怪的问题,比如,一共有多少种粉刷的方案?

为了让赫克托尽快开始粉刷,请你尽快告诉他答案。

\(Sol_1\):
因为 \((a_i \le 6)\),且各种油漆除数量外没有不同,所以可以将油漆按照数量进行分组;
记录 \(f_{i,j,k,l,p,t}\),表示剩下能刷 \(1,2,3,4,5\) 块,上一次从 \(t\) 转移过来的方案数,把前面 \(5\) 维,状压再哈希一下就行了。

\(Sol_2\):
考虑每次将一种颜色的柱子插入原来的柱子中,记 \(f_{i,j}\) 表示,前 \(i\) 种颜色,有 \(j\) 根柱子和它左边颜色一样的方案数,记 \(S_i\) 为前 \(i\) 中油漆刷的柱子数;
枚举将第 \(i + 1\) 种柱子分成 \(k\) 组 \((C_{a_{i + 1} - 1} ^ {k - 1})\),前 \(l\) 组被插在 \(j\) 根柱子中的 \(l\) 根柱子的左边,剩下的 \(k - l\) 组插在剩下 \(S_i + 1 - j\) 中;
可得状态转移方程:\(f[i + 1][j + a_{i + 1} - k - l] = f[i][j] \times C_{a_{i + 1} - 1} ^ {k - 1} \times C_j^l \times C_{S_i + 1 - j}^{k - l}\)。
最终答案为:\(f[n][0]\)。

代码如下:

#include 
#include 
#include 
int in() {
	int x = 0; char c = getchar(); bool f = 0;
	while (c < '0' || c > '9')
		f |= c == '-', c = getchar();
	while (c >= '0' && c <= '9')
		x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
	return f ? -x : x;
}
templateinline void chk_min(T &_, T __) { _ = _ < __ ? _ : __; }
templateinline void chk_max(T &_, T __) { _ = _ > __ ? _ : __; }

const int N = 16, mod = 1e9 + 7;
int n, a[N], f[N][1001], fac[1001], inv[1001];

int qpow(int base, int b, int ret = 1) {
	for (; b; b >>= 1, base = 1ll * base * base % mod)
		if (b & 1)
			ret = 1ll * ret * base % mod;
	return ret;
}

inline int C(const int n, const int m) {
	return 1ll * fac[n] * inv[m] % mod * inv[n - m] % mod;
}

int main() {
	fac[0] = inv[0] = 1;
	for (int i = 1; i <= 900; ++i)
		fac[i] = 1ll * fac[i - 1] * i % mod;
	inv[900] = qpow(fac[900], mod - 2);
	for (int i = 899; i; --i)
		inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
	int T = in();
	while (T--) {
		memset(f, 0, sizeof(f));
		n = in();
		for (int i = 1; i <= n; ++i)
			a[i] = in();
		f[0][0] = 1;
		for (int i = 0, s = 0; i < n; ++i) {
			s += a[i];
			for (int j = 0; j <= s; ++j) {
				if (!f[i][j])
					continue;
				for (int k = 1; k <= std::min(s + 1, a[i + 1]); ++k) {
					for (int l = std::max(0, k - (s + 1 - j)); l <= std::min(k, j); ++l) {
						(f[i + 1][j + a[i + 1] - k - l] +=
							1ll * f[i][j] * C(a[i + 1] - 1, k - 1) % mod * C(j, l) % mod * C(s + 1 - j, k - l) % mod) %= mod;
					}
				}
			}
		}
		printf("%d\n", f[n][0]);
	}
	return 0;
}

两种复杂度都是 \(O(n ^ 2 a T)\) (应该是)。