逃亡「GDOI2017」

【题目描述】
兔兔和蛋蛋是一对cp,她们的国家目前正发生战乱,所以她们正在决定是否一起逃离这个国家。

该国家有(n)个城市,城市之间的道路形成一棵有向树。只能从父节点城市走到子节点城市。(1)号城市是首都,从城市(1)可以到达所有城市。每个城市都有一支军队,编号为(i)的城市其军队的攻击力为(b_i),如果城市(i)能到达城市(j),且(b_i > b_j)(城市 i 军队的攻击力大于城市 j 军队的攻击力),则城市(i)会向城市(j)发动(a_i)次战争。

兔兔和蛋蛋这对cp决定当战争发生次数之和多于(k)的时候一起逃离这个国家,但是他们现在不知道各个城市的攻击力(b_i),只知道(0 leq b_i leq m)。她们想知道该国家发生战争次数恰好为(0, 1,dots, k) 的方案数(两个方案不同当且仅当存在一个城市(i)(b_i)的值不同),你能帮助她们吗?

【输入格式】
第一行三个整数(n,m,k),意义如上所述。

第二行(n)个数(a_i),意义如上所述。

第三行(n-1)个数,第(i)个数表示第(i + 1)个城市的父节点城市编号。

【输出格式】
(k + 1)行,第(i)行表示战争发生次数为(i-1)的方案数,答案可能很大,你只需要输出答案模(10^9 + 7)之后的值就可以了。

(0 < n leq 14, k leq 20, 0 leq m leq 100000000, 0 leq a_i, b_i leq m)

题解

看到(nle 14)就八成是状压DP了

容易发现我们只关心(n)个点之间的大小关系,至于(m)的限制最后乘上组合数即可

所以我们可以考虑这样一种DP策略

开始时所有点都没有被赋值(指(b_i)) 然后我们按照值从小到大的顺序一个一个地给它们赋值 这样每次赋值的点都比之前赋了值的所有点要大(或等于) 这样的话就方便进行转移

我们设(g[i][S])表示 (S)为已赋值的点的集合 此时将(i)点赋值 会新产生多少战争

由于是按照从小到大的顺序赋值的 所以(b_i)肯定比(S)中所有点都大 所以(g[i][S])就等于 (S)中在(i)子树里的点的数目 乘 (a_i)

但是还存在一个问题 万一两个点的值一样怎么办?这个其实也不难解决

我们设(f[i][j][S])表示已赋值的点有(i)个不同的值 有(j)场战争 已赋值的点集是(S)的方案数

最外层循环是(i) 然后按照dfs序来枚举一个要赋值的点(x) 我们打算把(x)赋值为(i)

然后枚举加入(x)后有多少场战争以及一个包含(x)的集合(S) 那么去掉(x)后就是(S_0=S-2^x)

首先 (b_x)可以比之前的所有点都大 即(f[i][j][S] += f[i-1][j-g[x][S_0]][S_0])

然后 也可能前面已经加入了值也是(i)的点 所以(f[i][j][S] += f[i][j-g[x][S_0]][S_0])

由于我们是按照dfs序枚举的 所以第二种情况中一定没有(x)子树中的点先被赋值成(i) 所以这个转移是没有问题的

最后统计答案 发动(k)次战争的方案数就是(sumlimits_{i=1}^{n} f[i][k][2^n-1] * C(m,i))
(C(m,i))就是我们要从(m)个可选的值里取(i)个来用

时间复杂度(O(n^22^nk))

更新:忘记放代码了QAQ

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

inline int read() {
	int x = 0, f = 1; char ch = getchar();
	for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1; 
	for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ '0');
	return x * f; 
}

const ll mod = 1000000007;
int n, m, mx;
int head[20], pre[40], to[40], sz;
int dfn[20], rnk[20], out[20], a[20], tme;
int f[15][21][(1<<15)], g[20][(1<<15)]; 
ll fac[20], inv[20];
//"点x"表示dfs序第x位的点 
//f[i][j][k] 共有i个不同的b[x] 会发动j次战争 k为已经确定b[x]的点的状压 
//g[i][k] k为已经确定b[x]的点的状压 确定b[i]会新增加多少战争

void addedge(int u, int v) {
	pre[++sz] = head[u]; head[u] = sz; to[sz] = v;
	pre[++sz] = head[v]; head[v] = sz; to[sz] = u;
}

ll calc(int l, int r) {
	ll ret = 1;
	for (int i = l; i <= r; i++) ret = ret * i % mod;
	return ret;
}

inline ll C(int _n, int _m) {
	return calc(_n - _m + 1, _n) * inv[_m] % mod;
}

inline ll fpow(ll x, ll t) {
	ll ret = 1;
	for (; t; t >>= 1, x = x * x % mod) if (t & 1) ret = ret * x % mod;
	return ret;
}

void dfs(int x, int fa) { //确定dfs序 
	dfn[x] = ++tme; rnk[tme] = x;
	for (int i = head[x]; i; i = pre[i]) {
		if (to[i] != fa) dfs(to[i], x);
	}
	out[x] = tme;
}

int main() {
	fac[0] = inv[0] = 1;
	for (int i = 1; i <= 14; i++) fac[i] = fac[i-1] * i % mod;
	for (int i = 1; i <= 14; i++) inv[i] = fpow(fac[i], mod - 2);
	n = read(); m = read()+1; mx = read();
	for (int i = 1; i <= n; i++) a[i] = read();
	for (int i = 2; i <= n; i++) {
		addedge(read(), i);
	}
	dfs(1, 0); 
	for (int i = 0; i <= (1 << n) - 1; i++) {
		for (int j = 1; j <= n; j++) {
			int x = rnk[j];
			if (!(i & (1 << (j-1)))) {
				for (int k = dfn[x] + 1; k <= out[x]; k++) {
					if (i & (1 << (k-1))) g[j][i] += a[x];
				}
			}
		}
	}
	f[0][0][0] = 1;
	for (int i = 1; i <= min(n, m); i++) { //枚举有i个不同的b[x]的取值 
		for (int x = 1; x <= n; x++) { //枚举要确定b[x]的值
			for (int j = 0; j <= mx; j++) { //将会有j场战争 
				for (int k = (1 << (x-1)); k <= (1 << n) - 1; k = ((k + 1) | (1 << (x-1)))) { //枚举一个包含j的状态压缩 
					int now = j - g[x][k-(1<<(x-1))]; //加入x前有now场战争
					if (now < 0) continue;
					f[i][j][k] = (f[i][j][k] + f[i-1][now][k-(1<<(x-1))]) % mod;
					f[i][j][k] = (f[i][j][k] + f[i][now][k-(1<<(x-1))]) % mod; 
				} 
			}
		} 
	} 
	for (int i = 0; i <= mx; i++) {
		ll ans = 0;
		for (int j = 1; j <= min(n, m); j++) {
			ans = (ans + f[j][i][(1<<n)-1] * C(m, j) % mod) % mod;
		}	
		printf("%lld
", ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/ak-dream/p/AK_DREAM66.html