[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; }
相关文章推荐
- Maven配置
- NYOJ 55 懒省事的小明 优先队列
- android页面跳转
- leetcode: Reverse Words in a String
- iOS-高德地图
- 线性表之顺序存储结构--C实现
- linux驱动头文件位置的说明
- javascript浏览器对象
- 7.9学习问题整理
- java安全性和数据库设计注意事项
- SDKD 2016 Summer Single Contest #03.G
- frambuffer机理
- js实现股票实时刷新数据
- js (function(){…})()等立即执行写法的理解
- <机器学习>自用笔记
- LISTVIEW嵌套GRIDVIEW的一些处理
- java后台调用 SAP RFC 第二种方法
- 敲一下enter键,完成iOS的打包工作
- 面向对象软件工程复习之极限编程和RUP习题
- 欢迎使用CSDN-markdown编辑器