SRM 619

easy: 
如果每堆石头不全为1,那么每次我们总能取一堆石头分给另外两堆,堆数-1,并且新的局面肯定有一堆的个数大于1。
于是,如果每堆石头数都为1 -> lose,否则的话判断堆数奇偶即可(堆数==1要特判)

medium:
树形dp,dp[i][j],表示以i为根的子树分配完成,并且i节点会j技能的最小花费。
转移:枚举i点在自己的department所用的技能(k),然后用子节点的dp值建图,跑KM。
得到最小花费后。枚举i节点会的另一个技能(r),算出总花费然后更新dp[i][k]和dp[i][r]。

hard:
可以把这题也看成那种dp套dp的题目来做。。。

先看一个naive版本的题目:给两个串,问是否能找到一种方案,两个串都删除两个位置的数,使得剩下的两个串相同。
可以用这样的dp表示方式来做:dp[i][3][3],表示A串可以匹配完前i个元素,并且A串已经删除了j个元素,B串已经删除了k个元素。dp值存的0/1,表示这种状态可不可达。
(这个看上去比较奇怪但是他是可以work的。。

那么这道题可以一边假设两个串元素的值,一边做上面的dp。
把上面后两维压成一个数9,然后做dp[i][1<<9]的dp,i还是上面的定义,1<<9表示对于目前构造的串是否能达到这9种状态。
当然仅仅通过A串的i位置元素和B串的i位置元素是没法推这样的dp的。可以记录一下临近的元素的相互关系(记录临近的4个B串元素的相互关系即可)
dp[i][1<<9][k],k是记录b[i-2],b[i-1],b[i],b[i+1]的相互关系(用最小表示法,然后哈希一下)

递推:
枚举b[i+2],然后再枚举a[i],通过1<<9中每个合法状态,用前面naive版本的dp,得到新的1<<9的状态,然后b[i-1]~b[i+2]哈希成新的k。dp[i+1][newj][newk]+=dp[i][j][k]*add,如果b[i+2]和a[i]都是选择b[i-2]~b[i+1]中出现过的数,add就是1,否则add是选择的方案数。


--------------------------------分割线---------------------------------------

naive版本的dp也可以这样子:dp[i][5]=A串删除最小次数,5表示A串和B串目前相差几个字母(-2~2)
这样我们1<<9的状态可以缩减成2*3*4*3*2(大概这么多)
但其实1<<9的写法如果写的姿势比较好,也差不多这些状态(没用状态会continue掉)

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

class SimilarSequencesAnother {
public:
	int getCount(int, int);
};

long long pmod = 1000000009;
long long dp[105][1 << 9][100];
int Hash[5][5][5][5];
int rHash[10005][4];
int ed = 0;
int add(int a[]) {
	if (Hash[a[0]][a[1]][a[2]][a[3]] != -1)
		return Hash[a[0]][a[1]][a[2]][a[3]];
	Hash[a[0]][a[1]][a[2]][a[3]] = ed;
	for (int i = 0; i < 4; ++i)
		rHash[ed][i] = a[i];
	ed++;
	return ed - 1;
}
void get(int id, int a[]) {
	for (int i = 0; i < 4; ++i)
		a[i] = rHash[id][i];
}
int tmp[5], tt[4], flag[10];
int SimilarSequencesAnother::getCount(int N, int bound) {
	int i, j, k, r;
	int jj, kk, p;
	long long cost1, cost2;
	long long m = bound;
	memset(dp, 0, sizeof(dp));
	memset(Hash, -1, sizeof(Hash));
	ed = 0;
	tt[0] = 0;tt[1] = 0;tt[2] = 0;tt[3] = 0;
	add(tt);
	dp[0][1][0] = m;
	if (m >= 2) {
		tt[0] = 0;tt[1] = 0;tt[2] = 0;tt[3] = 1;
		add(tt);
		dp[0][1][1] = m * (m - 1) % pmod;
	}
	for (i = 0; i < N; ++i) {
		for (j = 0; j < (1 << 9); ++j) {
			for (k = 0; k < ed; ++k) {
				if (dp[i][j][k] == 0)
					continue;
				get(k, tmp);
				int pn = max(tmp[0], max(tmp[1], max(tmp[2], tmp[3]))) + 1;
				for (tmp[4] = 0; tmp[4] <= pn; ++tmp[4]) {

					//把tmp[1]->tmp[4]复制到tt,并变成最小表达
					memset(flag, -1, sizeof(flag));
					int ttn = 0;
					for (r = 0; r < 4; ++r) {
						if (flag[tmp[r + 1]] == -1) {
							flag[tmp[r + 1]] = ttn++;
						}
						tt[r] = flag[tmp[r + 1]];
					}
					////////////////////////////////////////
					if (tmp[4] == pn)
						cost1 = m - pn;
					else
						cost1 = 1;
					if (cost1 <= 0)
						continue;
					kk = add(tt);
					for (p = 0; p <= max(tmp[4] + 1, pn); p++) {//枚举A串当前字符
						if (p == max(tmp[4] + 1, pn))
							cost2 = m - max(tmp[4] + 1, pn);
						else
							cost2 = 1;
						if (cost2 <= 0)
							continue;
						jj = 0;
						for (r = 0; r < 9; ++r) {
							if (j & (1 << r)) {
								for (int movea = 0; movea < 2; ++movea)
									for (int moveb = 0; moveb < 3; ++moveb) {
										int xx = r % 3 + movea;
										int yy = r / 3 + moveb;
										if (xx > 2 || yy > 2)
											continue;
										if (movea) {
											jj |= (1 << (yy * 3 + xx));
											continue;
										}
										if (p == tmp[2 - xx + yy])
											jj |= (1 << (yy * 3 + xx));
									}
							}
						}
						dp[i + 1][jj][kk] += dp[i][j][k] * cost1 % pmod * cost2
								% pmod;
						dp[i + 1][jj][kk] %= pmod;
					}
				}
			}
		}
	}
	long long ans = 0;
	for (j = 1; j < (1 << 9); ++j) {
		for (k = 0; k < ed; ++k) {
			if (rHash[k][2] == 0 && rHash[k][3] == 0) {
				ans += dp[N][j][k];
				ans %= pmod;
			}
		}
	}
	return ans;
}


你可能感兴趣的:(topcoder)