【BZOJ5336】【TJOI2018】party

【题目链接】

【思路要点】

  • 考虑最长公共子序列的DP数组,它的每一行只有\(O(K)\)个数,并且相邻两数只差在1以内。
  • 因此,我们可以将每一行压为一个在\(2^K\)以内的二进制数。
  • 预处理每个状态的后续状态,简单DP即可。
  • 需要滚动数组,时间复杂度\(O(N*2^K)\)。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXK = 20;
const int MAXN = 1005;
const int MAXS = 32768;
const int P = 1e9 + 7;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); } 
template <typename T> void read(T &x) {
	x = 0; int f = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
	for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
	x *= f;
}
template <typename T> void write(T x) {
	if (x < 0) x = -x, putchar('-');
	if (x > 9) write(x / 10);
	putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
	write(x);
	puts("");
}
int n, m, bit[MAXK];
int dest[MAXS][3];
int dp[2][3][MAXS], ans[MAXK];
char st[MAXK];
void update(int &x, int y) {
	x += y;
	if (x >= P) x -= P;
}
int main() {
	read(n), read(m);
	scanf("\n%s", st + 1);
	for (int i = 1; i <= m; i++)
		bit[i] = 1 << (i - 1);
	dp[0][0][0] = 1;
	int goal = (1 << m) - 1;
	for (int s = 0; s <= goal; s++) {
		static int now[MAXK];
		for (int i = 1; i <= m; i++)
			now[i] = now[i - 1] + ((bit[i] & s) != 0);
		static int res[MAXK];
		for (int i = 1; i <= m; i++) {
			if (st[i] == 'N') res[i] = now[i - 1] + 1;
			else res[i] = max(res[i - 1], now[i]);
			dest[s][0] |= (res[i] - res[i - 1]) * bit[i];
		}
		for (int i = 1; i <= m; i++) {
			if (st[i] == 'O') res[i] = now[i - 1] + 1;
			else res[i] = max(res[i - 1], now[i]);
			dest[s][1] |= (res[i] - res[i - 1]) * bit[i];
		}
		for (int i = 1; i <= m; i++) {
			if (st[i] == 'I') res[i] = now[i - 1] + 1;
			else res[i] = max(res[i - 1], now[i]);
			dest[s][2] |= (res[i] - res[i - 1]) * bit[i];
		}
	}
	for (int i = 1, now = 1, from = 0; i <= n; i++, swap(now, from)) {
		memset(dp[now], 0, sizeof(dp[now]));
		for (int s = 0; s <= goal; s++) {
			update(dp[now][1][dest[s][0]], dp[from][0][s]);
			update(dp[now][0][dest[s][1]], dp[from][0][s]);
			update(dp[now][0][dest[s][2]], dp[from][0][s]);
			update(dp[now][1][dest[s][0]], dp[from][1][s]);
			update(dp[now][2][dest[s][1]], dp[from][1][s]);
			update(dp[now][0][dest[s][2]], dp[from][1][s]);
			update(dp[now][1][dest[s][0]], dp[from][2][s]);
			update(dp[now][0][dest[s][1]], dp[from][2][s]);
		}
	}
	int now = n & 1;
	for (int i = 0; i <= 2; i++)
	for (int s = 0; s <= goal; s++) {
		int cnt = 0, tmp = s;
		while (tmp != 0) {
			cnt++;
			tmp -= tmp & -tmp;
		}
		update(ans[cnt], dp[now][i][s]);
	}
	for (int i = 0; i <= m; i++)
		writeln(ans[i]);
	return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值