[GDSOI2017]逃亡(状压DP)

[GDSOI2017]逃亡(状压DP)

题面

给出一棵\(n\)个点有向树,只能从父亲走向儿子。每个节点有一个攻击力\(b_i\),如果\(i\)能到达\(j\),且\(b_i>b_j\),则\(i\)会向\(j\)发动\(a_i\)次战争。给出\(b_i\)的范围\([0,m]\)求使得战争发生次数\(\leq K\)\(b_i\)赋值方案数。
\(n \leq 14,K \leq 20,m \leq 10^9\)

分析

看到这么小的数据范围显然考虑状压。

对于这类和权值相关题目,我们可以从小到大对节点赋值,这样当前点的值一定比已经赋值的节点要大,这方便了我们计数。注意\(m\)的范围很大,我们可以考虑离散化后的权值范围\(\min(m,n-1)\)来dp,

不妨设\(f_{\bm{S},i,j}\)表示,发生\(i\)场战斗,点值离散化后填满\([0,j]\)(即每一个值都在节点中存在),已经赋值的点集为\(\bm{S}\),实现上用二进制表示。

那么我们可以枚举\(x \notin \bm{S}\),它的值为\(j\),尝试将\(x\)加入点集.记\(cnt(x)\)为能与\(x\)发生战争的点的个数。
那么可以用刷表法

\[f_{\bm{S} \cup \{ x\},k+cnt(x)\cdot a_x,i} \leftarrow f_{\bm{S} \cup \{ x\},k+cnt(x)\cdot a_x,i}+f_{\bm{S},k,i}+ f_{\bm{S},k,i-1} (k+cnt(x)\cdot a_x \in [1,k]) \]

这是因为x的值是i,最终状态填满\([0,i]\),原来的状态可能填满\([0,i-1]\),也可能已经填满\([0,i]\)

最后统计答案,因为我们离散化了,最后还要还原。离散化的后的\([0,j]\)变成\([0,m]\),就要从\(m+1\)个数里选\(j+1\)个递增的数,方案数为\(C_{m+1}^{j+1}\)

那么结果就是

扫描二维码关注公众号,回复: 10988838 查看本文章
\[ans_i=\sum_{j=0}^{\min(m,n-1)} C_{m+1}^{j+1} \cdot f_{ \{1,2,3,\dots n \},i,j} \]

代码

#include<iostream>
#include<cstdio>
#include<cstring> 
#include<vector>
#include<algorithm>
#include<queue>
#define maxn 14
#define maxk 20
#define mod 1000000007
using namespace std;
typedef long long ll;
inline ll fast_pow(ll x,ll k){
	ll ans=1;
	while(k){
		if(k&1) ans=ans*x%mod;
		x=x*x%mod;
		k>>=1;
	}
	return ans; 
}
inline ll inv(ll x){
	return fast_pow(x,mod-2);
}
inline ll C(int n,int m){
	ll ans=1;
	for(int i=n;i>=n-m+1;i--) ans=ans*i%mod;
	for(int i=1;i<=m;i++) ans=ans*inv(i)%mod;
	return ans;
}

int n,m,K,maxv;
int fa[maxn+5],a[maxn+5];
vector<int>E[maxn+5];
bool is_cn[maxn+5][maxn+5];//cn[i][j]=1表示i能到达j 
int seq[maxn+5];//按拓扑序DP 
void topo_sort(){
	int ptr=0;
	static int in[maxn+5];
	queue<int>q;
	for(int i=2;i<=n;i++) in[i]++; 
	for(int i=1;i<=n;i++) if(!in[i]) q.push(i); 
	while(!q.empty()){
		int x=q.front();
		q.pop();
		seq[++ptr]=x;
		for(int i=0;i<(int)E[x].size();i++){
			int y=E[x][i];
			in[y]--;
			if(in[y]==0) q.push(y);
		}
	} 
}
//按值从小到大给每个节点赋值,注意把值域离散化看成[0,m],就可以放进dp状态 
ll dp[(1<<maxn)][maxk+1][maxn+1];//dp[s][i][j] 产生i场战斗,点值离散化后填满[0,j],已经赋值的点集s(值都<=当前值) 
int main(){
	scanf("%d %d %d",&n,&m,&K);
	maxv=min(n-1,m);//值离散化后的个数 
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=2;i<=n;i++){
		scanf("%d",&fa[i]);
		E[fa[i]].push_back(i);
	} 
	for(int i=1;i<=n;i++) for(int j=i;j;j=fa[j]) is_cn[j][i]=1;
	topo_sort();
	for(int i=1;i<(1<<n);i++) dp[i][0][0]=1;
	for(int i=1;i<=maxv;i++){
		for(int j=1;j<=n;j++){
			int x=seq[j];
			for(int s=0;s<(1<<n);s++){
				if(!(s&(1<<(x-1)))){//尝试往s中加入状态x 
					int sz=0;//当前状态s中x能够到达的个数,那么发生战斗数就是sz*a[x] 
					for(int u=1;u<=n;u++) if(( s&(1<<(u-1)) )&&is_cn[x][u]) sz++; 
					for(int k=0;k+sz*a[x]<=K;k++){
						dp[s|(1<<(x-1))][k+sz*a[x]][i]+=dp[s][k][i]+dp[s][k][i-1];
						//x的值是i,最终状态填满[0,i],原来的状态可能填满[0,i-1],也可能已经填满[0,i] 
						dp[s|(1<<(x-1))][k+sz*a[x]][i]%=mod;
					}
				}
			}
		}
	}
	for(int i=0;i<=K;i++){
		ll ans=0;
		for(int j=0;j<=maxv;j++){
			ans+=C(m+1,j+1)*dp[(1<<n)-1][i][j]%mod; //把值还原,[0,j]变成[0,m],就要从m+1个数理选j+1个递增的数 
			ans%=mod;
		}
		printf("%lld\n",ans);
	}
	
}

猜你喜欢

转载自www.cnblogs.com/birchtree/p/12740280.html