您的位置:首页 > 其它

[WorldWide_D幻想乡♂模拟赛][JZOJ4599]西行妖

2016-07-09 20:20 357 查看

Preface

今天射电磷(P5++5e)的模拟赛把大家都虐了~

最后一题个人认为质量很吼,就在这里记录一下。

题目大意

一棵1为根的树n个节点,你最多可以选择S个叶子节点,然后将它们到根节点路径染黑。求有多少种方案能染黑至少m个节点。

1≤m≤n≤1000,1≤S≤20

题目分析

Algorithm 1

最暴力的dp就是设fi,j,k表示以i为根,选择了j个叶子,染黑了k个节点的方案数,转移子树合并乱搞一下即可。

然后Samjia直接使用启发式合并优化了这个算法,合并子树时使用类似合并果子的算法,用一个堆维护合并代价最小的两个块合并,时间复杂度应该是可以证明的。

Algorithm 2

以下是WorldWide_D标解。

令fi,j,k表示统计到第i个叶子节点,已经选择了j个叶子,染黑了k个节点的方案数。

转移方程显然:

fi,j,k=∑fi′,j−1,k−(high(i)−high(lca(i,i′)))

时间复杂度O(n3s)。

比赛时我打了这个并且在实现时使用dfs,然而如果注意到dfs序的性质,可以将其优化。

∀DFN(i′)<DFN(i),显然high(lca(i′,i))随着DFN(i)的增大而不上升。

设当前节点为i,我们令

sj,k,d=∑lca(i′,i)=dfi′,j,k

再令

cntj,k−d=∑sj,k,d

显然对于一个叶子节点fi,j,k=cntj−1,k−high(i),然后更新sj,k,high(i)以及cnt。

然后当退出一棵子树时,我们的i对原本子树中的点取lca深度肯定会加1,因此令原本子树根节点深度为d,我们要将sj,k,d的值加到sj,k,d−1里面,然后清空sj,k,d,同时更新cnt数组。

具体细节比较多,可能会有些抽象,希望读者自行思考。

时间复杂度O(n2s)。

代码实现

#include <iostream>
#include <cstdio>
#include <cctype>
#include <cmath>

using namespace std;

int read()
{
int x=0,f=1;
char ch=getchar();
while (!isdigit(ch)) f=ch=='-'?-1:f,ch=getchar();
while (isdigit(ch)) x=x*10+ch-'0',ch=getchar();
return x*f;
}

const int P=1000000007;
const int N=1005;
const int E=N<<1;
const int S=22;

int fa
,high
,last
,next[E],tov[E],d
,p
,pos
;
int f
[S]
,cnt[S]
,sum[S]

;
bool vis
;
int n,m,s,tot,l,ans;

void insert(int x,int y){tov[++tot]=y,next[tot]=last[x],last[x]=tot;}

void dfs(int x)
{
for (int i=last[x],y;i;i=next[i])
high[y=tov[i]]=high[x]+1,d[x]++,dfs(y);
}

void dp(int x)
{
if (!d[x])
{
p[++l]=x;
f[l][1][high[x]]=1;
for (int j=1;j<=s;j++)
for (int k=1;k<=n;k++)
if (k-high[x]>=0)
(f[l][j][k]+=cnt[j-1][k-high[x]])%=P;
for (int j=1;j<=s;j++)
for (int k=1;k<=n;k++)
{
if (!f[l][j][k]) continue;
(sum[j][k][high[x]]+=f[l][j][k])%=P;
if (k-high[x]>=0) (cnt[j][k-high[x]]+=f[l][j][k])%=P;
}
}
else for (int i=last[x],y;i;i=next[i]) dp(y=tov[i]);
if (x==1) return;
for (int j=1;j<=s;j++)
for (int k=1;k<=n;k++)
{
if (!sum[j][k][high[x]]) continue;
if (k-high[x]>=0) (((cnt[j][k-high[x]]-=sum[j][k][high[x]])%=P)+=P)%=P;
(sum[j][k][high[x]-1]+=sum[j][k][high[x]])%=P;
if (k-high[x]+1>=0) (cnt[j][k-high[x]+1]+=sum[j][k][high[x]])%=P;
sum[j][k][high[x]]=0;
}
}

int main()
{
freopen("tree.in","r",stdin),freopen("tree.out","w",stdout);
n=read(),m=read(),s=read();
for (int i=2;i<=n;i++) fa[i]=read(),insert(fa[i],i);
high[1]=1,dfs(1);
l=0,dp(1),ans=0;
for (int i=1;i<=l;i++)
for (int j=1;j<=s;j++)
for (int k=m;k<=n;k++)
(ans+=f[i][j][k])%=P;
printf("%d\n",ans);
fclose(stdin),fclose(stdout);
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: