[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);
}
}