欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

#NOIP模拟赛#相似字符串(树形DP + 状压)

程序员文章站 2022-07-01 10:38:49
...

#NOIP模拟赛#相似字符串(树形DP + 状压)

#NOIP模拟赛#相似字符串(树形DP + 状压)

#NOIP模拟赛#相似字符串(树形DP + 状压)

#NOIP模拟赛#相似字符串(树形DP + 状压)

这题是一个状压树DP,有思路可以先想一下,不是特别难(但是我作为一个蒟蒻理解标程看了相当久才彻底想清楚,我的树DP太弱了)

标解写得有点模糊,其实也很清楚,但是我还是想要说一下我对这题思路的理解。

首先是预处理出前缀(我是暴力的,这个可以优化)。题中给出的限制条件很显然是树或者森林。

那么只需要对于有限制的那些点,计算出可能的方案数,其它的可以随便标号。

对于有限制的点,题中限制最多只有16个,用一个int的二进制来表示每一位一定是足够的。

定义Dp[r][S] 表示以r点作为根的子树中,已有标号为状态S的总方案数

对于每一个点,那一位上是0表示这个标号现在不存在(即不在现在这个树中) 1表示在(已经把此树中某点标号)

预先标记出非法的状态,每一个r的转移如下:

假设当前枚举到状态S,那么f[u][S]有两种转移情况

1:S中的1全部标在了r的子树中,那么分成两部分,对于每一棵子树来做,具体看算法一

2:S中的1有一个标在r处,其它的标在子树中,此时枚举限制条件,将前缀标号标在r处。


贴出标解:

#NOIP模拟赛#相似字符串(树形DP + 状压)

#NOIP模拟赛#相似字符串(树形DP + 状压)

#NOIP模拟赛#相似字符串(树形DP + 状压)

#NOIP模拟赛#相似字符串(树形DP + 状压)

Code:(本人代码,由于本人蒟蒻,,就不写C++11那个版本了,因为感觉自己用不上那个??)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

const int MOD = 1e9 + 7;

struct ST{
    int len;
    char val[55];
    bool operator < (const ST & X) const{
        return strcmp(val, X.val) < 0;
    }
}S[55];
struct node{
    int v, nxt;
}edge[(1 << 16) + 5];

int N, M, cnt, Ans, All, State;
int A[10], B[10], fa[55], T[20];
int fir[55], Constrait[55];
int Dp[55][(1 << 16) + 5];
bool Avoid[(1 << 16) + 5];

bool getint(int & num){
    char c; int flg = 1;    num = 0;
    while((c = getchar()) < '0' || c > '9'){
        if(c == '-')    flg = -1;
        if(c == -1) return 0;
    }
    while(c >= '0' && c <= '9'){
        num = num * 10 + c - 48;
        if((c = getchar()) == -1)   return 0;
    }
    num *= flg;
    return 1;
}

void addedge(int a, int b){
    edge[++ cnt].v = b, edge[cnt].nxt = fir[a], fir[a] = cnt;
}

void Pre_Work(){
    sort(S, S + N);
    for(int i = 0; i < N; ++ i){
        int MAX = 0;
        for(int j = 0; j < i; ++ j) if(S[i].len > S[j].len){
            bool flg = 1;
            for(int k = 0; k < S[j].len; ++ k)
                if(S[i].val[k] != S[j].val[k]){
                    flg = 0;break;
                }
            if(flg){
                if(MAX < S[j].len)
                    MAX = S[j].len,
                    fa[i + 1] = j + 1;
            }
        }
    }
    memset(fir, -1, sizeof fir );
    for(int i = 1; i <= N; ++ i)
        addedge(fa[i], i);
    State = 0;
    for(int i = 0; i < M; ++ i)//离散化限制条件编号
        T[++ State] = A[i], T[++ State] = B[i];
    sort(T + 1, T + 1 + State);
    State = unique(T + 1, T + 1 + State) - T - 1;
    All = 1 << State;
    for(int i = 0; i < M; ++ i)//对于以Ai为前缀的限制,Constrait[i]表示所有限制的标号(那一位记为1,注意:只要不出现X的前缀是X,那么i这一位是0)
        Constrait[lower_bound(T + 1, T + State + 1, A[i]) - T - 1] |= 1 << (lower_bound(T + 1, T + State + 1, B[i]) - T - 1);
    for(int i = 0; i < All; ++ i)
        for(int j = 0; j < State; ++ j)
            if((i & (1 << j)) && (Constrait[j] & i) != Constrait[j]){
                Avoid[i] = 1;
                break;//break的原因是:这里是判i非法,而只要有一个j令i非法,i就非法
            }
}

void Get_Dp(int R){
    Dp[R][0] = 1;
    for(int i = fir[R]; i != -1; i = edge[i].nxt){//枚举R的每一棵子树
        int v = edge[i].v;
        Get_Dp(v);
        for(int j = All - 1; j >= 0; -- j)  if(! Avoid[j])//枚举状态,要求合法
            for(int k = j; k; k = (k - 1) & j)//将当前合法状态中的1分成两部分,一部分放入v这棵子树中,另一部分放入R的其他子树中
                Dp[R][j] = (Dp[R][j] + 1ll * Dp[R][k ^ j] * Dp[v][k]) % MOD;
				//(注意:这里一定保证前缀(Constrait)合法,不会计算bi和ai被放成兄弟关系的情况,因为非法的划分时Dp[v][k] = 0)
    }
    if(R)
        for(int j = All - 1; j >= 0; -- j)  if(! Avoid[j])
            for(int k = 0; k < State; ++ k)//再次枚举状态,此时处理的是:将R编号标为k,其它的限制在R的子树中(此时前缀关系成立)
                if((j & (1 << k)) && (Constrait[k] & j) == Constrait[k])
                    Dp[R][j] = (Dp[R][j] + Dp[R][j ^ (1 << k)]) % MOD;
}

int main(){
    freopen("similar.in", "r", stdin);
    freopen("similar.out", "w", stdout);
    getint(N);
    for(int i = 0; i < N; ++ i)    scanf("%s", S[i].val), S[i].len = strlen(S[i].val);
    getint(M);
    for(int i = 0; i < M; ++ i)    getint(A[i]);
    for(int i = 0; i < M; ++ i)    getint(B[i]);
    Pre_Work();
    Get_Dp(0);
    Ans = Dp[0][All - 1];
    for(int i = 1; i <= N - State; ++ i)
        Ans = 1ll * Ans * i % MOD;
    printf("%d\n", Ans);
    return 0;
}



给出标程:

(普通C++版):

#include 

using namespace std;

#define Look(...) fprintf(stderr, __VA_ARGS__)

template 
void Setmax(TAT &a, const TAT &b) 
{
    if (a < b) a = b;
}
template 
void Setmin(TAT &a, const TAT &b)
{
    if (a > b) a = b;
}

typedef long long LL;

#define MAXN  55
#define MOD   1000000007
#define MAXS  ((1 << 16) + 15)

int n;
int fa[MAXN];
struct edge
{
    int to, next;
    edge() {}
    edge(int _t, int _n):to(_t), next(_n) {}
}e[MAXN];
int head[MAXN], et = -1;
int lst[MAXN], lt = 0, N;
int constrait[MAXN];
int f[MAXN][MAXS];
bool ban[MAXS] = {0};

void Addedge(int a, int b)
{
    e[++et] = edge(b, head[a]), head[a] = et;
}

void Dfs(int p)//树形Dp
{
    static int *a, *b;
    memset(f[p], 0, sizeof(f[p]));
    f[p][0] = 1;
    for (int i = head[p]; i != -1; i = e[i].next) {
        Dfs(e[i].to);
        a = f[p];
        b = f[e[i].to];
        for (int j = N - 1; j >= 0; --j) {//枚举子集,合并子树
            if (!ban[j]) {
                for (int k = j; k != 0; k = (k - 1) & j) {
                    a[j] = (a[j] + (LL)a[j ^ k] * b[k]) % MOD;
                }
            }
        }
    }
    if (p != 0) {
        for (int j = N - 1; j >= 0; --j) {//枚举根节点
            for (int k = 0; k < lt; ++k) {
                if (((j >> k) & 1) && (constrait[k] & j) == constrait[k]) {
                    (f[p][j] += f[p][j ^ (1 << k)]) %= MOD;
                }
            }
        }
    }
}

int Solve()
{
    int ans = 0;
    Dfs(0);
    ans = f[0][(1 << lt) - 1];
    for (int i = 1; i <= n - lt; ++i) {//乘上其他位置的阶乘
        (ans = (LL)ans * i % MOD) %= MOD;
    }
    return ans;
}

int Count(vector names, vector info1, vector info2) {
    n = names.size();
    memset(fa, -1, sizeof(fa));
    memset(head, -1, sizeof(head));
    memset(ban, 0, sizeof(ban));
    sort(names.begin(), names.end());
    et = -1;
    for (int i = 0; i < n; ++i) {//处理前缀关系树
        int Max = 0;
        fa[i + 1] = 0;
        for (int j = 0; j < n; ++j) {
            if (i == j || names[i].length() <= names[j].length()) continue;
            bool ok = 1;
            for (int k = 0; k < (int)names[j].length(); ++k) {
                if (names[i][k] != names[j][k]) {
                    ok = 0;
                    break;
                }
            }
            if (ok) {
                if (Max < (int)names[j].length()) {
                    Max = (int)names[j].length();
                    fa[i + 1] = j + 1;
                }
            }
        }
    }
    for (int i = 1; i <= n; ++i) {//建树
        Addedge(fa[i], i);
    }
    lt = 0;
    memset(constrait, 0, sizeof(constrait));
    for (int i = 0; i < (int)info1.size(); ++i) {//离散化受限制位置
        lst[++lt] = info1[i];
        lst[++lt] = info2[i];
    }
    sort(lst + 1, lst + 1 + lt);
    lt = unique(lst + 1, lst + 1 + lt) - lst - 1;
    N = (1 << lt);
    for (int i = 0; i < (int)info1.size(); ++i) {//将每个位置的前缀位置压位
        constrait[lower_bound(lst + 1, lst + 1 + lt, info1[i]) - lst - 1] |= (1 << (lower_bound(lst + 1, lst + 1 + lt, info2[i]) - lst - 1));
    }
    for (int i = 0; i < N; ++i) {//处理不合法状态
        for (int j = 0; j < lt; ++j) {
            if ((i & (1 << j)) && (i & constrait[j]) != constrait[j]) {
                ban[i] = 1;
                break;
            }
        }
    }
    return Solve();
}

int main() {
	vector names;
	vector info1, info2;
	int n, m, t;
	string x;
	
	cin >> n;
	for(int i = 0; i < n; i++) {
		cin >> x;
		names.push_back(x);
	} 
	cin >> m;
	for(int i = 0; i < m; i++) {
		cin >> t;
		info1.push_back(t);
	}
	for(int i = 0; i < m; i++) {
		cin >> t;
		info2.push_back(t);
	}
	int Ans = Count(names, info1, info2);
	cout << Ans << '\n';
	return 0;
}

(C++11版):

#include  

using namespace std;  

#define ui unsigned
#define ll long long

#define pii std::pair
#define mp std::make_pair
#define fi first
#define se second

#define SZ(x) (int)(x).size()
#define pb push_back

templateinline void chkmax(T &x, const T &y) {if(x < y) x = y;}
templateinline void chkmin(T &x, const T &y) {if(x > y) x = y;}

const int N = 50, M = 16;
const int MOD = 1000000007;

int n, m, pn, p[M + 9];
std::vector adj[N + 9];
std::vector vld;
bool ok[1 << M];
std::vector sub[1 << M];
int f[N + 9][1 << M], g[1 << M];

bool prefix(std::string a, std::string b) {// 判断a是否是b的前缀
	return SZ(a) < SZ(b) && b.substr(0, SZ(a)) == a;
}

bool cmp(const std::string &a, const std::string &b) {// 判断a的长度是否 str, vector  a, vector  b) {
	n = SZ(str), m = SZ(a);
	std::sort(str.begin(), str.end(), cmp);// 根据长度排序后,某个字符串的父亲就是最长的是它的前缀的串
	for(int i = 0; i < n; ++i) {
		int fa = n;// 如果没找到,那就连到一个新的空节点
		for(int j = i - 1; j >= 0; --j)
			if(prefix(str[j], str[i])) {// 找到最长的前缀
				fa = j;
				break;
			}
		adj[fa].pb(i);
	}
	// 离散化a和b
	for(auto i : a) p[++pn] = i;
	for(auto i : b) p[++pn] = i;
	std::sort(p + 1, p + pn + 1);
	pn = std::unique(p + 1, p + pn + 1) - p - 1;
	for(int i = 0; i < m; ++i) {
		a[i] = std::lower_bound(p + 1, p + pn + 1, a[i]) - p - 1;
		b[i] = std::lower_bound(p + 1, p + pn + 1, b[i]) - p - 1;
	}
	// 将所有的合法状态提出来,因为如果a是b的前缀,那么a在树中的位置一定是b的位置的祖先
	for(int i = 0; i < (1 << pn); ++i) {
		bool f = true;
		for(int j = 0; j < m; ++j)
			if((i >> a[j] & 1) && !(i >> b[j] & 1)) {// 不可能出现只有a而没有b的情况
				f = false;
				break;
			}
		if(f) vld.pb(i), ok[i] = true;// ok[i]表示i是否合法,vld存储了所有合法的状态
	}
	for(auto s : vld)// 将每个合法状态的合法子状态提出来
		for(int t = s; ; t = (t - 1) & s) {
			if(ok[t] && ok[s - t]) sub[s].pb(t);
			if(!t) break;
		}
	dp(n);
	int ans = f[n][(1 << pn) - 1];
	for(int i = 1; i <= n - pn; ++i) ans = (ll)ans * i % MOD;// 剩下的n - pn个数随意放
	return ans;
}

int main() {
	vector names;
	vector info1, info2;
	int n, m, t;
	string x;
	
	cin >> n;
	for(int i = 0; i < n; i++) {
		cin >> x;
		names.push_back(x);
	} 
	cin >> m;
	for(int i = 0; i < m; i++) {
		cin >> t;
		info1.push_back(t);
	}
	for(int i = 0; i < m; i++) {
		cin >> t;
		info2.push_back(t);
	}
	int Ans = Count(names, info1, info2);
	cout << Ans << '\n';
	return 0;
}