NOI模拟(5.21) TJOID1T3 party (bzoj5336)

Party

题目背景:

5.21 模拟 TJOI2018D1T3

分析:状压DP

 

我们定义dp[i][stats]表示,枚举到兑奖串第i位,当前每一个长度的子序列的最小终点为stats的方案数,解释一下后面半句,对于一个串,若最长的匹配子序列是n,那么对于长度1 ~ n的匹配子序列都有至少一个,我们定义一个子序列的终点为,这个子序列最后一个数所在的位置,考虑每一种长度的子序列,每一种可行方案的当中终点最小的那个一定是最优的,显然这个最小终点随着子序列长度递增而严格递增,那么我们可以直接将这些位置设为1其他设为0表示为一个二进制状态,我们预处理每一个状态stats加上一个字符之后可以到达的状态to[stats][0/1/2],那么转移就是dp[i + 1][to[stats][0/1/2]]+= dp[i][stats],然后发现要求原串中不能出现NOI这个子串,那么直接再加一维表示最后两位的情况,如果是NNONIN为情况1NO为情况2,其他都是情况0,然后对应限制一下就可以了。复杂度O(n * 2k* 9)6秒还是比较轻松的。

 

Source:

 

/*
	created by scarlyw
*/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

inline char read() {
	static const int IN_LEN = 1024 * 1024;
	static char buf[IN_LEN], *s, *t;
	if (s == t) {
		t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
		if (s == t) return -1;
	}
	return *s++;
}

/*
template
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = read(), iosig = false; !isdigit(c); c = read()) {
		if (c == -1) return ;
		if (c == '-') iosig = true;	
	}
	for (x = 0; isdigit(c); c = read()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}
//*/

const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
	if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
	*oh++ = c;
}

template
inline void W(T x) {
	static int buf[30], cnt;
	if (x == 0) write_char('0');
	else {
		if (x < 0) write_char('-'), x = -x;
		for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
		while (cnt) write_char(buf[cnt--]);
	}
}

inline void flush() {
	fwrite(obuf, 1, oh - obuf, stdout);
}

// /*
template
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = getchar(), iosig = false; !isdigit(c); c = getchar()) {
		if (c == -1) return ;
		if (c == '-') iosig = true;	
	}
	for (x = 0; isdigit(c); c = getchar()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}
// */

const int MAXN = 1000 + 10;
const int MAXM = 20;
const int MAXK = 15;
const int mod = 1000000000 + 7;

inline void add(int &x, int t) {
    x += t, (x >= mod) ? (x -= mod) : (x);
}

int n, k;
int to[1 << MAXK | 1][3], pos[MAXM], trans[3][3], cnt[1 << MAXK | 1];
int dp[2][1 << MAXK | 1][3], a[MAXM], ans[MAXM];
char s[MAXM];

inline void solve() {
    R(n), R(k), scanf("%s", s + 1);
    for (int i = 1; i <= k; ++i)
        a[i] = (s[i] == 'N') ? 0 : (s[i] == 'O' ? 1 : 2);
    for (int i = 0, end = (1 << k); i < end; ++i) {
        for (int j = 0; j < 3; ++j) {
            int cnt = 0, x;
            for (int l = 1; l <= k; ++l) pos[l] = 0;
            for (int l = 1; l <= k; ++l) {
                x = std::max(cnt + ((i >> l - 1) & 1), cnt + (j == a[l])); 
                pos[x] = (pos[x] ? pos[x] : l), cnt += ((i >> l - 1) & 1);
                //x表示,以当前位置为结尾的最长可行长度
				//pos[x]表示这个长度的最小终点 
            }
            for (int l = 1; l <= k; ++l) 
                if (pos[l] != 0) to[i][j] |= (1 << pos[l] - 1);
        } 
    }
    trans[0][0] = trans[1][0] = trans[2][0] = 1;
    trans[1][1] = 2, trans[2][2] = -1, dp[0][0][0] = 1;
    for (int i = 0, cur = 0; i < n; ++i, 
        memset(dp[cur], 0, sizeof(dp[cur])), cur ^= 1)
            for (int j = 0, end = (1 << k); j < end; ++j)
                for (int k = 0; k < 3; ++k)
                    if (dp[cur][j][k]) {
                        for (int l = 0; l < 3; ++l)
                            if (trans[k][l] >= 0)
                                add(dp[cur ^ 1][to[j][l]][trans[k][l]], 
                                    dp[cur][j][k]);
                    }
    for (int i = 0, end = (1 << k); i < end; ++i) {
        cnt[i] = cnt[i >> 1] + (i & 1);
        add(ans[cnt[i]], dp[n & 1][i][0]), add(ans[cnt[i]], dp[n & 1][i][1]);
        add(ans[cnt[i]], dp[n & 1][i][2]);
    }
    for (int i = 0; i <= k; ++i) std::cout << ans[i] << '\n';
}

int main() {
    freopen("party.in", "r", stdin);
    freopen("party.out", "w", stdout);
    solve();
    return 0;
}

你可能感兴趣的:(NOIP解题报告,状态压缩DP)