BZOJ3598 [Scoi2014]方伯伯的商场之旅

题目名字好长。

 

当年在考场上只会分块打表的题。

 

前几天有人讲之后觉得大概可做。而且好像那个方法很优秀。

 

于是我难得地去写了一回不用记搜且不记上界的数位dp。感觉这个玩意写出来很短,但是思考和调试的复杂度变高了。我觉得是状态定义变得不明确的原因。也许再补一些题就好了。

 

考虑先让所有都合并到最低位,一个基础数位dp。(然后写丑了好久)

 

然后考虑如果把一个数字的集合位置从p移动到p+1,设这个数字从低位到高位是a[0]..a[n-1],那么贡献就是∑a[0..p]-∑a[p+1..n-1]。显然当p从右往左移动的时候它是会先负后正的。如果它负的话就减,否则就不玩了。可以发现它们互相不影响。于是对于每一个p单独计算一遍贡献就行了。

 

然后还是没有原版跑得快。OTL。

 

#define PROC "shop"
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long dint;
#ifdef WIN32
#define lld "%I64d"
#else
#define lld "%lld"
#endif
#define _l (long long int)

const int maxn = 53;
const int maxm = 509;
const int mb = 253;

dint l, r;
int n, m, bs, a[maxn];

dint dpBeginning() {
	static dint f[maxn], c[maxn], g[maxn], cr[maxn];
	c[0] = bs;
	f[0] = 0;
	for (int i = 1; i < n; ++ i) {
		c[i] = c[i - 1] * bs;
		f[i] = f[i - 1] * bs;
		for (int j = 1; j < bs; ++ j)
			f[i] = f[i] + c[i - 1] * i * j;
	}
	cr[0] = g[0] = 0;
	cr[1] = a[0] + 1;
	g[1] = a[1] * cr[1];
	for (int i = 2; i <= n; ++ i)  {
		cr[i] = g[i] = 0;
		for (int d = 0; d < a[i - 1]; ++ d) {
			cr[i] += c[i - 2];
			g[i] += f[i - 2] + c[i - 2] * (d * (i - 1) + a[i] * i);
		}
		cr[i] += cr[i - 1];
		g[i] += cr[i - 1] * a[i] * i + g[i - 1];
	}
	return g[n];
}

dint dp(dint v) {
	static dint c[maxn][maxm];
	static dint cr[maxn][maxm];
	m = 0;
	for (n = 0; v; v /= bs)
		a[n ++] = v % bs;
	a[n] = 0;
	m = n * bs;
	dint ans(dpBeginning());
	//printf(lld "\n", ans);
	for (int p = 1; p < n; ++ p) {
		memset(c, 0, sizeof(c));
		for (int i = 0; i < bs; ++ i)
			c[0][mb - i] = 1;
		for (int i = 1; i < n; ++ i)
			for (int j = mb - m; j <= mb + m; ++ j)
				if (c[i - 1][j])
					for (int d = 0; d < bs; ++ d) {
						int t((i < p) ? j - d : j + d);
						c[i][t] += c[i - 1][j];
					}
		memset(cr, 0, sizeof(cr));
		cr[0][mb - a[0]] = 1;
		for (int i = 1; i <= n; ++ i) {
			for (int d = 0; d < a[i - 1]; ++ d)
				if (i > 1) {
					for (int j = mb - m; j <= mb + m; ++ j)
						if (c[i - 2][j]) {
							int t((i - 1 < p) ? j - d : j + d);
							t = (i < p) ? t - a[i] : t + a[i];
							cr[i][t] += c[i - 2][j];
						}
				}
				else  {
					int t((i < p) ? mb - d - a[i] : mb - d + a[i]);
					++ cr[i][t];
				}
			for (int j = mb - m; j <= mb + m; ++ j)
				if (cr[i - 1][j]) {
					int t((i < p) ? j - a[i] : j + a[i]);
					cr[i][t] += cr[i - 1][j];
				}
		}
		for (int i = 1; i <= m; ++ i)
			ans -= cr[n][i + mb] * i;
	}
	return ans;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen(PROC ".in", "r", stdin);
	freopen(PROC ".out", "w", stdout);
#endif

	scanf(lld lld "%d", &l, &r, &bs);
	printf(lld "\n", dp(r) - dp(l - 1));
}

BZOJ1814 Ural 1519 Formula 1

插头dp的练手题么。虽然之前还没写过哈密顿回路的题。

 

学习了一下广义括号法。发现状态数比之前记连通性的方法要少。仔细想想发现,两个连通的插头的顺序有关。不能存在1212这种东西。所以连通性的方法记了很多废状态。那把连通性方法改进一下会不会变得更快呢?可以研究一下。我觉得应该是等效了吧。

 

今天的debug时间明显缩短了。虽然还是debug了老半天。然后怎么代码还是那么长。想起我每次都觉得把东西挤到一起看起来很不雅观,于是去写一堆找转移的函数。晕。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long dint;
#ifdef WIN32
#define lld "%I64d"
#else
#define lld "%lld"
#endif
#define _l (long long int)

const int maxn = 15;
const int maxst = 50009;

#define mbit(_x_,_y_) ((_x_)<<((_y_)<<1))
#define gbit(_x_,_y_) (((_x_)>>((_y_)<<1))&3)
#define cbit(_x_,_y_) ((_x_)&(~(3<<((_y_)<<1))))

int n, m, tots, slst[maxst];
bool mp[maxn][maxn];
dint f[2][maxst];

void dfsState(int l, int c, int z) {
	if (l == m + 1) {
		if (!c)
			slst[tots ++] = z;
	}
	else {
		dfsState(l + 1, c, z);
		dfsState(l + 1, c + 1, z | mbit(1, l));
		if (c)
			dfsState(l + 1, c - 1, z | mbit(2, l));
	}
}
void preState() {
	tots = 0;
	dfsState(0, 0, 0);
	sort(slst, slst + tots);
}
inline int fState(int s) {
	register int ret(lower_bound(slst, slst + tots, s) - slst);
	if (slst[ret] != s)
		puts("naive");
	return ret;
}

#define inc(_a_,_b_) (_a_+=_b_)
inline int brv(int x) {
	if (!x)
		return 0;
	else
		return (x == 1) ? 1 : -1;
}

bool isEnd(int s, int y) {
	for (int i = 0; i < y; ++ i)
		if (gbit(s, i))
			return 0;
	for (int i = y + 2; i <= m; ++ i)
		if (gbit(s, i))
			return 0;
	return 1;
}
inline int mergePlug(int s, int y) {
	int p, c;
	if (gbit(s, y) != gbit(s, y + 1)) {
		s = cbit(s, y);
		s = cbit(s, y + 1);
		return fState(s);
	}
	else if (gbit(s, y) == 1) {
		for (c = 1, p = y + 1; c; ++ p, c += brv(gbit(s, p)));
		s = cbit(s, y);
		s = cbit(s, y + 1);
		s = cbit(s, p);
		return fState(s | mbit(1, p));
	}
	else {
		for (c = -1, p = y; c; -- p, c += brv(gbit(s, p)));
		s = cbit(s, y);
		s = cbit(s, y + 1);
		s = cbit(s, p);
		return fState(s | mbit(2, p));
	}
}
inline int extPlugRight(int s, int y) {
	s |= mbit(gbit(s, y), y + 1);
	s = cbit(s, y);
	return fState(s);
}
inline int extPlugDown(int s, int y) {
	s |= mbit(gbit(s, y + 1), y);
	s = cbit(s, y + 1);
	return fState(s);
}
inline int forkPlug(int s, int y) {
	s |= mbit(1, y);
	s |= mbit(2, y + 1);
	return fState(s);
}
inline int nextRow(int s) {
	int r(0);
	for (int i = 0; i < m; ++ i)
		r |= mbit(gbit(s, i), i + 1);
	return fState(r);
}

dint dp() {
	int cur(0), prv(1);
	dint ans;
	memset(f, 0, sizeof(f));
	f[cur][0] = 1;
	for (int x = 0; x < n; ++ x) {
		for (int y = 0; y < m; ++ y) {
			if (mp[x][y])
				ans = 0;
			swap(cur, prv);
			memset(f[cur], 0, sizeof(f[cur]));
			for (int i = 0; i < tots; ++ i)
				if (f[prv][i]) {
					int s(slst[i]);;
					if (mp[x][y]) {
						if (gbit(s, y) && gbit(s, y + 1)) {
							if (gbit(s, y) == 1 && gbit(s, y + 1) == 2) {
								if (isEnd(s, y))
									inc(ans, f[prv][i]);
							}
							else
								inc(f[cur][mergePlug(s, y)], f[prv][i]);
						}
						else if (gbit(s, y)) {
							inc(f[cur][i], f[prv][i]);
							inc(f[cur][extPlugRight(s, y)], f[prv][i]);
						}
						else if (gbit(s, y + 1)) {
							inc(f[cur][i], f[prv][i]);
							inc(f[cur][extPlugDown(s, y)], f[prv][i]);
						}
						else
							inc(f[cur][forkPlug(s, y)], f[prv][i]);
					}
					else {
						if (!gbit(s, y) && !gbit(s, y + 1))
							inc(f[cur][i], f[prv][i]);
					}
				}
		}
		swap(cur, prv);
		memset(f[cur], 0, sizeof(f[cur]));
		for (int i = 0; i < tots; ++ i)
			if (f[prv][i] && !gbit(slst[i], m))
				inc(f[cur][nextRow(slst[i])], f[prv][i]);
	}
	return ans;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d%d", &n, &m);
	for (int i = 0; i < n; ++ i) {
		char g[maxn];
		scanf("%s", g);
		for (int j = 0; j < m; ++ j)
			mp[i][j] = (g[j] == '.');
	}
	preState();
	printf(lld "\n", dp());
}

BZOJ2704 旅游

居然网上都没有找到程序来和我拍。好吧其实它是一道权限题而且过的人也不多。

 

其实就是裸的插头DP。本来想学一下广义括号的然后被状态数吓到了于是只好还是写最小表示。连通性。

 

然后我发现最小表示连通性很容易写挂。而在findstate的时候检查一下是个不错的办法。至少这道题靠这个就可以直接debug出来问题了。

 

比上次写插头要好许多了。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long dint;
#define _l (long long int)
#define mbit(_x_,_y_) ((_l _x_)<<((_y_)<<2))
#define gbit(_x_,_y_) (((_x_)>>((_y_)<<2))&0xf)

const int maxn = 13;
const int maxst = 570009;

dint slst[maxst];
int n, m, v[maxn][maxn], tots, f[2][maxst], cnt[17];
bool av[2][maxst];

void dfsState(int l, int tot, dint z) {
	if (l == m) {
		for (int i = 1; i <= tot; ++ i)
			if (cnt[i] != 2)
				return;
		slst[tots ++] = z;
	}
	else {
		for (int i = 0; i <= tot; ++ i)
			if (!i || cnt[i] == 1) {
				++ cnt[i];
				dfsState(l + 1, tot, z | mbit(i, l));
				-- cnt[i];
			}
		++ cnt[tot + 1];
		dfsState(l + 1, tot + 1, z | mbit(tot + 1, l));
		-- cnt[tot + 1];
		if (!cnt[15] && l < m - 1) {
			++ cnt[15];
			dfsState(l + 1, tot, z | mbit(15, l));
			-- cnt[15];
		}

	}
}

void preState() {
	tots = 0;
	memset(cnt, 0, sizeof(cnt));
	dfsState(0, 0, 0);
	sort(slst, slst + tots);
}

inline int fState(dint x) {
	return lower_bound(slst, slst + tots, x) - slst;
	//if (slst[ret] != x)
		//puts("naive");
	//return ret;
}

#define fUpMax(_sa_,_sv_) { \
	register int _a_(_sa_), _v_(_sv_); \
	if (!av[cur][_a_]) { \
		av[cur][_a_] = 1; \
		f[cur][_a_] = _v_; \
	} \
	else if (_v_ > f[cur][_a_]) \
		f[cur][_a_] = _v_; \
}
#define upMax(_a_,_b_) { \
	if (_a_ < _b_) \
		_a_ = _b_; \
}

void unzipState(dint z, int* a) {
	for (int i = 0; i < m; ++ i)
		a[i] = gbit(z, i);
}

dint zipState(int* a) {
	dint s(0);
	for (int i = 0; i < m; ++ i)
		s |= mbit(a[i], i);
	return s;
}

bool isFinalPlug(int *a, int y) {
	for (int i = 0; i < y - 1; ++ i)
		if (a[i])
			return 0;
	for (int i = y + 1; i <m; ++ i)
		if (a[i])
			return 0;
	return 1;
}

int mergePlug(int* a, int y) {
	static int b[maxn], c[17];
	memcpy(b, a, sizeof(b));
	if (a[y - 1] == 15) {
		b[y - 1] = b[y];
		b[y] = 0;
	}
	else {
		int f(a[y - 1]), t(a[y]);
		for (int i = 0; i < m; ++ i)
			if (b[i] == f)
				b[i] = t;
		b[y - 1] = b[y] = 0;
		memset(c, 0, sizeof(c));
		f = 0;
		for (int i = 0; i < m; ++ i)
			if (b[i]) {
				if (c[b[i]])
					b[i] = c[b[i]];
				else
					b[i] = c[b[i]] = ++ f;
			}
	}
	return fState(zipState(b));
}

int extendPlugRight(int* a, int y) {
	static int b[maxn];
	memcpy(b, a, sizeof(b));
	if (a[y - 1] == 15) {
		int t(1);
		for (int i = 0; i < y - 1; ++ i)
			if (a[i] < 15 && a[i] + 1 > t)
				t = a[i] + 1;
		for (int i = y + 1; i < m; ++ i)
			if (a[i] < 15 && a[i] >= t)
				++ b[i];
		b[y - 1] = b[y] = t;
	}
	else {
		b[y] = b[y - 1];
		b[y - 1] = 0;
	}
	return fState(zipState(b));
}

int newPlug(int* a, int y) {
	static int b[maxn];
	memcpy(b, a, sizeof(b));
	b[y] = 15;
	return fState(zipState(b));
}

int dp() {
	int prv(0), cur(1), so(fState(15)), ans(0);
	memset(av, 0, sizeof(av));
	av[cur][so] = 1;
	f[cur][so] = v[0][0];
	for (int x = 0; x < n; ++ x)
		for (int y = !x ? 1 : 0; y < m; ++ y) {
			swap(prv, cur);
			memset(av[cur], 0, sizeof(av[cur]));
			for (int i = 0; i < tots; ++ i)
				if (av[prv][i]) {
					dint s(slst[i]);
					static int a[maxn];
					unzipState(s, a);
					if (v[x][y]) {
						if (y && a[y - 1] && a[y]) {
							if (a[y - 1] == a[y]) {
								if (isFinalPlug(a, y))
									upMax(ans, f[prv][i] + v[x][y]);
							}
							else {
								fUpMax(mergePlug(a, y), f[prv][i] + v[x][y]);
							}
						}
						if (y && a[y - 1] && !a[y])
							fUpMax(extendPlugRight(a, y), f[prv][i] + v[x][y]);
						if (a[y] && (!y || a[y - 1] != 15)) 
							fUpMax(i, f[prv][i] + v[x][y]);
						if (!a[y] && (!y || a[y - 1] != 15) && y < m - 1)
							fUpMax(newPlug(a, y), f[prv][i] + v[x][y]);
					}
					if (!a[y] && (!y || a[y - 1] != 15))
						fUpMax(i, f[prv][i]);
				}
		}
	return ans;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	bool sw(0);
	scanf("%d%d", &n, &m);
	if (n < m)
		sw = 1;
	for (int i = 0; i < n; ++ i)
		for (int j = 0; j < m; ++ j)
			if (sw)
				scanf("%d", v[j] + i);
			else
				scanf("%d", v[i] + j);
	if (sw)
		swap(n, m);
	preState();
	printf("%d\n", dp());
}

BZOJ1223 [HNOI2002]Kathy函数

比较神奇的数学题。首先你要知道这个函数的意思是把它的二进制flip。至于怎么来的我也是听说的。然后推一下发现的确是这么回事。

 

于是就变成数位dp了。然后我发现数位dp常常可以用奇奇怪怪的方法水过去。然后又发现数组从0开始标号也会造成麻烦。+1-1啥的最讨厌了。

 

于是这题就变成高精度练习题了。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 409;

struct BigInt {
	int l, a[maxn], base;
	BigInt(int b = 10) {
		l = 0;
		base = b;
	}
	void push() {
		for (int i = 0; i < l - 1; ++ i) {
			a[i + 1] += a[i] / base;
			a[i] %= base;
		}
		for (; a[l - 1] >= base; ++ l) {
			a[l] = a[l - 1] / base;
			a[l - 1] %= base;
		}
	}
	void pull() {
		for (; l && !a[l - 1]; -- l);
	}
	void read() {
		static char g[maxn];
		base = 10;
		scanf("%s", g);
		l = strlen(g);
		for (int i = 0; i < l; ++ i)
			a[i] = g[l - i - 1] - 48;
		pull();
	}

	inline void setOne() {
		l = 1;
		a[0] = 1;
	}
	inline void setZero() {
		l = 0;
	}

	void assAdd(const BigInt& x) {
		for (int i = 0; i < l && i < x. l; ++ i)
			a[i] += x. a[i];
		for (int i = l; i < x. l; ++ i)
			a[i] = x. a[i];
		if (x. l > l)
			l = x. l;
		push();
	}
	void assDec() {
		-- a[0];
		for (int i = 0; a[i] < 0; ++ i) {
			a[i] += base;
			-- a[i + 1];
		}
		pull();
	}
	void assMul(int x) {
		for (int i = 0; i < l; ++ i)
			a[i] *= x;
		push();
	}
	int mod(int x) const {
		int s(0);
		for (int i = l - 1; i >= 0; -- i)
			s = (s * base + a[i]) % x;
		return s;
	}
	void assDiv(int x) {
		for (int i = l - 1; i >= 0; -- i) {
			if (i)
				a[i - 1] += a[i] % x * base;
			a[i] /= x;
		}
		pull();
	}
	int compareTo(const BigInt& x) const {
		if (l > x. l)
			return 1;
		else if (l < x. l)
			return -1;
		for (int i = l - 1; i >= 0; -- i)
			if (a[i] > x. a[i])
				return 1;
			else if (a[i] < x. a[i])
				return -1;
		return 0;
	}
	void ass(const BigInt& x) {
		static BigInt tmp;
		if (base == x. base) {
			l = x. l;
			memcpy(a, x. a, sizeof(a));
		}
		else {
			tmp. base = x. base;
			tmp. ass(x);
			for (l = 0; tmp. l; ++ l) {
				a[l] = tmp. mod(base);
				tmp. assDiv(base);
			}
		}
	}
	void print(char eon = 10) {
		for (int i = l - 1; i >= 0; -- i)
			putchar(a[i] + 48);
		if (!l)
			putchar(48);
		if (eon)
			putchar(eon);
	}
	void flip() {
		for (int i = 0; (i << 1) < l; ++ i)
			swap(a[i], a[l - i - 1]);
		pull();
	}
};

BigInt tmp, m, bm(2), br(2), ans, p2[maxn], ONE;

bool checkFlip(const BigInt& a) {
	int q(a. l >> 1), p(a. l - q - 1);
	for (int i = 0; p - i >= 0; ++ i)
		if (a. a[p - i] > a. a[q + i])
			return 1;
		else if (a. a[p - i] < a. a[q + i])
			return 0;
	return 1;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	ONE. setOne();
	tmp. read();
	m. ass(tmp);
	bm. ass(tmp);
	int l((bm. l + 1) >> 1);
	p2[0]. setOne();
	for (int i = 1; i <= l; ++ i) {
		p2[i]. ass(p2[i - 1]);
		p2[i]. assMul(2);
	}
	for (int i = 1; i < bm. l; ++ i)
		ans. assAdd(p2[((i + 1) >> 1) - 1]);
	for (int i = bm. l - 2; i > bm. l - l - 1; -- i)
		if (bm. a[i])
			ans. assAdd(p2[i - (bm. l >> 1)]);
	if (checkFlip(bm))
		ans. assAdd(ONE);
	ans. print();
}

BZOJ1023 [SHOI2008]cactus仙人掌图

第六百道题一定要不水!虽然离第一页还有几十道题的距离。

 

很早之前见过的题吧。然后一直觉得特别难写所以都没写。以前写点不重复的仙人掌都觉得挺痛苦,更不要说这个了。

 

现在想想还行,因为我yy出了化简的方法。先搞出dfs树。然后一个顶点如果是一个环里深度最浅的,那么称它为这个环的头。可以证明一个顶点最多在一个不以它为头的环上。把这个环记下来,其它的环都从这个顶点去找边来找就行了。这样的话可以比较节省代码量,写起来也比较明了。虽然还是比mhy长。

 

然后因为我在最后一步dp的时候写了一个static的f数组,所以光荣了。debug了好久。晕。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

struct edge {
	int t, av;
	edge *next, *rv;
};
struct ball {
	int l, *a, h;
};

const int maxn = 50009;
const int maxm = 200009;
const int maxarr = 400009;

int n, m, cb, fb[maxn], d[maxn];
int f[maxn], ans, fd[maxn], fe[maxn];
int stc[maxn], tst;
int ibuf_arr[maxarr], *ibuf(ibuf_arr);
bool vis[maxn];
edge elst[maxm], *ep(elst), *head[maxn];
ball b[maxm];

inline edge* addEdge(int u, int v) {
	ep-> t = v;
	ep-> av = 1;
	ep-> next = head[u];
	return head[u] = ep ++;
}

inline void upMax(int& a, int b) {
	if (a < b)
		a = b;
}

void dfsBall(int p) {
	vis[p] = 1;
	stc[++ tst] = p;
	for (edge *e = head[p]; e; e = e-> next)
		if (e-> av) {
			e-> rv-> av = 0;
			if (vis[e-> t] && (!fb[p])) {
				e-> av = 0;
				b[++ cb]. l = 0;
				b[cb]. a = ibuf;
				for (int i = tst; 1; -- i) {
					b[cb]. a[b[cb]. l ++] = stc[i];
					if (stc[i] != e-> t)
						fb[stc[i]] = cb;
					else
						break;
				}
				b[cb]. h = e-> t;
				ibuf += b[cb]. l;
			}
			else {
				d[e-> t] = d[p] + 1;
				dfsBall(e-> t);
			}
		}
	-- tst;
}

void downDP(int p) {
	fd[p] = fe[p] = 0;
	for (edge* e = head[p]; e; e = e-> next)
		if (e-> av) {
			if (fb[e-> t] && p == b[fb[e-> t]]. h) {
				int bi(fb[e-> t]), fl(0);
				for (int i = 0; i < b[bi]. l; ++ i)
					if (b[bi]. a[i] != b[bi]. h) {
						int q(b[bi]. a[i]);
						downDP(q);
						upMax(fl, fd[q] + min(d[q] - d[p], b[bi]. l - d[q] + d[p]));
					}
				if (fl > fd[p]) {
					fe[p] = fd[p];
					fd[p] = fl;
				}
				else if (fl > fe[p])
					fe[p] = fl;
			}
			else if (!fb[p] || fb[p] != fb[e-> t]) {
				downDP(e-> t);
				int v(fd[e-> t] + 1);
				if (v > fd[p]) {
					fe[p] = fd[p];
					fd[p] = v;
				}
				else if (v > fe[p])
					fe[p] = v;
			}
		}
	upMax(ans, fd[p] + fe[p]);
}

#define modi(_i_) (((_i_)>=b[bi].l)?((_i_)-b[bi].l):(_i_))
void upDP(int p, int u) {
	static int q[maxn << 1], fu[maxn << 1];
	for (edge* e = head[p]; e; e = e-> next)
		if (e-> av) {
			if (fb[e-> t] && p == b[fb[e-> t]]. h) {
				int bi(fb[e-> t]), fl(0);
				int *f(ibuf);
				ibuf += b[bi]. l;
				for (int i = 0; i < b[bi]. l; ++ i) {
					int q(b[bi]. a[i]);
					f[i] = 0;
					if (q == p)
						break;
					fu[i] = fd[q];
					upMax(fl, fd[q] + min(d[q] - d[p], b[bi]. l - d[q] + d[p]));
				}
				if (fl == fd[p])
					fu[b[bi]. l - 1] = max(u, fe[p]);
				else
					fu[b[bi]. l - 1] = max(u, fd[p]);
				int hd(0), tl(1), hl(b[bi]. l >> 1);
				q[0] = 0;
				for (int i = 1; i < (b[bi]. l << 1); ++ i) {
					int fi(modi(i));
					while (hd < tl && i - q[hd] > hl)
						++ hd;
					if (hd < tl)
						upMax(f[fi], fu[modi(q[hd])] + i - q[hd]);
					while (hd < tl && fu[modi(q[tl - 1])] - q[tl - 1] <= fu[fi] - i)
						-- tl;
					q[tl ++] = i;
				}
				hd = 0;
				tl = 1;
				q[0] = (b[bi]. l << 1) - 1;
				for (int i = (b[bi]. l << 1) - 2; i >= 0; -- i) {
					int fi(modi(i));
					while (hd < tl && q[hd] - i > hl)
						++ hd;
					if (hd < tl)
						upMax(f[fi], fu[modi(q[hd])] + q[hd] - i);
					while (hd < tl && fu[modi(q[tl - 1])] + q[tl - 1] <= fu[fi] + i)
						-- tl;
					q[tl ++] = i;
				}
				for (int i = 0; i < b[bi]. l - 1; ++ i) {
					upMax(ans, f[i] + fd[b[bi]. a[i]]);
					upDP(b[bi]. a[i], f[i]);
				}
				ibuf -= b[bi]. l;
			}
			else if (!fb[p] || fb[e-> t] != fb[p]) {
				int v((fd[e-> t] + 1 == fd[p]) ? fe[p] : fd[p]);
				upDP(e-> t, max(u, v) + 1);
			}
		}
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d%d", &n, &m);
	memset(head, 0, sizeof(head));
	for (int i = 0; i < m; ++ i) {
		int c, u, v;
		scanf("%d%d", &c, &u);
		while (-- c) {
			scanf("%d", &v);
			edge *a(addEdge(u, v)), *b(addEdge(v, u));
			a-> rv = b;
			b-> rv = a;
			u = v;
		}
	}
	memset(vis, 0, sizeof(vis));
	memset(fb, 0, sizeof(fb));
	cb = 0;
	tst = 0;
	d[1] = 1;
	dfsBall(1);
	ans = 0;
	downDP(1);
	upDP(1, 0);
	printf("%d\n", ans);
}

BZOJ2734 [HNOI2012]集合选数

最初感觉好多状态怎么做。

 

然后发现(其实是瞟了一下题解)如果把一个数分成2a*3b*c,那么c只影响上界。2a*3b中a最大是16,b最大是10,列一张17*11的表出来,然后发现右下方还有很多空东西。于是直接像插头一样状压一下轮廓线呗。反正对一个数有影响的只有它的上面和左边。最近写插头写得有点疯啊。其实它的很多思想都是可以推广出来用的。比如最小表示啥的。

 

于是如果直接这样写的话大概要跑30000多次dp,还是比较悬。然后再想想发现只有表里能取的数的形状发生改变的时候才会改变dp的答案。表里面的数很少所以改变次数也应该很少。所以记一下上一次的形状是什么然后比较相不相同,相同就直接沿用。

 

然后跑得飞快。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define _l (long long int)
#define pow2(_x_) (1<<(_x_))

const int maxn = 19;
const int maxm = 15;
const int maxv = 100000;
const int maxst = pow2(11) + 9;
const int mod = 1e9 + 1;

int f[2][maxst], vl[maxn][maxm], n, m, v;
int dis[maxn], ldis[maxn], lans, ans;

void pre() {
	for (int i = 0; i < maxn; ++ i) {
		vl[i][0] = (1 << i);
		if (vl[i][0] > maxv)
			vl[i][0] = -1;
		for (int j = 1; j < maxm; ++ j) 
			if (vl[i][j - 1] > -1) {
				vl[i][j] = vl[i][j - 1] * 3;
				if (vl[i][j] > maxv)
					vl[i][j] = -1;
			}
			else
				vl[i][j] = -1;
	}
}

bool getDis(int x) {
	memset(dis, 0, sizeof(dis));
	for (int i = 0; i < maxn; ++ i)
		while (vl[i][dis[i]] > -1 && x >= vl[i][dis[i]])
			++ dis[i];
	bool eq(1);
	for (int i = 0; i < maxn && eq; ++ i)
		if (dis[i] != ldis[i])
			eq = 0;
	if (!eq)
		memcpy(ldis, dis, sizeof(dis));
	return eq;
}

inline void incm(int& _a_, int _b_) {
	_a_ += _b_;
	if (_a_ >= mod)
		_a_ -= mod;
}

int dp() {
	int prv(1), cur(0), m(dis[0]), e(pow2(m));
	memset(f, 0, sizeof(f));
	f[cur][0] = 1;
	for (int i = 0; dis[i]; ++ i)
		for (int j = 0; j < dis[i]; ++ j) {
			swap(cur, prv);
			memset(f[cur], 0, sizeof(f[cur]));
			for (int k = 0; k < e; ++ k)
				if (f[prv][k]) {
					if (!(k & pow2(j)) && (!j || !(k & pow2(j - 1))))
						incm(f[cur][k | pow2(j)], f[prv][k]);
					incm(f[cur][k & (~pow2(j))], f[prv][k]);
				}
		}
	int ans(0);
	for (int i = 0; i < e; ++ i)
		incm(ans, f[cur][i]);
	return ans;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	pre();
	scanf("%d", &v);
	ans = 1;
	lans = 0;
	memset(ldis, -1, sizeof(ldis));
	ans = 1;
	for (int i = 1; i <= v; ++ i)
		if (i % 2 && i % 3) {
			if (getDis(v / i))
				ans = _l ans * lans % mod;
			else
				ans = _l ans * (lans = dp()) % mod;
		}
	printf("%d\n", ans);
}

BZOJ3242 [Noi2013]快餐店

好吧我的本意是想学习一下dp的。然后发现了这道纠结了一年半还多的题。

 

首先把环找出来。然后环上会长一些树。先把树的答案求了,因为它很好求。

 

然后考虑环,那么答案应该是随便断一条边之后答案的最小值(而不是最大值)。于是乎记录一下每个点按某个方向到起点的距离,记为d[i]。算一下每个点的树的深度,记为l[i]。于是每次要最大化l[i]+l[j]+|d[i]-d[j]|。反正是最大化所以顺序是没有关系的,但是不能自交。这是个比较容易错的地方。于是就去写个线段树来维护吧。只查询不修改让我总有能线性搞的想法,虽然没想清楚具体怎么实现。

 

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>

using namespace std;

struct edge {
	int t, v;
	edge *next;
};

typedef long long dint;
#ifdef WIN32
#define lld "%I64d"
#else
#define lld "%lld"
#endif
#define _l (long long int)

const int maxn = 100009;

edge elst[maxn << 1], *ep(elst), *head[maxn];
int n, fa[maxn], df[maxn], ol[maxn], d[maxn];
int tlp, lp[maxn << 1];
dint lv[maxn << 1], le[maxn << 1], md[maxn], ans;

inline void addEdge(int u, int v, int w) {
	ep-> t = v;
	ep-> v = w;
	ep-> next = head[u];
	head[u] = ep ++;
}

void findLoop() {
	static int q[maxn];
	int hd(0), tl(1);
	int uo(0), vo(0), dx, a;
	q[0] = 1;
	fa[1] = -1;
	d[1] = 1;
	memset(ol, 0, sizeof(ol));
	memset(fa, 0, sizeof(fa));
	while (hd < tl && !uo) {
		int p(q[hd ++]);
		for (edge* e = head[p]; e; e = e-> next)
			if (e-> t ^ fa[p]) {
				if (fa[e-> t]) {
					uo = p;
					vo = e-> t;
					dx = e-> v;
					break;
				}
				else {
					d[e-> t] = d[p] + 1;
					fa[e-> t] = p;
					df[e-> t] = e-> v;
					q[tl ++] = e-> t;
				}
			}
	}
	tlp = 0;
	for (int u = uo, v = vo; u ^ v; ++ tlp)
		if (d[u] > d[v])
			a = u = fa[u];
		else
			a = v = fa[v];
	++ tlp;
	for (int u = uo, v = vo; u ^ v; )
		if (d[u] > d[v]) {
			int i(d[uo] - d[u]);
			lp[i] = u;
			le[i] = df[u];
			u = fa[u];
		}
		else {
			int i(tlp - d[vo] + d[v] - 1);
			lp[i] = v;
			le[i - 1] = df[v];
			v = fa[v];
		}
	lp[d[uo] - d[a]] = a;
	le[tlp - 1] = dx;
	for (int i = 0; i < tlp; ++ i)
		ol[lp[i]] = 1;
}

void dpTree(int p0) {
	static int q[maxn];
	int hd(0), tl(1);
	q[0] = p0;
	while (hd < tl) {
		int p(q[hd ++]);
		for (edge* e = head[p]; e; e = e-> next)
			if (!ol[e-> t]) {
				ol[e-> t] = ol[p] + 1;
				q[tl ++] = e-> t;
			}
	}
	for (int i = tl - 1; i >= 0; -- i) {
		int p(q[i]);
		dint s2(0);
		md[p] = 0;
		for (edge* e = head[p]; e; e = e-> next)
			if (ol[e-> t] == ol[p] + 1) {
				dint v(md[e-> t] + e-> v);
				if (v > md[p]) {
					s2 = md[p];
					md[p] = v;
				}
				else if (v > s2)
					s2 = v;
			}
		if (md[p] + s2 > ans)
			ans = md[p] + s2;
	}
}

struct dat {
	dint x, y, s;
	dat() {}
	dat(dint x0, dint y0) {
		x = x0, y = y0, s = 0;
	}
	dat(dat a, dat b) {
		s = max(max(a. s, b. s), a. x + b. y);
		x = max(a. x, b. x);
		y = max(a. y, b. y);
	}
};
struct seg {
	int l, r;
	dat d;
	seg *ls, *rs;
};
seg slst[maxn * 5], *sp(slst), *rt;

#define midp(p) ((p->l+p->r)>>1)
inline seg* sgtBuild(int l, int r) {
	seg *p(sp ++);
	p-> l = l;
	p-> r = r;
	if (l + 1 == r) {
		p-> d = dat(lv[l] + le[l], lv[l] - le[l]);
	}
	else {
		p-> ls = sgtBuild(l, midp(p));
		p-> rs = sgtBuild(midp(p), r);
		p-> d = dat(p-> ls-> d, p-> rs-> d);
	}
	return p;
}
inline dat sgtQry(seg* p, int l, int r) {
	if (p-> l == l && p-> r == r)
		return p-> d;
	else if (r <= midp(p))
		return sgtQry(p-> ls, l, r);
	else if (l >= midp(p))
		return sgtQry(p-> rs, l, r);
	else
		return dat(sgtQry(p-> ls, l, midp(p)), sgtQry(p-> rs, midp(p), r));
}

void dpLoop() {
	dint totl(0);
	for (int i = 0; i < tlp; ++ i) {
		lv[i + tlp] = lv[i];
		le[i + tlp] = le[i];
		lp[i + tlp] = lp[i];
		totl += le[i];
	}
	le[tlp * 2] = 0;
	for (int i = tlp * 2 - 1; i >= 0; -- i)
		le[i] += le[i + 1];
	rt = sgtBuild(0, tlp * 2);
	dint cans(0x7fffffffffffffffll);
	for (int i = 0; i < tlp; ++ i) {
		cans = min(cans, sgtQry(rt, i, i + tlp). s);
	}
	ans = max(ans, cans);
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d", &n);
	memset(head, 0, sizeof(head));
	for (int i = 0; i < n; ++ i) {
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		addEdge(u, v, w);
		addEdge(v, u, w);
	}
	findLoop();
	ans = 0;
	for (int i = 0; i < tlp; ++ i) {
		dpTree(lp[i]);
		lv[i] = md[lp[i]];
	}
	dpLoop();
	printf("%.1lf\n", (double)ans / 2.0);
}

BZOJ1079 [SCOI2008]着色方案

看上去比较不可做的DP。好像别人写的记搜跑得飞快。

 

我看我是写最小表示写上瘾了。

 

压一下状态,有点像今天第二题。算每种值的有多少。然后直接跑。

 

感觉废状态比较多啊,跑得好慢。另外用memset清空高维数组会比较快。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int mod = 1e9 + 7;
const int maxn = 17;
const int maxst = 16009;

#define mbit(_a_,_b_) ((_a_)<<((_b_)<<2))
#define gbit(_a_,_b_) (((_a_)>>((_b_)<<2))&0xf)
#define _l (long long int)

int m, c[maxn], n, mt, tots, slst[maxst];
int f[2][7][maxst];

void dfsState(int t, int l, int v) {
	if (l == mt + 1) {
		slst[tots ++] = v | t;
	}
	else {
		for (int i = 0; i <= t; ++ i)
			dfsState(t - i, l + 1, v | mbit(i, l));
	}
}

inline int fState(int x) {
	return lower_bound(slst, slst + tots, x) - slst;
}

inline void incm(int& a, int b) {
	a += b;
	if (a >= mod)
		a -= mod;
}

#define clearFCur() { \
	for (int i = 1; i <= mt; ++ i) \
		for (int j = 0; j < tots; ++ j) \
			f[cur][i][j] = 0; \
}

int dp() {
	int cur(0), prv(1);
	int sb(0);
	for (int i = 0; i <= mt; ++ i)
		sb |= mbit(c[i], i);
	memset(f, 0, sizeof(f));
	for (int i = 1; i <= mt; ++ i)
		if (gbit(sb, i) > 0)
			f[cur][i - 1][fState(sb - mbit(1, i) + mbit(1, i - 1))] = gbit(sb, i);
	for (int ti = 1; ti < n; ++ ti) {
		swap(cur, prv);
//		clearFCur();
		memset(f[cur], 0, sizeof(f[cur]));
		for (int i = 0; i <= mt; ++ i)
			for (int j = 0; j < tots; ++ j)
				if (f[prv][i][j]) {
					int s(slst[j]), c;
					for (int k = 1; k <= mt; ++ k)
						if ((c = gbit(s, k) - (k == i)))
							incm(f[cur][k - 1][fState(s - mbit(1, k) + mbit(1, k - 1))], _l f[prv][i][j] * c % mod);
				}
	}
	return f[cur][0][fState(m)];
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d", &m);
	memset(c, 0, sizeof(c));
	mt = 0;
	n = 0;
	for (int i = 0; i < m; ++ i) {
		int tmp;
		scanf("%d", &tmp);
		n += tmp;
		++ c[tmp];
		if (tmp > mt)
			mt = tmp;
	}
	tots = 0;
	dfsState(m, 1, 0);
	sort(slst, slst + tots);
	printf("%d\n", dp());
}

BZOJ1187 [HNOI2007]神奇游乐园

我的第二道插头DP。差不多纠结了一天。

 

这道题网上的题解都讲的好简略啊。最后还得自己yy。没有用括号序列法让我觉得自己比较厉害。

 

考虑轮廓线上的m个格子,有3种情况:没有插头,有且只能往一边走,有且要往两边走。对于第二种,要用最多3个不同的值来记录连通性。还要有两个不同的值来表示第一种情况和第三种情况。我比较懒,所以直接用一个八进制数去表示,然后每次二分找编号。

 

考虑转移,分一堆情况进行讨论。默认从左上朝右下走。

 

首先是要走当前格子的情况。

 

如果左边和上面都有插头,那么看它们是否连通。如果不连通则把它们连通,如果已经连通,看是否还有其它插头。没有就更新答案,否则忽略。

 

如果只有左边有插头,再分类。如果它是第二种,那么这个格子可以新建一个第三种插头,或者把左边格子的插头接过来。如果它是第三种那么它必需把左边格子接过来。

 

如果只有上面有插头,那么必需把上面的插头接上来。

 

如果左边和上面都没有插头,那么可以新建一个第三种插头。

 

然后如果不走这个格子的话,要求上面没有插头,且左边不是第三种插头。

 

按mhy的话说,插头dp就是写起来很麻烦。的确。不过写出来也还是挺高兴的。

 

然后代码丑到不能看了。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 109;
const int maxm = 9;
const int maxst = 50009;
const int inf = 0x3f3f3f3f;

#define pow8(_x_) (1<<(_x_)<<(_x_)<<(_x_))
#define mbit(_x_,_y_) ((_x_)<<(_y_)<<(_y_)<<(_y_))
#define gbit(_x_,_y_) (((_x_)>>(_y_)>>(_y_)>>(_y_))&7)

int f[2][maxst], n, m, a[maxn][maxm], ans;
int slst[maxst], tots;

inline void upMax(int& a, int b) {
	if (a < b)
		a = b;
}

void dfsState(int cur, int tot, int val, int e) {
	if (cur == m) {
		static int cnt[maxm];
		memset(cnt, 0, sizeof(cnt));
		for (int i = 0; i < m; ++ i)
			++ cnt[gbit(val, i)];
		bool lg(1);
		for (int i = 1; i <= tot && lg; ++ i)
			if (cnt[i] != 0 && cnt[i] != 2)
				lg = 0;
		if (lg)
			slst[tots ++] = val;
	}
	else {
		for (int i = 0; i <= tot; ++ i)
			dfsState(cur + 1, tot, val | mbit(i, cur), e);
		dfsState(cur + 1, tot + 1, val | mbit(tot + 1, cur), e);
		if (e)
			dfsState(cur + 1, tot, val | mbit(7, cur), 0);
	}
}
void preState() {
	tots = 0;
	dfsState(0, 0, 0, 1);
	sort(slst, slst + tots);
}

int fState(int s) {
	int ret(lower_bound(slst, slst + tots, s) - slst);
	if (slst[ret] != s)
		puts("naive");
	return ret;
}

int joinState(int s, int p) {
	static int z[maxm], y[maxn], c;
	for (int i = 0; i < m; ++ i)
		z[i] = gbit(s, i);
	if (z[p - 1] == 7) {
		z[p - 1] = z[p];
		z[p] = 0;
	}
	else {
		int f(z[p]), t(z[p - 1]);
		for (int i = 0; i < m; ++ i)
			if (z[i] == f)
				z[i] = t;
		z[p - 1] = 0;
		z[p] = 0;
		memset(y, 0, sizeof(y));
		c = 0;
		for (int i = 0; i < m; ++ i)
			if (z[i] && z[i] < 7) {
				if (y[z[i]]) {
					z[i] = y[z[i]];
				}
				else {
					y[z[i]] = ++ c;
					z[i] = y[z[i]];
				}
			}
	}
	int f(0);
	for (int i = 0; i < m; ++ i)
		f |= mbit(z[i], i);
	return fState(f);
}

int expState(int s, int p) {
	static int z[maxm];
	for (int i = 0; i < m; ++ i)
		z[i] = gbit(s, i);
	int f(1);
	for (int i = 0; i < p - 1; ++ i)
		if (z[i] < 7 && z[i] + 1 > f)
			f = z[i] + 1;
	for (int i = 0; i < m; ++ i)
		if (z[i] < 7 && z[i] >= f)
			++ z[i];
	z[p - 1] = f;
	z[p] = f;
	f = 0;
	for (int i = 0; i < m; ++ i)
		f |= mbit(z[i], i);
	return fState(f);
}
int extState(int s, int p) {
	static int z[maxm];
	for (int i = 0; i < m; ++ i)
		z[i] = gbit(s, i);
	z[p] = z[p - 1];
	z[p - 1] = 0;
	int f(0);
	for (int i = 0; i < m; ++ i)
		f |= mbit(z[i], i);
	return fState(f);
}

int forkState(int s, int p) {
	return fState(s | mbit(7, p));
}

bool legalState(int s, int p) {
	static int z[maxm];
	for (int i = 0; i < m; ++ i)
		z[i] = gbit(s, i);
	for (int i = 0; i < p - 1; ++ i)
		if (z[i])
			return 0;
	for (int i = p + 1; i < m; ++ i)
		if (z[i])
			return 0;
	return 1;
}

void fillArr(int* a, int sz, int v) {
	for (int i = 0; i < sz; ++ i)
		a[i] = v;
}

void dp() {
	int cur(0), prv(1);
	fillArr(f[cur], tots, -inf);
	f[cur][0] = 0;
	for (int x = 0; x < n; ++ x) {
		for (int y = 0; y < m; ++ y) {
			swap(cur, prv);
			fillArr(f[cur], tots, -inf);
			f[cur][0] = 0;
			for (int i = 0; i < tots; ++ i) 
				if (f[prv][i] > -inf) {
					int s(slst[i]);
					if (gbit(s, y) && y && gbit(s, y - 1)) {
						if (gbit(s, y) == gbit(s, y - 1)) {
							if (legalState(s, y))
								upMax(ans, f[prv][i] + a[x][y]);
						}
						else {
							upMax(f[cur][joinState(s, y)], f[prv][i] + a[x][y]);
						}
					}
					if (!gbit(s, y) && y && gbit(s, y - 1)) {
						if (gbit(s, y - 1) == 7)
							upMax(f[cur][expState(s, y)], f[prv][i] + a[x][y]);
						else
							upMax(f[cur][extState(s, y)], f[prv][i] + a[x][y]);
					}
					if (gbit(s, y) && (!y || gbit(s, y - 1) != 7))
						upMax(f[cur][i], f[prv][i] + a[x][y]);
					if (y < m - 1 && !gbit(s, y) && (!y || gbit(s, y - 1) != 7))
						upMax(f[cur][forkState(s, y)], f[prv][i] + a[x][y]);
					if (!gbit(s, y) && (!y || gbit(s, y - 1) != 7))
						upMax(f[cur][i], f[prv][i]);
				}
		}
	}
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d%d", &n, &m);
	preState();
	ans = -inf;
	for (int i = 0; i < n; ++ i)
		for (int j = 0; j < m; ++ j)
			scanf("%d", a[i] + j);
	ans = -inf;
	dp();
	printf("%d\n", ans);
}

BZOJ1415 [Noi2005]聪聪和可可

最近很颓啊。

 

这题很久之前看过。当时以为是高消就没管。然后开坑之神idy做了之后仔细一想才知道不是。

 

这题的做法是dp。首先因为边数少所以不用floyd,直接bfs预处理出所有t[i][j]表狼在i,兔子在j的时候狼下一步去哪。然后有一个结论是每一步两个玩意的距离至少缩短1,所以状态是不会有环的,这也是为啥可以不用高消。然后坑点是不能直接开状态队列,因为那不是拓补序。要先把每个点的入度算出来再跑dp。结局是我的代码超长。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

struct edge {
	int t;
	edge *next;
};

#define mkword(_x_,_y_) (((_x_)<<16)|(_y_))
#define hiword(_x_) ((_x_)>>16)
#define loword(_x_) ((_x_)&0x0000ffff)

const int maxn = 1009;
const int maxst = 1000009;
const int inf = 0x3f3f3f3f;

int n, m, st, te, deg[maxn], d[maxn], t[maxn][maxn], ind[maxn][maxn];
edge elst[maxn << 1], *ep(elst), *head[maxn];
double f[maxn][maxn], g[maxn][maxn], c[maxn], ans;

inline void addEdge(int u, int v) {
	ep-> t = v;
	ep-> next = head[u];
	head[u] = ep ++;
}

void bfs(int p0) {
	static int q[maxn];
	int hd(0), tl(1);
	memset(d, -1, sizeof(d));
	d[q[0] = p0] = 0;
	while (hd < tl) {
		int p(q[hd ++]);
		for (edge* e = head[p]; e; e = e-> next)
			if (d[e-> t] == -1) {
				d[e-> t] = d[p] + 1;
				q[tl ++] = e-> t;
			}
	}
	for (int p = 1; p <= n; ++ p)
		if (p == p0) {
			t[p][p] = p;
		}
		else {
			t[p][p0] = inf;
			for (edge* e = head[p]; e; e = e-> next)
				if (d[p] == d[e-> t] + 1 && e-> t < t[p][p0])
					t[p][p0] = e-> t;
		}
}

void dp() {
	static int q[maxst];
	static bool iq[maxn][maxn];
	int hd(0), tl(1);
	memset(iq, 0, sizeof(iq));
	memset(f, 0, sizeof(f));
	memset(g, 0, sizeof(g));
	memset(ind, 0, sizeof(ind));
	ans = 0;
	q[0] = mkword(st, te);
	iq[st][te] = 1;
	while (hd < tl) {
		int i(hiword(q[hd])), j(loword(q[hd])), i0, j0;
		++ hd;
		if (t[t[i][j]][j] != j) {
			for (edge* e = head[j]; e; e = e-> next) {
				i0 = t[t[i][j]][j];
				j0 = e-> t;
				++ ind[i0][j0];
				int zn(mkword(i0, j0));
				if (!iq[i0][j0]) {
					q[tl ++] = zn;
					iq[i0][j0] = 1;
				}
			}
			i0 = t[t[i][j]][j];
			j0 = j;
			++ ind[i0][j0];
			int zn(mkword(i0, j0));
			if (!iq[i0][j0]) {
				q[tl ++] = zn;
				iq[i0][j0] = 1;
			}
		}
	}
	memset(f, 0, sizeof(f));
	memset(g, 0, sizeof(g));
	g[st][te] = 1;
	ans = 0;
	q[0] = mkword(st, te);
	hd = 0;
	tl = 1;
	while (hd < tl) {
		int i(hiword(q[hd])), j(loword(q[hd])), i0, j0;
		++ hd;
		if (i == j) {
			ans += f[i][j];
		}
		else if (t[t[i][j]][j] == j) {
			ans += f[i][j] + g[i][j];
		}
		else {
			for (edge* e = head[j]; e; e = e-> next) {
				i0 = t[t[i][j]][j];
				j0 = e-> t;
				g[i0][j0] += g[i][j] * c[j];
				f[i0][j0] += (f[i][j] + g[i][j]) * c[j];
				int zn(mkword(i0, j0));
				-- ind[i0][j0];
				if (!ind[i0][j0])
					q[tl ++] = zn;
			}
			i0 = t[t[i][j]][j];
			j0 = j;
			g[i0][j0] += g[i][j] * c[j];
			f[i0][j0] += (f[i][j] + g[i][j]) * c[j];
			int zn(mkword(i0, j0));
			-- ind[i0][j0];
			if (!ind[i0][j0])
				q[tl ++] = zn;
		}
	}
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d%d%d%d", &n, &m, &st, &te);
	memset(d, 0x3f, sizeof(d));
	memset(deg, 0, sizeof(deg));
	for (int i = 0; i < m; ++ i) {
		int u, v;
		scanf("%d%d", &u, &v);
		addEdge(u, v);
		addEdge(v, u);
		++ deg[u];
		++ deg[v];
	}
	for (int i = 1; i <= n; ++ i) {
		bfs(i);
		c[i] = 1.0 / (deg[i] + 1);
	}
	dp();
	printf("%.3lf\n", ans);
}

BZOJ1040 [ZJOI2008]骑士

多少年前的坑了。

 

用mac写的第一篇题解呢。

 

环套树DP。先把树上的情况处理了,然后枚举取不取环上的第一个。

 

要注意会有重边的情况,比较恶心。

 

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>

using namespace std;

const int buf_len = 4000;
char buf[buf_len], *bufb(buf), *bufe(buf + 1);
#define readBuf() { \
	if (++ bufb == bufe) \
		bufe = (bufb = buf) + fread(buf, 1, sizeof(buf), stdin); \
}
#define readInt(_x_) { \
	register int _s_(0); \
	do { \
		readBuf(); \
	} while (!isdigit(*bufb)); \
	do { \
		_s_ = _s_ * 10 + *bufb - 48; \
		readBuf(); \
	} while (isdigit(*bufb)); \
	_x_ = _s_; \
}

typedef long long dint;
#ifdef WIN32
#define lld "%I64d"
#else
#define lld "%lld"
#endif

struct edge {
	int t;
	edge *next;
};

const int maxn = 1000009;

int n, v[maxn];
int stc[maxn], tst, lpp[maxn], tlp;
int vis[maxn], onl[maxn];
dint f[maxn], g[maxn];
edge elst[maxn << 1], *ep(elst), *head[maxn];

inline void addEdge(int u, int v) {
	ep-> t = v;
	ep-> next = head[u];
	head[u] = ep ++;
}

void dfsLoop(int p, int fr) {
	vis[p] = fr + 1;
	stc[tst ++] = p;
	for (edge* e = head[p]; e; e = e-> next)
		if (e-> t != fr) {
			if (!vis[e-> t]) {
				dfsLoop(e-> t, p);
			}
			else if (vis[e-> t] && vis[e-> t] != p + 1) {
				if (onl[p])
					continue;
				tlp = 0;
				do {
					-- tst;
					lpp[tlp ++] = stc[tst];
					onl[stc[tst]] = 1;
				} while (stc[tst] != e-> t);
			}
		}
	if (stc[tst - 1] == p)
		-- tst;
}

void dfsDP(int p, int fr) {
	f[p] = 0;
	g[p] = v[p];
	vis[p] = -1;
	for (edge* e = head[p]; e; e = e-> next)
		if (e-> t != fr && !onl[e-> t] && vis[e-> t] != -1) {
			dfsDP(e-> t, p);
			f[p] += max(f[e-> t], g[e-> t]);
			g[p] += f[e-> t];
		}
}

dint calcCC(int p) {
	tst = 0;
	tlp = 0;
	dfsLoop(p, 0);
	if (!tlp)
		lpp[tlp ++] = p;
	for (int i = 0; i < tlp; ++ i)
		dfsDP(lpp[i], 0);
	dint s0(0), s1(0);
	if (tlp < 3) {
		for (int i = 0; i < tlp; ++ i)
			s0 = max(s0, g[lpp[i]] - f[lpp[i]]);
		for (int i = 0; i < tlp; ++ i)
			s0 += f[lpp[i]];
	}
	else {
		dint f0(g[lpp[0]] + f[lpp[1]] + f[lpp[tlp - 1]]), g0(f0);
		for (int i = 2; i < tlp - 1; ++ i) {
			dint f1(f[lpp[i]] + max(f0, g0)), g1(g[lpp[i]] + f0);
			f0 = f1;
			g0 = g1;
		}
		s0 = max(f0, g0);
	}
	if (tlp < 3) {
		s1 = s0;
	}
	else {
		dint f0(f[lpp[0]]), g0(f0);
		for (int i = 1; i < tlp; ++ i) {
			dint f1(f[lpp[i]] + max(f0, g0)), g1(g[lpp[i]] + f0);
			f0 = f1;
			g0 = g1;
		}
		s1 = max(g0, f0);
	}
	return max(s0, s1);
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	readInt(n);
	memset(head, 0, sizeof(head));
	for (int i = 1; i <= n; ++ i) {
		int x;
		readInt(v[i]);
		readInt(x);
		addEdge(i, x);
		addEdge(x, i);
	}
	memset(vis, 0, sizeof(vis));
	memset(onl, 0, sizeof(onl));
	dint ans(0);
	for (int i = 1; i <= n; ++ i)
		if (!vis[i])
			ans += calcCC(i);
	printf(lld "\n", ans);
}

BZOJ1017 [JSOI2008]魔兽地图DotR

数据水。填坑。语文阅读题,看到都不想做。

 

就一树形dp。f[i][j][k]表示第i个物品,上供j个,花k块钱的代价。背包转移是O(m2)的,因为是一棵树所以转移次数是O(n)的,但是还要考虑j最大为100。一个技巧是合并完所有的东西之后再考虑把上供的烧掉。虽然这样的复杂度还是有点高。

 

毕竟我还是太弱啊,居然又花了一个上午。(虽然睡到十点)

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

struct edge {
	int t, v;
	edge *next;
};

const int maxm = 2009;
const int maxn = 53;
const int maxa = 103;

int n, m, f[maxn][maxa][maxm], ans[maxm], vl[maxn], vmax[maxn];
bool lf[maxn], ir[maxn];
edge elst[maxn], *ep(elst), *head[maxn];

inline void addEdge(int u, int v, int w) {
	ep-> t = v;
	ep-> v = w;
	ep-> next = head[u];
	head[u] = ep ++;
}

inline void upMax(int& a, int b) {
	if (a < b)
		a = b;
}

void mergeFunc(int* s, int* a, int* b) {
	static int r[maxm];
	memset(r, -1, sizeof(r));
	for (int i = 0; i <= m; ++ i)
		if (a[i] > -1)
			for (int j = 0; j + i <= m; ++ j)
				if (b[j] > -1)
					upMax(r[i + j], a[i] + b[j]);
	for (int i = 0; i <= m; ++ i)
		s[i] = r[i];
}

void dfsVMax(int p) {
	if (lf[p])
		return;
	vmax[p] = 100;
	for (edge* e = head[p]; e; e = e-> next) {
		dfsVMax(e-> t);
		vmax[p] = min(vmax[p], vmax[e-> t] / e-> v);
	}
}

void dfsDP(int p) {
	if (lf[p])
		return;
	for (int i = 0; i <= vmax[p]; ++ i)
		f[p][i][0] = 0;
	for (edge* e = head[p]; e; e = e-> next) {
		dfsDP(e-> t);
		for (int j = 0; j <= vmax[p]; ++ j)
			mergeFunc(f[p][j], f[p][j], f[e-> t][j * e-> v]);
	}
	for (int j = 1; j <= vmax[p]; ++ j)
		for (int k = 0; k < j; ++ k)
			for (int l = 0; l <= m; ++ l)
				if (f[p][j][l] > -1)
					upMax(f[p][k][l], f[p][j][l] + vl[p] * (j - k));
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d%d", &n, &m);
	memset(head, 0, sizeof(head));
	memset(f, -1, sizeof(f));
	memset(ir, 1, sizeof(ir));
	for (int i = 1; i <= n; ++ i) {
		char ty[3];
		scanf("%d%s", vl + i, ty);
		lf[i] = (ty[0] == 'B');
		if (lf[i]) {
			int b, c;
			scanf("%d%d", &c, &b);
			for (int j = 0; j <= b && c * j <= m; ++ j)
				for (int k = 0; k <= j; ++ k)
					upMax(f[i][k][j * c], (j - k) * vl[i]);
			vmax[i] = b;
		}
		else {
			int t, v, w;
			scanf("%d", &t);
			for (int j = 0; j < t; ++ j) {
				scanf("%d%d", &v, &w);
				addEdge(i, v, w);
				ir[v] = 0;
			}
		}
	}
	for (int i = 1; i <= n; ++ i)
		if (ir[i])
			dfsVMax(i);
	memset(ans, -1, sizeof(ans));
	ans[0] = 0;
	for (int i = 1; i <= n; ++ i)
		if (ir[i]) {
			dfsDP(i);
			mergeFunc(ans, ans, f[i][0]);
		}
	int s(0);
	for (int i = 0; i <= m; ++ i)
		upMax(s, ans[i]);
	printf("%d\n", s);
}

 

 

BZOJ2331 [SCOI2011]地板

插头DP的基础题。想插头DP都是将军没走的时候给我讲的了,距今有大半年了吧。mhy已经写了无数道了,我才开始写。唉,还是效率太低。

 

插头DP的意思是把已经处理的部分的轮廓线上对后面状态有影响的东西状压下来,然后挨着处理每个格子的状态(比如方向,或者这里的地板怎么铺之类的)。

 

具体到这道题来说,考虑一个相邻格子之前的边,那么它可能没有连接两个相同的磁砖,有可能连接了两个还没有拐过弯的磁砖,有可能连接了两个已经拐过弯的磁砖,一共是3种情况,所以用一个三进制数把它压下来。然后直接一路转移过去就行了。要注意的是每行都处理完了之后要把最边上的那一条从右移动到左,所以还需要把所有的状态都改一下。

 

今天效率比较低,心不在焉,然后在最后一个转移的地方wa了大半天。插头DP的调试也比较麻烦。所以我还是naive。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 13;
const int maxa = 109;
const int maxs = 200009;
const int mod = 20110520;

bool mp[maxa][maxa];
int n, m, a[maxn], b[maxn], f[2][maxs];

#define fc f[cur]
#define fp f[prv]
#define doNothing ;
inline void mInc(int&  _a_, int _b_) {
	_a_ += _b_;
	if (_a_ >= mod)
		_a_ %= mod;
}

void unzipStatus(int z, int* a) {
	for (int i = 0; i <= m; ++ i) {
		a[i] = z % 3;
		z /= 3;
	}
}
int zipStatus(int* a) {
	int s(0);
	for (int i = m; i >= 0; -- i)
		s = s * 3 + a[i];
	return s;
}

int plugDP() {
	int prv(0), cur(1), e(1);
	memset(f, 0, sizeof(f));
	fc[0] = 1;
	for (int i = 0; i <= m; ++ i)
		e *= 3;
	for (int i = 0; i < n; ++ i) {
		for (int j = 0; j < m; ++ j) {
			swap(cur, prv);
			for (int z = 0; z < e; ++ z)
				fc[z] = 0;
			for (int z = 0; z < e; ++ z)
				if (fp[z]) {
					unzipStatus(z, a);
					if (mp[i][j]) {
						if (a[j] == 0) {
							if (a[j + 1] == 0) {
								memcpy(b, a, sizeof(a));
								b[j] = b[j + 1] = 2;
								mInc(fc[zipStatus(b)], fp[z]);
								b[j] = 0;
								b[j + 1] = 1;
								mInc(fc[zipStatus(b)], fp[z]);
								b[j] = 1;
								b[j + 1] = 0;
								mInc(fc[zipStatus(b)], fp[z]);
							}
							else if (a[j + 1] == 1) {
								memcpy(b, a, sizeof(a));
								b[j] = 0;
								b[j + 1] = 2;
								mInc(fc[zipStatus(b)], fp[z]);
								b[j] = 1;
								b[j + 1] = 0;
								mInc(fc[zipStatus(b)], fp[z]);
							}
							else if (a[j + 1] == 2) {
								memcpy(b, a, sizeof(a));
								b[j] = 2;
								b[j + 1] = 0;
								mInc(fc[zipStatus(b)], fp[z]);
								b[j] = 0;
								mInc(fc[zipStatus(b)], fp[z]);
							}
						}
						else if (a[j] == 1) {
							if (a[j + 1] == 0) {
								memcpy(b, a, sizeof(a));
								b[j] = 0;
								b[j + 1] = 1;
								mInc(fc[zipStatus(b)], fp[z]);
								b[j] = 2;
								b[j + 1] = 0;
								mInc(fc[zipStatus(b)], fp[z]);
							}
							else if (a[j + 1] == 1) {
								memcpy(b, a, sizeof(a));
								b[j] = b[j + 1] = 0;
								mInc(fc[zipStatus(b)], fp[z]);
							}
							else if (a[j + 1] == 2) {
								doNothing
							}
						}
						else if (a[j] == 2) {
							if (a[j + 1] == 0) {
								memcpy(b, a, sizeof(a));
								b[j] = b[j + 1] = 0;
								mInc(fc[zipStatus(b)], fp[z]);
								b[j + 1] = 2;
								mInc(fc[zipStatus(b)], fp[z]);
							}
							else if (a[j + 1] == 1) {
								doNothing
							}
							else if (a[j + 1] == 2) {
								doNothing
							}
						}
					}
					else {
						if (a[j] == 0 && a[j + 1] == 0)
							mInc(fc[z], fp[z]);
					}
				}
		}
		swap(cur, prv);
		for (int z = 0; z < e; ++ z) 
			fc[z] = 0;
		for (int z = 0; z < e; ++ z) 
			if (fp[z]) {
				unzipStatus(z, a);
				if (a[m])
					continue;
				for (int j = m; j; -- j)
					a[j] = a[j - 1];
				a[0] = 0;
				fc[zipStatus(a)] = fp[z];
			}
	}
	return fc[0];
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d%d", &n, &m);
	bool rev(n < m);
	for (int i = 0; i < n; ++ i) {
		static char g[maxa];
		scanf("%s", g);
		for (int j = 0; j < m; ++ j)
			if (rev)
				mp[j][i] = (g[j] == '_');
			else
				mp[i][j] = (g[j] == '_');
	}
	if (rev)
		swap(n, m);
	printf("%d\n", plugDP());
}

BZOJ3769 SPOJ8549 BST again

什么鬼。

 

很好想的dp。f[i][j]表示深度不超过i且大小为j的二叉树的个数。转移是f[i][j] = ∑(f[i - 1][k] * f[i - 1][j - k - 1])。

 

然后直接暴力是O(n3)的。在spoj上能过在bzoj上不能过。在bzoj上得用记忆化搜索来减掉一些无用的东西。虽然我觉得很烦。

 

然后试图用fft,发现fft比暴力还慢。然后试图用fnt,然后发现fnt好像不能对1e9+7这种质数用。因为1e9+6只有来一个2。真悲伤。我还是太弱啊。

 

#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
 
using namespace std;
 
const int maxn = 609;
const int mod = 1000000007;
 
#define _l (long long int)
 
int f[maxn][maxn];
 
int getf(int i, int j) {
    if (i == 0)
        return j <= 1;
    else if (j == 0)
        return 1;
    else if (f[i][j] == -1) {
        f[i][j] = 0;
        for (int k = 0; k < j; ++ k)
            f[i][j] = (f[i][j] + _l getf(i - 1, k) * getf(i - 1, j - k - 1)) % mod;
    }
    return f[i][j];
}
 
int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif
 
    memset(f, -1, sizeof(f));
    int t, n, k, s;
    scanf("%d", &t);
    while (t --) {
        scanf("%d%d", &n, &k);
        if (!k)
            puts((n == 1) ? "1" : "0");
        else {
            s = getf(k, n) - getf(k - 1, n);
            if (s < 0)
                s += mod;
            printf("%d\n", s);
        }
    }
}

BZOJ3870 Our happy ending

立杰出的题,orz。

 

一个时间比较微妙的状压DP。用一个二进制状态表示0~k中哪些数已经可以被表示出来了,然后大于k的数直接乘,因为对状态不产生影响。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

#define pow2(x) (1<<(x))
#define _l (long long int)

const int maxn = 21;
const int maxb = pow2(maxn) + 9;
const int mod = 1e9 + 7;

int n, k, l, f[2][maxb], zu, va;

#define incm(_a_,_b_) { \
	_a_ +=  _b_; \
	if (_a_ >= mod) \
		_a_ %= mod; \
}

int sov() {
	int prv(0), cur(1);
	memset(f, 0, sizeof(f));
	va = max(0, l - k) + 1;
	zu = pow2(k + 1) - 1;
	f[cur][1] = 1;
	for (int ti = 0; ti < n; ++ ti) {
		swap(cur, prv);
		for (int i = zu; i; -- i)
			f[cur][i] = 0;
		for (int i = zu; i; -- i)
			if (f[prv][i]) {
				for (int j = 1; j <= k; ++ j) {
					int zn((i | (i << j)) & zu);
					incm(f[cur][zn], f[prv][i]);
				}
				incm(f[cur][i], _l f[prv][i] * va % mod);
			}
	}
	int ans(0);
	for (int i = zu, e = pow2(k); i >= e; -- i)
		incm(ans, f[cur][i]);
	return ans;
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	int t;
	scanf("%d", &t);
	while (t --) {
		scanf("%d%d%d", &n, &k, &l);
		printf("%d\n", sov());
	}
}

BZOJ3136 [Baltic2013]brunhilda

决心写一道usaco之外的题,于是就挑中了这道。

 

为啥这编辑器有BUG,每次按回车要向下跳?

 

发现可以预处理DP。f_i表示第i个数被砍完需要最少多少刀。对于每一个i,设p_i是它的质因子里最大的一个在给定集合里的,那么它可以转移到(i+1,i+p_i)这个范围。然后f_i显然不减,所以用单调队列之类的玩意。

 

然后卡着时间限制过了。不知道前面那些秒跑的人是怎么搞的。

 

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int maxn = 10000009;
const int maxm = 100009;

int k[maxn], f[maxn], n, m, q, p[maxm], v[maxm];

void pre(int n) {
	sort(p, p + m);
	memset(k, 0, sizeof(k));
	for (int i = 0; i < m; ++ i)
		if (!i || p[i] != p[i - 1])
			for (int j = 0; j <= n; j += p[i])
				k[j] = p[i];

	static int q[maxn];
	int hd(0), tl(1);
	q[0] = f[0] = 0;
	for (int i = 1; i <= n; ++ i) {
		while (hd < tl && i >= q[hd] + k[q[hd]])
			++ hd;
		if (hd == tl) {
			f[i] = -1;
			continue;
		}
		f[i] = f[q[hd]] + 1;
		while (hd < tl && (f[i] < f[q[tl - 1]] || (f[i] == f[q[tl - 1]] && i + k[i] >= q[tl - 1] + k[q[tl - 1]])))
			-- tl;
		q[tl ++] = i;
	}
}

int main() {
#ifndef ONLINE_JUDGE
	freopen("in.txt", "r", stdin);
#endif

	scanf("%d%d", &m, &q);
	memset(k, 0, sizeof(k));
	for (int i = 0; i < m; ++ i)
		scanf("%d", p + i);
	n = 0;
	for (int i = 0; i < q; ++ i) {
		scanf("%d", v + i);
		if (v[i] > n)
			n = v[i];
	}
	pre(n);
	for (int i = 0; i < q; ++ i)
		if (f[v[i]] == -1)
			puts("oo");
		else
			printf("%d\n", f[v[i]]);
}