状压dp 练习

文章介绍了几个与棋盘问题和算法相关的题目,包括国王的排列以避免攻击、学校食堂的做菜调度优化、字符串匹配的计数问题以及中国象棋的马的布局问题。每个问题都涉及动态规划和状态转移的思路,以及如何优化计算效率。
摘要由CSDN通过智能技术生成

[SCOI2005] 互不侵犯

题目描述

在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

注:数据有加强(2018/4/25)

输入格式

只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N)

输出格式

所得的方案数

样例 #1

样例输入 #1

3 2

样例输出 #1

16

分析 - AC

定义 d p ( i , j , u ) dp(i,j,u) dp(i,j,u),前i行,放了j个国王,第i行状态为u,方案数。
转移: d p ( i , j , u ) = ∑ d p ( i − 1 , j − c n t ( u ) , v ) dp(i,j,u)=\sum{dp(i-1,j-cnt(u),v)} dp(i,j,u)=dp(i1,jcnt(u),v),其中行状态u与v不矛盾。
时间复杂度 O ( n k 2 2 n ) O(nk2^{2n}) O(nk22n),应该过不了。

单独的一行内,国王不能相攻。仅考虑这一点,预处理出每行合法方案数最多是89。每行状态只在其中选。
同时也可以预处理出每个u能转移到的不矛盾的v。
时间复杂度降低为约 O ( 5774409 ) O(5774409) O(5774409). 空间也能优化(离散化),这里不写。

int n, k;
ll ans, dp[MAXN][MAXK][MAXSTA];
vector<int> g[MAXSTA];

void dfs(int k, int u, int v) {
	if (k >= n) {
		g[u].push_back(v);
		return;
	}
	dfs(k + 1, u, v);
	if (!k) {
		if (!(u & 3)) dfs(k + 1, u, v | 1);
	}
	else if (!(v >> k - 1 & 1) && !(u >> k - 1 & 7)) dfs(k + 1, u, v | 1 << k);
}

int main() {
	setIO("");
	
	scanf("%d%d", &n, &k);
	
	for (int i = 0; i < (1 << n); ++i) {
		dfs(0, i, 0);
	}
//	cout << g[0].size();

	dp[0][0][0] = 1;
	for (int i = 1; i <= n; ++i) {
		for (int u : g[0]) {
			for (int v : g[u]) {
				int cu = __builtin_popcount(u), cv = __builtin_popcount(v);
				for (int j = cv; j + cu <= k; ++j) {
					dp[i][j + cu][u] += dp[i - 1][j][v];
				}
			}
		}
	}
	for (int i : g[0]) {
		ans += dp[n][k][i];
	}
	printf("%lld\n", ans);
	
	return 0;
}

邦邦的大合唱站队

题目背景

BanG Dream!里的所有偶像乐队要一起大合唱,不过在排队上出了一些问题。

题目描述

N个偶像排成一列,他们来自M个不同的乐队。每个团队至少有一个偶像。

现在要求重新安排队列,使来自同一乐队的偶像连续的站在一起。重新安排的办法是,让若干偶像出列(剩下的偶像不动),然后让出列的偶像一个个归队到原来的空位,归队的位置任意。

请问最少让多少偶像出列?

输入格式

第一行2个整数N,M。

接下来N个行,每行一个整数 a i ( 1 ≤ a i ≤ M ) a_i(1\le a_i \le M) ai(1aiM),表示队列中第i个偶像的团队编号。

输出格式

一个整数,表示答案

样例 #1

样例输入 #1

12 4
1
3
2
4
2
1
2
3
1
1
3
4

样例输出 #1

7

提示

【样例解释】

1  33  3
2  34  4
2  41  22  2
3  21  1
1  1
3  14  1

【数据规模】

对于20%的数据, N ≤ 20 , M = 2 N\le 20, M=2 N20,M=2

对于40%的数据, N ≤ 100 , M ≤ 4 N\le 100, M\le 4 N100,M4

对于70%的数据, N ≤ 2000 , M ≤ 10 N\le 2000, M\le 10 N2000,M10

对于全部数据, 1 ≤ N ≤ 1 0 5 , M ≤ 20 1\le N\le 10^5, M\le 20 1N105,M20

分析 - AC

比较初始序列与最终序列,要出列的就是两者不同的位置。所以就是求最终序列与初始序列的最少不同位置。

m很小,状压。定义 d p ( i ) dp(i) dp(i),让某些乐队从下标为1的位置开始排好队(乐队顺序不定),最少不同点。显然具备最优解子结构。
d p ( i ) = d p ( v ) + v h ( j ) − s ( j , l , r ) . dp(i)=dp(v)+vh(j)-s(j,l,r). dp(i)=dp(v)+vh(j)s(j,l,r). 其中状态v是从状态i中去掉一个乐队j, v h ( j ) vh(j) vh(j)表示乐队j的人数, v h ( j ) − s ( j , l , r ) vh(j)-s(j,l,r) vh(j)s(j,l,r)表示乐队j被放到v后面引起的不同点增量,用前缀和预处理。

scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) {
	int a; scanf("%d", &a); --a;
	++vh[a];
	for (int j = 0; j < m; ++j) {
		s[j][i] = vh[j];
	}
}

//  预处理也可以
//	cnt[0] = 0;
//	for (int i = 1; i < (1 << m); ++i) {
//		for (int j = 0; j < m; ++j) {
//			if (i >> j & 1) {
//				cnt[i] = cnt[i ^ 1 << j] + vh[j];
//				break;
//			}
//		}
//	}

memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for (int i = 1; i < (1 << m); ++i) {
	int cnt = 0;
	for (int j = 0; j < m; ++j) {
		if (i >> j & 1) cnt += vh[j];
	}
	for (int j = 0; j < m; ++j) {
		if (i >> j & 1) {
			int v = i ^ (1 << j);
			ckmin(dp[i], dp[v] + vh[j] - (s[j][cnt] - s[j][cnt - vh[j]]));
		}
	}
}
printf("%lld\n", dp[(1 << m) - 1]);

[SDOI2009] 学校食堂

题目描述

小F 的学校在城市的一个偏僻角落,所有学生都只好在学校吃饭。学校有一个食堂,虽然简陋,但食堂大厨总能做出让同学们满意的菜肴。当然,不同的人口味也不一定相同,但每个人的口味都可以用一个非负整数表示。 由于人手不够,食堂每次只能为一个人做菜。做每道菜所需的时间是和前一道菜有关的,若前一道菜的对应的口味是a,这一道为b,则做这道菜所需的时间为(a or b)-(a and b),而做第一道菜是不需要计算时间的。其中,or 和and 表示整数逐位或运算及逐位与运算,C语言中对应的运算符为“|”和“&”。

学生数目相对于这个学校还是比较多的,吃饭做菜往往就会花去不少时间。因此,学校食堂偶尔会不按照大家的排队顺序做菜,以缩短总的进餐时间。

虽然同学们能够理解学校食堂的这种做法,不过每个同学还是有一定容忍度的。也就是说,队伍中的第i 个同学,最多允许紧跟他身后的Bi 个人先拿到饭菜。一旦在此之后的任意同学比当前同学先拿到饭,当前同学将会十分愤怒。因此,食堂做菜还得照顾到同学们的情绪。 现在,小F 想知道在满足所有人的容忍度这一前提下,自己的学校食堂做完这些菜最少需要多少时间。

输入格式

第一行包含一个正整数C,表示测试点的数据组数。 每组数据的第一行包含一个正整数N,表示同学数。 每组数据的第二行起共N行,每行包含两个用空格分隔的非负整数Ti和Bi,表示按队伍顺序从前往后的每个同学所需的菜的口味和这个同学的忍受度。 每组数据之间没有多余空行。

输出格式

包含C行,每行一个整数,表示对应数据中食堂完成所有菜所需的最少时间。

样例 #1

样例输入 #1

2
5
5 2
4 1
12 0
3 3
2 2
2
5 0
4 0

样例输出 #1

16
1

提示

对于第一组数据:

同学1允许同学2或同学3在他之前拿到菜;同学2允许同学3在他之前拿到菜;同学3比较小气,他必须比他后面的同学先拿菜……

一种最优的方案是按同学3、同学2、同学1、同学4、同学5做菜,每道菜所需的时间分别是0、8、1、6及1。

【数据规模和约定】

对于30%的数据,满足1 ≤ N ≤ 20。

对于100%的数据,满足1 ≤ N ≤ 1,000,0 ≤ Ti ≤ 1,000,0 ≤ Bi ≤ 7,1 ≤ C ≤ 5。

存在30%的数据,满足0 ≤ Bi ≤ 1。

存在65%的数据,满足0 ≤ Bi ≤ 5。

存在45%的数据,满足0 ≤ Ti ≤ 130。

分析 - 68pts

b最大是7,值得注意。
这意味着,如果某个状态下,没吃到饭的排在最前面的人是i,那么i-1及之前的一定吃过了,i和i+8及之后的一定没吃过,不确定的只有 [ i + 1 , i + 7 ] [i+1,i+7] [i+1,i+7],可以状压为u. 因为做饭时间与上一道菜有关,有后效性,所以还要加一维k,表示上一道菜的口味。
定义状态 f ( i , u , k ) f(i,u,k) f(i,u,k). 转移:下一个吃到菜的人是 [ i , i + 7 ] [i,i+7] [i,i+7]中的某一个,这同时收到b的限制。我用记忆化搜索来写。

时间复杂度 O ( c n t 2 7 ) O(cnt2^7) O(cnt27).

const int MAXB = 5;//部分分数据
const int MAXN = 1005, MAXSTA = (1 << MAXB), MAXT = 1005;

int tt, n, t[MAXN], b[MAXN], dp[MAXN][MAXSTA][MAXT];

int lowzero(int i) {
	for (int j = 0; j < MAXB; ++j) {
		if (!(i >> j & 1)) return j;
	}
	return MAXB;
}
int cost(int i, int j) {
	if (i == -1) return 0;
	return (i | j) - (i & j);
}

int f(int i, int u, int k) {
	if (i > n) return 0;
	if (dp[i][u][k] != -1) return dp[i][u][k];
	int nxt = lowzero(u) + 1, lmt = i + b[i]; ckmin(lmt, n);
	int res = f(i + nxt, u >> nxt, t[i]) + cost(k, t[i]);
	for (int j = 0; i + j + 1 <= lmt; ++j) {
		if (!(u >> j & 1)) {
			ckmin(res, f(i, u | 1 << j, t[i + j + 1]) + cost(k, t[i + j + 1]));
			ckmin(lmt, i + j + 1 + b[i + j + 1]);
		}
	}
	return dp[i][u][k] = res;
}

int main() {
	scanf("%d", &tt);
	
	while (tt--) {
		memset(dp, -1, sizeof(dp));
		
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i) {
			scanf("%d%d", t + i, b + i);
		}
		
		printf("%d\n", f(1, 0, -1));
	}
	
	return 0;
}

优化状态定义 - AC

i和u应该没法优化。k,上一道菜的口味,取决于上一个吃到菜的人。
已知i-1已经吃到了菜,那么上一个吃到菜的人不可能是i-9及之前。同时也不可能是i+8及之前。所以这个人在 [ i − 8 , i + 7 ] [i-8,i+7] [i8,i+7]内。
时间复杂度 O ( 14 c n 2 7 ) O(14cn2^7) O(14cn27).

int lowzero(int i) {
	for (int j = 0; j < MAXB; ++j) {
		if (!(i >> j & 1)) return j;
	}
	return MAXB;
}
int cost(int i, int j) {
	return (i | j) - (i & j);
}

int f(int i, int u, int k) {
	if (i > n) return 0;
	if (dp[i][u][k + MAXB + 1] != -1) return dp[i][u][k + MAXB + 1];
	int nxt = lowzero(u) + 1, lmt = i + b[i]; ckmin(lmt, n);
	int res = f(i + nxt, u >> nxt, -nxt) + cost(t[i + k], t[i]);
	for (int j = 0; i + j + 1 <= lmt; ++j) {
		if (!(u >> j & 1)) {
			ckmin(res, f(i, u | 1 << j, j + 1) + (k ? cost(t[i + k], t[i + j + 1]) : 0));
			ckmin(lmt, i + j + 1 + b[i + j + 1]);
		}
	}
//	return res; 这行原本忘注释了,TLE
	return dp[i][u][k + MAXB + 1] = res;
}

int main() {
	scanf("%d", &tt);
	
	while (tt--) {
		memset(dp, -1, sizeof(dp));
		
		scanf("%d", &n);
		for (int i = 1; i <= n; ++i) {
			scanf("%d%d", t + i, b + i);
		}
		
		printf("%d\n", f(1, 0, 0));
	}
	
	return 0;
}

[SDOI2009] Bill的挑战

题目描述

Sheng_bill 不仅有惊人的心算能力,还可以轻松地完成各种统计。在昨天的比赛中,你凭借优秀的程序与他打成了平局,这导致 Sheng_bill 极度的不满。于是他再次挑战你。这次你可不能输。

这次,比赛规则是这样的:

给出 N N N 个长度相同的字符串(由小写英文字母和 ? 组成), S 1 , S 2 , … , S N S_1,S_2,\dots,S_N S1,S2,,SN,求与这 N N N 个串中的刚好 K K K 个串匹配的字符串 T T T 的个数,答案对 1000003 1000003 1000003 取模。

若字符串 S x ( 1 ≤ x ≤ N ) S_x(1\le x\le N) Sx(1xN) T T T 匹配,满足以下条件:

  1. ∣ S x ∣ = ∣ T ∣ |S_x|=|T| Sx=T
  2. 对于任意的 1 ≤ i ≤ ∣ S x ∣ 1\le i\le|S_x| 1iSx,满足 S x [ i ] = ? S_x[i]= \texttt{?} Sx[i]=? 或者 S x [ i ] = T [ i ] S_x[i]=T[i] Sx[i]=T[i]

其中 T T T 只包含小写英文字母。

输入格式

本题包含多组数据

第一行一个整数 T T T,表示数据组数。

对于每组数据,第一行两个整数, N N N K K K

接下来 N N N 行,每行一个字符串 S i S_i Si

输出格式

每组数据输出一行一个整数,表示答案。

样例 #1

样例输入 #1

5

3 3

???r???

???????

???????

3 4

???????

?????a?

???????

3 3

???????

?a??j??

????aa?

3 2

a??????

???????

???????

3 2

???????

???a???

????a??

样例输出 #1

914852

0

0

871234

67018

提示

数据规模与约定

  • 对于 30 % 30\% 30% 的数据, N ≤ 5 N\le5 N5 ∣ S i ∣ ≤ 20 |S_i|\le20 Si20
  • 对于 70 % 70\% 70% 的数据, N ≤ 13 N\le13 N13 ∣ S i ∣ ≤ 30 |S_i|\le30 Si30
  • 对于 100 % 100\% 100% 的数据, 1 ≤ T ≤ 5 1\le T\le 5 1T5 1 ≤ N ≤ 15 1\le N \le15 1N15 1 ≤ ∣ S i ∣ ≤ 50 1\le|S_i|\le50 1Si50

分析 - AC

定义 d p ( i , j ) dp(i,j) dp(i,j),只考虑字符串的前i个字符(为了递推),匹配的情况被压缩为j.
考虑第i位填什么,共有26种可能,每种与n个字符串第i位的匹配情况trans是固定的。从第i-1位转移到第i位,多一个字母,匹配的字符串个数只有可能减,不可能增,后面对前面无影响, d p ( i , u & t r a n s ) = ∑ d p ( i − 1 , u ) dp(i,u\&trans)=\sum dp(i - 1,u) dp(i,u&trans)=dp(i1,u).

scanf("%d%d", &n, &k);
for (int i = 0; i < n; ++i) {
	scanf("%s", s[i] + 1);
} m = strlen(s[0] + 1);

dp[0][(1 << n) - 1] = 1;
for (int i = 1; i <= m; ++i) {
	for (int j = 1; j <= 26; ++j) {
		int trans = 0;
		for (int k = 0; k < n; ++k) {
			if (s[k][i] == '?' || s[k][i] == 'a' + j - 1) trans |= 1 << k;
		}
		for (int u = 0; u < (1 << n); ++u) {
			plusmod(dp[i][u & trans], dp[i - 1][u]);
		}
	}
}
for (int i = 0; i < (1 << n); ++i) {
	if (__builtin_popcount(i) == k) plusmod(ans, dp[m][i]);
}
printf("%d\n", ans);

yyy loves Maths VII

题目背景

yyy 对某些数字有着情有独钟的喜爱,他叫他们为幸运数字;然而他作死太多,所以把自己讨厌的数字成为“厄运数字”。

题目描述

一群同学在和 yyy 玩一个游戏。

每次,他们会给 yyy n n n 张卡片,卡片上有数字,所有的数字都是“幸运数字”,我们认为第 i i i 张卡片上数字是 a i a_{i} ai

每次 yyy 可以选择向前走 a i a_{i} ai 步并且丢掉第 i i i 张卡片。当他手上没有卡片的时候他就赢了。

但是呢,大家对“厄运数字”的位置布置下了陷阱,如果 yyy 停在这个格子上,那么他就输了。注意:即使到了终点,但是这个位置是厄运数字,那么也输了。

现在,有些同学开始问:yyy 有多大的概率会赢呢?

大家觉得这是个好问题,有人立即让 yyy 写个程序:“电脑运行速度很快! 24 24 24 的阶乘也不过就 620   448   401   733   239   439   360   000 620\,448\,401\,733\,239\,439\,360\,000 620448401733239439360000,yyy 你快写个程序来算一算。”

yyy 表示很无语,他表示他不想算概率,最多算算赢的方案数,而且是对 1 0 9 + 7 10^9+7 109+7 取模后的值。

大家都不会写程序,只好妥协。

但是这时候 yyy 为难了, 24 ! 24! 24! 太大了,要跑好长时间。

他时间严重不够!需要你的帮助!

由于 yyy 人格分裂,某个数字可能既属于幸运数字又属于厄运数字。

输入格式

第一行一个整数 n n n

下面一行 n n n 个整数,第 i i i 个整数代表第 i i i 张卡片上的数字 a i a_i ai

第三行 m m m 表示 yyy 的厄运数字个数(最多 2 2 2 个)。

输出格式

输出胜利方案数对 1 0 9 + 7 10^9+7 109+7 取模的结果。

样例 #1

样例输入 #1

8
1 3 1 5 2 2 2 3
0

样例输出 #1

40320

样例 #2

样例输入 #2

24
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
2
10 15

样例输出 #2

0

提示

  • 10 % 10\% 10% 的数据 n ≤ 10 n \leq 10 n10
  • 50 % 50\% 50% 的数据 n ≤ 23 n \leq 23 n23
  • 100 % 100\% 100% 的数据 n ≤ 24 n \leq 24 n24

分析 - 50pts

状态:卡牌使用情况。
算法复杂度 O ( n 2 n ) O(n2^n) O(n2n) n = 24 n=24 n=24需要常数优化。

dp[0] = 1;
for (int i = 1; i < (1 << n); ++i) {
	int u = 0;
	for (int j = 0; j < n; ++j) {
		if (i >> j & 1) u += a[j];
	}
	if (u == b[1] || u == b[2]) continue;
	for (int j = 0; j < n; ++j) {
		if (i >> j & 1) {
			int v = i ^ 1 << j;
			dp[i] = (dp[i] + dp[v]) % MOD;
		}
	}
}
printf("%d\n", dp[(1 << n) - 1]);

lowbit枚举1 - AC

转移时枚举已经用过的卡牌,可以用lowbit加速,类似于树状数组。

dp[0] = 1;
for (int i = 1; i < (1 << n); ++i) {
	int j = i;
	if (!u[i]) u[i] = u[lowbit(i)] + u[i ^ lowbit(i)];
	if (u[i] == b[1] || u[i] == b[2]) continue;
	while (j) {
		int lb = lowbit(j);
		dp[i] = (dp[i] + dp[i ^ lb]) % MOD;
		j ^= lowbit(j);
	}
}

[九省联考 2018] 一双木棋 chess

题目描述

菲菲和牛牛在一块 n n n m m m 列的棋盘上下棋,菲菲执黑棋先手,牛牛执白棋后手。

棋局开始时,棋盘上没有任何棋子,两人轮流在格子上落子,直到填满棋盘时结束。

落子的规则是:一个格子可以落子当且仅当这个格子内没有棋子且这个格子的左侧及上方的所有格子内都有棋子。

棋盘的每个格子上,都写有两个非负整数,从上到下第 i i i 行中从左到右第 j j j 列的格子上的两个整数记作 a i , j a_{i,j} ai,j b i , j b_{i,j} bi,j

在游戏结束后,菲菲和牛牛会分别计算自己的得分:菲菲的得分是所有有黑棋的格子上的 a i , j a_{i,j} ai,j 之和,牛牛的得分是所有有白棋的格子上的 b i , j b_{i,j} bi,j 的和。

菲菲和牛牛都希望,自己的得分减去对方的得分得到的结果最大。现在他们想知道,在给定的棋盘上,如果双方都采用最优策略且知道对方会采用最优策略,那么,最终的结果如何?

输入格式

第一行有两个整数,分别表示棋盘的行数 n n n 和列数 m m m
2 2 2 到第 ( n + 1 ) (n + 1) (n+1) 行,每行 m m m 个整数,第 ( i + 1 ) (i + 1) (i+1) 行的第 j j j 个整数表示 a i , j a_{i, j} ai,j
( n + 2 ) (n + 2) (n+2) 到第 ( 2 n + 1 ) (2n + 1) (2n+1) 行,每行 m m m 个整数,第 ( n + i + 1 ) (n + i + 1) (n+i+1) 行的第 j j j 个整数表示 b i , j b_{i, j} bi,j

输出格式

输出一行一个整数,表示菲菲的得分减去牛牛的得分的结果。

样例 #1

样例输入 #1

2 3
2 7 3
9 1 2
3 7 2
2 3 1

样例输出 #1

2

提示

样例 1 说明

棋盘如图所示,双方都采用最优策略时,棋局如下:

  • 菲菲下在第 1 1 1 行第 1 1 1 列(这是第一步时唯一可以落子的格子)。
  • 牛牛下在第 1 1 1 行第 2 2 2 列。
  • 菲菲下在第 2 2 2 行第 1 1 1 列。
  • 牛牛下在第 1 1 1 行第 3 3 3 列。
  • 菲菲下在第 2 2 2 行第 2 2 2 列。
  • 牛牛下在第 2 2 2 行第 3 3 3 列(这是这一步时唯一可以落子的格子)。
  • 填满棋盘,游戏结束。

盘面如下:

菲菲的得分为 2 + 9 + 1 = 12 2 + 9 + 1 = 12 2+9+1=12,牛牛的得分为 7 + 2 + 1 = 10 7 + 2 + 1 = 10 7+2+1=10

数据规模与约定

各测试点信息如下表。

  • 对于编号为奇数的测试点,保证 b i , j = 0 b_{i, j} = 0 bi,j=0
  • 对于全部的测试点,保证 1 ≤ n , m ≤ 10 1 \leq n, m \leq 10 1n,m10 0 ≤ a i , j , b i , j ≤ 1 0 5 0 \leq a_{i, j}, b_{i, j} \leq 10^5 0ai,j,bi,j105

分析 - AC

dp,状态只需要记录当前那些格子被占了。
每一列上已有的棋子可被写成一个不递增序列,搜索得到状态数最多是184756个(开空间时可以多开,以防算错),时间够用。
记搜。为了方便,函数传参:一个字符串表示各行状态,如x220表示第一列不能再放棋子,第二三列放了两个棋子,第四列还没有棋子;k表示当前执棋者,可以根据字符串算出来,但递归时传递更简便。
记忆化用的数组与函数不同,只需要以字符串映射到的编号为索引。

int n, m, a[2][MAXN][MAXM], dp[MAXSTA];
map<string, int> vh;

int f(string s, int k) {
	if (s[m - 1] == 'x') return 0;
	if (vh.find(s) == vh.end()) vh[s] = vh.size();
	if (dp[vh[s]] != INF) return dp[vh[s]];
	int res = -INF;
	char tmp;
	for (int i = 0; i < m; ++i) {
		if (!i && s[i] != 'x' || i && s[i - 1] > s[i]) {
			tmp = s[i]; ++s[i]; 
			int nx = s[i] - '0'; if (nx == n) s[i] = 'x';
			ckmax(res, a[k][nx][i] - f(s, k ^ 1));
			s[i] = tmp;
		}
	}
	return dp[vh[s]] = res;
}

int main() {
	setIO("");
	
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) {
		for (int j = 0; j < m; ++j) {
			cin >> a[0][i][j];
		}
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 0; j < m; ++j) {
			cin >> a[1][i][j];
		}
	}
	
	memset(dp, 0x3f, sizeof(dp));
	cout << f("0000000000", 0) << '\n';
	
	return 0;
}

中国象棋 - 摆上马

题目背景

相信自己的做法 大喊一声 I won’t MLE!您就会过这道题

Imakf 玩腻了国际象棋,决定玩一玩中国象棋。

他发现中国象棋的马和国际象棋的马有所不同,他意识到这又可以出一道简单的问题,于是他又准备摆一摆马了

题目描述

Imakf 有一个 X X X Y Y Y 列的棋盘,还有很多完全相同的马(你可以认为有无数个)。现在在棋盘上摆上马(或者不摆),求任何马无法攻击另一匹马的方案总数。

中国象棋的马和国际象棋的马不同。

注意:实际问题中是没有兵的。

当然由于方案可能过多,请输出对 ( 1 0 9 + 7 ) (10^9+7) (109+7) 取模的值

输入格式

第一行两个正整数 X , Y X,Y X,Y

输出格式

方案对 ( 1 0 9 + 7 ) (10^9+7) (109+7) 取模的值。

样例 #1

样例输入 #1

1 1

样例输出 #1

2

样例 #2

样例输入 #2

3 3

样例输出 #2

145

提示

对于 100% 的数据,有 1 ≤ X ≤ 100 1\le X\leq100 1X100 1 ≤ Y ≤ 6 1\le Y\leq6 1Y6

对于 20% 的数据,有 X , Y ≤ 6 X,Y\leq6 X,Y6

对于另外 20% 的数据,有 X ≤ 20 X\leq20 X20

对于样例 1,可以选择不摆或者摆。

对于样例 2,我有一个绝妙的解释可惜我写不下。

分析 - AC

定义状态 d p ( i , u , v ) dp(i,u,v) dp(i,u,v),前i行,最后一行状态为u,倒数第二行状态为v. 转移到 d p ( i − 1 , v , w ) dp(i-1,v,w) dp(i1,v,w).

需要判断u与v、u与w是否合法。下面u与v被讨论,u与w同理。
要求:位置上能互相攻击的两个马(分别在u和v中)中间有另外两个马构成保护。
u&v中的1就是构成保护的位。
u>>1 & v<<1 | u<<1 & v>>1中的1就是需要保护的位。
不能有“需要保护且没有保护”的位。将u&v取反后,1就是没有保护的位。
所以写出u与v合法条件!((i >> 1 & j << 1 | i << 1 & j >> 1) & ~(i & j)).

时间复杂度 O ( x 2 y ) O(x2^y) O(x2y),滚动数组优化后空间复杂度 O ( 2 y ) O(2^y) O(2y). 可以预处理对u合法的v.

for (int i = 0; i < (1 << y); ++i) {
	for (int j = 0; j < (1 << y); ++j) {
		if (!((i >> 1 & j << 1 | i << 1 & j >> 1) & ~(i & j))) g[i].push_back(j);
	}
}

dp[0][0][0] = 1;
for (int i = 1; i <= x; ++i) {
	memset(dp[i & 1], 0, sizeof(dp[i & 1]));
	for (int u = 0; u < (1 << y); ++u) {
		for (int v : g[u]) {
			for (int w = 0; w < (1 << y); ++w) {
				if (!((w << 1 & u | u >> 1 & w | w >> 1 & u | u << 1 & w) & ~v)) {
					plusmod(dp[i & 1][u][v], dp[!(i & 1)][v][w]);
				}
			}
		}
	}
}
for (int i = 0; i < (1 << y); ++i) {
	for (int j : g[i]) {
		plusmod(ans, dp[x & 1][i][j]);
	}
}
printf("%d\n", ans);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值