SRM566 1000pts

绍一的模拟赛题

【题意】

小Z养了$𝑚(\leqslant 50)$只企鹅,第$i$只企鹅的坐标为$(x_i, y_i)$,颜色为$𝑐_𝑖$。出于对企鹅安全的考虑,他决定造栅栏。地面上有$𝑛(\leqslant 200)$个桩,编号$0...𝑛 − 1$,小Z会用一些直的栅栏,每个栅栏连接某两个桩子。对于最后的结果,任意两个栅栏不能交叉。任意一个桩只能连接0个或2个栅栏,栅栏必须围成一些封闭的多边形。由于经费有限,每一个多边形内至少包含一只企鹅。小Z的企鹅有着许多颜色。相同颜色的企鹅必须在同一个多边形内。为了保证所有企鹅的安全,每一只企鹅都必须被包含在某一个多边形内。

各种不合法的情况:

某种合法的情况:


现在小Z想知道自己有多少种合法的不同的造栅栏的方案𝑎𝑛𝑠。

 

【题解】

用ℎ[𝑙][𝑟][0..1]表示𝑙, 𝑟必须选,点的编号在𝑙, 𝑟之间,最外层的多边形内没有(0)或有(1)企鹅的除了最外层多边形其他多边形均合法的方案数。𝑔[𝑙][𝑟]表示𝑙必须选,点的编号在𝑙, 𝑟之间合法的方案数。𝑓[𝑙][𝑟]表示,点的编号在𝑙, 𝑟之间合法的方案数。当一个栅栏两侧没有相同颜色的企鹅时定义这
个栅栏是有效的。

然后就可以直接区间DP了。考虑计算ℎ[𝑙][𝑟][𝑛𝑜𝑤],枚举中间点𝑛𝑙,当< 𝑛𝑙, 𝑙 >合法时进行转移,分类讨论𝑛𝑜𝑤和三角形(𝑙, 𝑛𝑙, 𝑟)中有没有点的情况,再考虑向量< 𝑙, 𝑛𝑙 >右侧有没有企鹅,没有直接转移,有的话,考虑多边形(𝑙, 𝑙 + 1, 𝑛𝑙 − 1, 𝑛𝑙)内有没有点分类讨论一下即可。考虑计算𝑔[𝑙][𝑟],枚举中间点𝑛𝑟,当< 𝑛𝑟, 𝑙 >合法时进行转移,如果向量< 𝑟, 𝑙 >左侧和向量< 𝑙, 𝑛𝑙 >左侧的企鹅的并集为空𝑔[𝑙][𝑟] += ℎ[𝑙][𝑛𝑟][1],否则如果向量< 𝑛𝑟 + 1, 𝑟 >的企鹅和上面两个的并集为空𝑔[𝑙][𝑟] += ℎ[𝑙][𝑛𝑟][1] * 𝑓[𝑛𝑟 + 1][𝑟]。𝑓[𝑙][𝑟]可以轻松的从𝑔[𝑙][𝑟]转移过来。

【分析】

题解非常地神,直接给出了一个dp,这里从头分析为什么要这么做。

首先很容易确定一条边是否可以选(如果这条边两侧有相同颜色的企鹅,那么不能选)

那么现在问题变成了知道一些边能选不能选,要求选择一些边使得它们围成多个多边形,且每个企鹅都在一个多边形里,每个多边形里都有至少一个企鹅。
问题是在一个环上选出一些边,怎么就变成区间dp了呢?

考虑最朴素的暴力:枚举每条边选不选,再检查是否满足条件。所以第一步,我们考虑枚举一条边,选择任意一个点作为起始点,如下图选择了这个绿色的点

为了不重复,我们枚举这个点沿逆时针方向遇到的第一个有边的点。具体地说,这个点可能有边,可能没有边,如果没有边,我们按逆时针顺序继续枚举下一个点,直到枚举到一个有边的点,标为蓝色。

接下来继续枚举第二的点作为边的另一端,同样的,为了不重复,这个点从$r$开始按顺时针顺序枚举,找到第一个与$i$有边的$j$,由于$i$是从$l$开始第一个有边的点,所以$[l,i+1]$里面的点都不会再有边了,而$[j+1,r]$里点只有它们内部才可能有边,所以$<i,j>$这条边只能与$[i+1,j-1]$里面的点构成多边形了。而这恰好是一个区间,所以可以考虑区间dp。我们现在关心的是什么?是$[i,j]$这段区间里,能形成多少种如红线的方案。具体来说就是选了某些边,且$i,j$都已经作为一个端点,这样就可以在添加了$<i,j>$之后合并了,记方案数为$h(i,j)$(暂时不考虑企鹅的限制),那么此时答案需要加上$h(i,j)*([j+1,r]里连边的方案数)$。

再来看$[j+1,r]$里连边的方案数怎么求,我们发现这又是可以看成一个环,我们又回到了一个和原来的问题一样的问题,可以用同样的方式求解,不妨用$f(l,r)$表示,这样答案就是$f(0,n-1)$。

现在我们考虑$h(l,r)$怎么求,同样的,我们可以枚举$l$出去的第一条边$<l,i>$。

那么现在有两种情况:

1.$<i,r>$之间有边,这样的方案数就是$f(l+1,i-1)f(i+1,r-1)$。

2.$<i,r>$之间没有边,这样的方案数是$h(i,r)f(l+1,i-1)$。

至此,在忽略[每个企鹅都在一个多边形里,每个多边形里都有至少一个企鹅]这个限制的情况下,我们已经可以在$O(n^4)$解决这个问题了。

我们发现求解h的复杂度是$O(n^3)$的,而求解f是$O(n^4)$的,所以考虑优化f的转移。

我们发现$$f(l,r) = \sum_{l \leqslant i \leqslant r} \sum_{i < j \leqslant r} h(i,j)f(j+1,r)$$

而$$f(l-1,r) = \sum_{l-1 \leqslant i \leqslant r} \sum_{i < j \leqslant r} h(i,j)f(j+1,r)$$

对于$l \leqslant i \leqslant r$的部分,两个状态是一样的,所以我们自然不必每次都枚举。

一种思路是记$$e(l,r) = \sum_{l < j \leqslant r} h(l,j)f(j+1,r)$$

则$$f(l,r) = \sum_{l \leqslant i \leqslant r} e(i,j)$$

$e$可以理解为$l$一定选的情况下$[l,r]$内的方案数。

这样$f,h,e$可以在$O(n)$的时间内转移,总时间复杂度$O(n^3)$.

 

现在是时候考虑[每个企鹅都在一个多边形里,每个多边形里都有至少一个企鹅]这个限制了。

实际上也很简单,我们只要保证在闭合一个多边形的时候它里面有企鹅,并且在转移的时候保证空出来的图形里没企鹅即可。

这需要在$h$的状态里记录这个未封闭的图形里是否有企鹅,在转移$f$的时候保证S1没有企鹅,在转移h时保证S2没企鹅,在通过连接$<i,r>$转移$h$的时候保证S3没有企鹅。

 

 

我为什么用了$f,h,e$却不用$g$呢?因为在代码里$g(l,r)$表示连了$<l,r>$这条边$[l,r]$内的方案数,实际上就是$h(l,r,1)$。

现在来回答开头的问题

在环上选点是如何变成了区间dp?

【notice】 mod 100007是会爆int的,一开始注意到了写程序的时候却先忽略了,后来查了半天还以为转移写错了。。

【代码】

#include<bits/stdc++.h>

using namespace std;

typedef long long LL;
const int N = 230, mod = 100007;

int cnt[N][N], ok[N][N], col[N]; // cnt[l][r] <l, r> 右侧的企鹅数量
int n, m, r;

int get_num(int a[], int n) {
	int res = m;
	a[n] = a[0];
	for(int i = 0; i < n; i++) {
		res -= cnt[a[i]][a[i+1]];
	}
	//assert(res >= 0);
	return res;
}

void addit(int &x, int y) {
	if((x += y) >= mod) x -= mod;
}

int f[N][N], h[N][N][2], e[N][N];

int E(int, int);

int F(int l, int r) {
	if(l >= r || !cnt[l][r]) return 1;
	int &res = f[l][r];
	if(res >= 0) return res;

	res = 0;
	for(int i = l; i+1 < r; i++) {
		if(cnt[i][r] + cnt[r][l] == m) addit(res, E(i, r));
	}

	return res;
}

int H(int l, int r, int s) {
	if(r - l + 1 <= 2) return 0;
	int &res = h[l][r][s];
	if(res >= 0) return res;
	res = 0;

	for(int i = l + 1; i < r; i++) if(ok[l][i]) {
		int a[] = {l, l + 1, i - 1, i, 0}, b[] = {l, i, r, 0};
		if(i - l + 1 >= 3 && get_num(a, 4)) continue; // 空出的四边形内有企鹅

		int sc = get_num(b, 3) > 0;/* 当前三角形里是否有点 */ 
		// 不连<i, r>从h转移
		if(s == sc) {
			addit(res, (LL) F(l + 1, i - 1) * H(i, r, 0) % mod);
		}
		if(s) {
			addit(res, (LL) F(l + 1, i - 1) * H(i, r, 1) % mod);
		}
		// 连接<i, r>直接转移
		if(ok[i][r] && s == sc) {
			int c[] = {i, i + 1, r - 1, r, 0};
			if(r - i + 1 >= 3 && get_num(c, 4));
			else addit(res, (LL) F(l + 1, i - 1) * F(i + 1, r - 1) % mod);
		}
	}

	return res;
}

#define G(l, r) (ok[l][r] ? H(l, r, 1) : 0)

int E(int l, int r) {
	int &res = e[l][r];
	if(res >= 0) return res;
	res = 0;
	
	for(int i = l + 1; i <= r; i++) if(ok[l][i]) {
		int a[] = {l, i, i + 1, r, 0};
		if(i < r && get_num(a, 4));
		else addit(res, (LL) G(l, i) * F(i + 1, r) % mod);
	}

	return res;
}

struct Point {
	double x, y;
	Point() {}
	Point(double x, double y) : x(x), y(y) {}
	Point operator - (const Point &rhs) const {
		return Point(x - rhs.x, y - rhs.y);
	}
} p[N], q[N];

double Cross(const Point &a, const Point &b) {
	return a.x * b.y - a.y * b.x;
}

int idx(char c) {
	if('a' <= c && c <= 'z') return c - 'a';
	return c - 'A' + 26;
}

void input() {
	scanf("%d%d%d", &n, &m, &r);
	double pi = acos(-1.0);
	for(int i = 0; i < n; i++) {
		double ang = 2 * pi * i / n;
		p[i] = Point(r * cos(ang), r * sin(ang));
	}
	char str[9];
	for(int i = 0; i < m; i++) {
		scanf("%s%lf%lf", str, &q[i].x, &q[i].y);
		col[i] = idx(str[0]);
	}
}

bool OnLeft(const Point &p, const Point &a, const Point &b) {
	assert(fabs(Cross(b - a, p - a)) > 1e-3);
	return Cross(b - a, p - a) > 0;
}

void init() {
	LL st[N][N] = {0};
	for(int i = 0; i < n; i++) {
		for(int j = 0; j < n; j++) if(i != j) {
			int &res = cnt[i][j];
			for(int k = 0; k < m; k++) {
				if(!OnLeft(q[k], p[i], p[j])) {
					res++;
					st[i][j] |= 1LL << col[k];
				}
			}
		}
	}
	for(int i = 0; i < n; i++) {
		for(int j = i+1; j < n; j++) {
			if(st[i][j] & st[j][i]);
			else ok[i][j] = ok[j][i] = 1;
		}
	}
}

//#define dout(x) cerr << #x << " = " << x << endl

int main() {
	freopen("penguin.in", "r", stdin);
	freopen("penguin.out", "w", stdout);

	input(), init();

	memset(f, -1, sizeof f);
	memset(h, -1, sizeof h);
	memset(e, -1, sizeof e);

	printf("%d\n", F(0, n-1));

	return 0;
}

 

 

posted @ 2016-06-29 19:49  Showson  阅读(324)  评论(0编辑  收藏  举报