【题解】LOJ2462完美的集合(树DP 魔改Lucas)

省选模拟考这个???????????????????

题目大意:

有一棵树,每个点有两个属性,一个是重量\(w_i\)一个是价值\(v_i\)。我们称一个点集\(S\)合法当且仅当

  • 该集合是一个联通块\(\qquad (1)\)
  • 该集合的所有点的重量和\(\le m\),输入中给定\(m\),\(\qquad (2)\)
  • 该集合的所有点的价值和是(全局)所有可能的价值和中最大的\(\qquad (3)\)

我们称一个点集的集合\(B=\{S_i\}\)合法当且仅当

  • \(|B|=k\),输入中给定\(k\),\(\qquad (4)\)
  • 存在一个点\(x\),对于所有\(S\in B\)有\(x \in S\)。\(\qquad (5)\)
  • 对于上述点\(x\),存在一个\(x\)对于所有\(S\in B\),对于所有\(y\in S\)有\(\mathrm{dis}(x,y)\times v_y\le Max\)。输入中给定\(Max\)。\(\qquad (6)\)

请输出不同的\(B\)的个数,答案对\(5^{23}\)取模。

\(n\le 60,m\le 10000,k,w_i,v_i\le 10^9,Max\le 10^{18}\)

分两个部分解决问题因为这道题是二合一。


考虑找到所有包含\(x\)的\(S\)们。这些\(S\)的并集在树上构成了一个联通块,由于我们确定了一个\(x\),所以每个点\(y\)是否在\((6)\)中合法是确定的,因此我们从原树上扣出一个和\(x\)联通的联通块(记为树\(T_x\)),在这个上面找到所有的\(S\)。

沿用这个博客t2的一些方法https://www.cnblogs.com/winlere/protected/p/11788856.html

外校的同学可能打不开,这里摘抄过来

定义一下二元组的运算:

\(e_1=(x_1,y_1),e_2=(x_2,y_2)\),设\(u=\max\{x_1,x_2\}\)

\[e_1+e_2=(u,[x_1=u]y_1+[x_2=u]y_2)
\]

值得注意的是这个东西满足结合律和交换律

我们枚举一个\(x\)并且令其为根,设\(dp(i,j)=(a,b)\),表示\(i\)是\(S\)中最浅的点,\(S\)的重量和是\(j\),且价值和是\(a\),这样的\(S\)的方案数是\(b\)。(因为x是根,也就是当前最浅的点,所以\(dp(x,j)\)的含义就是树上所有包含\(x\)的合法的\(S\)的情况了)

二元组的运算法则和链接里面那道题是一样的。

所以我们到此满足了\((1),(2),(3),(5),(6)\)的限制,条件\((3)\)关于全局的限制可以把所有\(x\)枚举完之后得到最大值再统计答案。

考虑如何转移\(dp\),直接树上\(DP\)的复杂度是\(O(nm^2)\)的。这是因为在儿子转移父亲的时候做了一个完全背包,但是我们实际上是01背包,然后题解给出了一个办法可以做成01背包

状态改为\(dp(i,j)\)表示考虑前\(i\)个dfs序,选择的联通块的价值为j,转移顺序改变一下,按照\(T_x\)的dfs序的倒序转移,转移是(注意这里的加法是上面定义的!):

  • 选择该dfs序的点\(dp(i,j)=dp(i,j)+ dp(i+1,j-w_{now})\),其中\(now\)表示当前点的编号。(记得更新二元组的x)
  • 不选择该dfs序的点\(dp(i,j)=dp(i,j)+dp(i+siz[now],j)\),\(now\)的意思同上。\(+siz[now]\)表示跳过一整个子树(因为这个点我们不选,又由于\(S\)是要和\(x\)(根)联通,所以要跳过整颗子树。这样子DP对于一个点,要么选,要么不选它的整颗子树,可以保证最终\(dp\)到根上的方案都是合法的,相当于从最近的考虑过了的点转移过来)

这样我们就做了一个01背包了,转移只要for一个j,不需要for第二个j。复杂度\(O(nm)\)可以接受

现在我们可以得到\(h_x\)表示包含\(x\)的\(S\)的方案数。问\(B\)的方案,只要满足\((4)\)那么直接就是\(h_x\choose k\)了。

但是这里有些问题,对于一个\(B\),可能有多个\(x\)使得它合法。设这个\(x\)的集合为\(|X|\),一个方案我们总共算了\(|X|\)次。怎么去重?

然后题解给出一个性质

对于一个\(B\),使得这个\(B\)合法的\(x\)的集合\(|X|\)在树上构成一个联通块

假设一条链\(1--2--3\),\(1,3\)可以但是\(2\)不行,这种情况不可能存在

因为:

  • 对于\(1\)的子树,既然\(3\)行,\(2\)为啥不行,距离还短些。
  • 对于\(2\)的子树,既然\(1,3\)行为啥\(2\)不行,距离还短些。
  • 对于\(3\)的子树,既然\(1\)行,\(2\)为啥不行,距离还短些。

因此\(X\)是树上一个联通块

然后由于一个联通块,点的个数=边的个数+1,因此去重就有思路了,设\(h_{e=(a,b)}\)表示必须\(ab\)点都是可以作为\(x\)的所有合法的\(S\)的个数,求\(h_e\)的方法和\(h_x\)一样,扣出来的树是\(T_a\cap T_b\),按dfs倒序转移的时候判断下\(now==i\),如果是这样就强制转移"选择"的情况。

那么答案就是

\[\sum_i {h_i \choose k} -\sum_{e=(a,b)} {h_e \choose k}
\]

这样对于一个\(B\),我们算了\(|X|-(|X|-1)=1\)次。

到这里复杂度\(O(n^2m)\)

二合一的第一部分完结,现在问题就是求一个组合数。。。。


显然\(h\le 2^{60}\)拿long long存下,现在要求一个\(n,m\)都很大的组合数,模\(5^{23}\)。

考虑exLucas。这里只需要求\(p^i\)下的组合数,然而\(exLucas\)是\(O(p^i)\)的,搞不得。

然而考虑这里的瓶颈是啥,其实是求\(g(n)\)的时候我们是暴力求循环节\(O(p^i)\),然后给出一个不暴力的做法....

搞个生成函数

\[f_n(x)=\prod_{5\not \mid i}^n (x+i)
\]

所以\(g(n)=f_n(0)\)。

然后考虑\(f\)的倍增...

\[f_{10k}(x)=f_{5k}(x)f_{5k}(x+5k)
\]

对于\(f_{5k}(x)\),\(x^{>23}\)次都是无意义的,因为系数都有一个\(5^{23}\)。而最终我们只需要求\(f_{n}(0)\)(也就是常数项),所以对于任何\(f_{t}(x)\),我们只需要保留前\(24\)项。

求那么\(f_{10k}\)可以递归到\(f_{5k}\),问题规模缩小了一半。问题是\(f_{5k}(x)\)不一定能递归下去,其实只要递归到\(\le 5k\)最近的\(10\)的倍数即可,再暴力乘上最多\(9\)项形如\((x+i)\)的式子。处理\(f_n(x)\)也是同样的办法。

因为多项式长度是常数(24),所以复杂度是\(T(n)=T(n/2)+\text{不大不小的常数}=O(\text{不大不小的常数}\log n)\)

一个坑点是,"最大价值和",没有x的合法限制,所以要单独DP出来....

什么叫二合一啊(战术仰头)

  1. //@winlere
  2. #include<iostream>
  3. #include<cstdio>
  4. #include<cstring>
  5. #include<algorithm>
  6. #include<vector>
  7. using namespace std; typedef long long ll;
  8. typedef vector<ll> poly;
  9. int n;
  10. ll m,k,Max_val;
  11. const ll mod=11920928955078125;
  12. template<class T> T MOD(const T&a){return a>=mod?a-mod:a;}
  13. template<class T> T MOD(const T&a,const T&b){return (__int128)a*b%mod;}
  14. ll ksm(ll ba,ll p){
  15. ll ret=1;
  16. while(p){
  17. if(p&1) ret=MOD(ret,ba);
  18. ba=MOD(ba,ba);
  19. p>>=1;
  20. }
  21. return ret;
  22. }
  23. const ll phi=ksm(5,22)*4;
  24. const int SS=24;
  25. poly operator * (poly a,poly b){
  26. if(a.empty()||b.empty()) return poly(24,0);
  27. poly ret(a.size()+b.size()-1,0);
  28. for(int t=0,ed=a.size();t<ed;++t)
  29. for(int i=0,ed2=b.size();i<ed2;++i)
  30. ret[t+i]=MOD(ret[t+i]+MOD(a[t],b[i]));
  31. return ret.resize(min<int>(SS,ret.size())),ret;
  32. }
  33. namespace C{
  34. ll cnt(ll n){
  35. ll ret=0;
  36. while(n) ret+=n/5,n/=5;
  37. return ret;
  38. }
  39. poly PP[101];
  40. ll ch[SS+1][SS+1];
  41. poly fac(ll n){
  42. if(n<=100) return PP[n];
  43. ll k=n/10*10,w=1;
  44. poly f=fac(k/2),ret(f.size(),0);
  45. for(int t=0,ed=f.size();t<ed;++t,w=MOD<ll>(w,k/2))
  46. for(int i=t;i<ed;++i)
  47. ret[i-t]=MOD(ret[i-t]+MOD(MOD(w,ch[i][t]),f[i]));
  48. ret=ret*f;
  49. for(ll g=k+1;g<=n;++g)
  50. if(g%5) ret=ret*(poly){MOD(g),1};
  51. return ret;
  52. }
  53. ll jc(ll n){
  54. ll ret=1;
  55. while(n) ret=MOD(ret,fac(n)[0]),n/=5;
  56. return ret;
  57. }
  58. void init(){
  59. PP[0]={1,0};
  60. for(int t=1;t<=100;++t)
  61. if(t%5) PP[t]=PP[t-1]*(poly){t,1};
  62. else PP[t]=PP[t-1];
  63. for(int t=0;t<=SS;++t)
  64. for(int i=ch[t][0]=1;i<=t;++i)
  65. ch[t][i]=MOD(ch[t-1][i]+ch[t-1][i-1]);
  66. }
  67. ll c(ll n){
  68. if(n<k) return 0;
  69. if(k==1) return n;
  70. ll ret=ksm(5,cnt(n)-cnt(k)-cnt(n-k));
  71. if(!ret) return ret;
  72. ret=MOD(ret,MOD(jc(n),ksm(MOD(jc(k),jc(n-k)),phi-1)));
  73. return ret;
  74. }
  75. }
  76. namespace DDPP{
  77. const int maxn=60+5;
  78. vector<pair<int,int>> e[maxn];
  79. void add(int fr,int to,int w){
  80. e[fr].push_back({to,w});
  81. e[to].push_back({fr,w});
  82. }
  83. int siz[maxn],dfn[maxn],arc[maxn],r[maxn],w[maxn],v[maxn],time;
  84. ll dis[maxn];
  85. struct DATA{
  86. ll cnt,val;
  87. DATA(ll a=0,ll b=0):cnt(a),val(b){}
  88. DATA operator + (DATA x){return {(val>=x.val)*cnt+(x.val>=val)*x.cnt,(val>=x.val)*val+(val<x.val)*x.val};}
  89. DATA operator + (ll x){return {cnt,cnt?val+x:0};}
  90. }dp[maxn][10001];
  91. void dfs(int now,int last){
  92. siz[now]=0;
  93. if(Max_val!=-1&&dis[now]*v[now]>Max_val) return;
  94. dfn[now]=++time; arc[time]=now; siz[now]=1;
  95. for(auto t:e[now])
  96. if(t.first^last)
  97. dis[t.first]=dis[now]+t.second,dfs(t.first,now),siz[now]+=siz[t.first];
  98. }
  99. DATA solve(int rt,int must){
  100. if(must==-1) return {0,0};
  101. memset(arc,0,sizeof arc);
  102. memset(dp,0,sizeof dp);
  103. time=0;
  104. ll W=must?lower_bound(e[rt].begin(),e[rt].end(),(pair<int,int>){must,0})->second:0;
  105. if(Max_val!=-1&&(W*v[rt]>Max_val||W*v[must]>Max_val)) return {0,0};
  106. siz[rt]=1; dis[rt]=W; dfn[rt]=time=1; arc[1]=rt;
  107. if(must) dis[must]=W,dfs(must,rt),siz[rt]+=siz[must];
  108. for(auto t:e[rt])
  109. if(t.first!=must)
  110. dis[t.first]=W+t.second,dfs(t.first,rt),siz[rt]+=siz[t.first];
  111. dp[0][0]={1,0};
  112. for(int t=time;t>0;--t){
  113. int cur=arc[t],last=arc[t+1],sub=arc[t+siz[cur]];
  114. if(cur==must||cur==rt)
  115. for(int i=w[cur];i<=m;++i)
  116. dp[cur][i]=dp[last][i-w[cur]]+v[cur];
  117. else{
  118. for(int i=w[cur];i<=m;++i)
  119. dp[cur][i]=(dp[last][i-w[cur]]+v[cur])+dp[sub][i];
  120. for(int i=0;i<w[cur];++i)
  121. dp[cur][i]=dp[sub][i];
  122. }
  123. }
  124. DATA ret(0,0);
  125. for(int t=w[rt];t<=m;++t)
  126. ret=ret+dp[rt][t];
  127. return ret;
  128. }
  129. void dfs0(int now,int last){
  130. r[now]=last;
  131. for(auto t:e[now])
  132. if(t.first^last)
  133. dfs0(t.first,now);
  134. }
  135. void init(){
  136. dfs0(1,-1);
  137. for(int t=1;t<=n;++t) sort(e[t].begin(),e[t].end());
  138. }
  139. }
  140. int main(){
  141. #ifndef ONLINE_JUDGE
  142. freopen("yukinoshita_yukino.in","r",stdin);
  143. freopen("yukinoshita_yukino.out","w",stdout);
  144. #endif
  145. cin.tie(0); cout.tie(0); ios::sync_with_stdio(0);
  146. cin>>n>>m>>k>>Max_val;
  147. for(int t=1;t<=n;++t) cin>>DDPP::w[t];
  148. for(int t=1;t<=n;++t) cin>>DDPP::v[t];
  149. for(int t=1,a,b,c;t<n;++t)
  150. cin>>a>>b>>c,DDPP::add(a,b,c);
  151. C::init(); DDPP::init();
  152. ll ret=0,g=0,sav=Max_val;
  153. Max_val=-1;
  154. for(int t=1;t<=n;++t){
  155. auto t1=DDPP::solve(t,0);
  156. if(t1.val>g) g=t1.val;
  157. }
  158. Max_val=sav;
  159. for(int t=1;t<=n;++t){
  160. auto t1=DDPP::solve(t,0),t2=DDPP::solve(t,DDPP::r[t]);
  161. if(t1.val>g) ret=0,g=t1.val;
  162. if(t1.val==g) ret=MOD(ret+C::c(t1.cnt));
  163. if(t2.val==g) ret=MOD(ret-C::c(t2.cnt)+mod);
  164. }
  165. cout<<ret<<endl;
  166. return 0;
  167. }

【题解】LOJ2462完美的集合(树DP 魔改Lucas)的更多相关文章

  1. [loj2462]完美的集合

    当$k$个集合依次为$S_{1},S_{2},...,S_{k}$时,称$x$合法当且仅当: 1.$\forall 1\le i\le k,x\in S_{i}$ 2.$\forall y\in \b ...

  2. [HAOI2015][bzoj 4033]树上染色(树dp+复杂度分析)

    [题目描述]有一棵点数为N的树,树边有边权.给你一个在0~N之内的正整数K,你要在这棵树中选择K个点,将其染成黑色,并将其他的N-K个点染成白色.将所有点染色后,你会获得黑点两两之间的距离加上白点两两 ...

  3. Codeforces 750E New Year and Old Subsequence 线段树 + dp (看题解)

    New Year and Old Subsequence 第一感觉是离线之后分治求dp, 但是感觉如果要把左边的dp值和右边的dp值合起来, 感觉很麻烦而且时间复杂度不怎么对.. 然后就gun取看题解 ...

  4. 【题解】ARC101F Robots and Exits(DP转格路+树状数组优化DP)

    [题解]ARC101F Robots and Exits(DP转格路+树状数组优化DP) 先删去所有只能进入一个洞的机器人,这对答案没有贡献 考虑一个机器人只能进入两个洞,且真正的限制条件是操作的前缀 ...

  5. CF456D A Lot of Games (字典树+DP)

    D - A Lot of Games CF#260 Div2 D题 CF#260 Div1 B题 Codeforces Round #260 CF455B D. A Lot of Games time ...

  6. HDU4916 Count on the path(树dp??)

    这道题的题意其实有点略晦涩,定义f(a,b)为 minimum of vertices not on the path between vertices a and b. 其实它加一个minimum ...

  7. 51nod1812树的双直径(换根树DP)

    传送门:http://www.51nod.com/Challenge/Problem.html#!#problemId=1812 题解:头一次写换根树DP. 求两条不相交的直径乘积最大,所以可以这样考 ...

  8. 【XSY1545】直径 虚树 DP

    题目大意 ​ 给你一棵\(n\)个点的树,另外还有\(m\)棵树,第\(i\)棵树与原树的以\(r_i\)为根的子树形态相同.这\(m\)棵树之间也有连边,组成一颗大树.求这棵大树的直径长度. \(n ...

  9. bzoj1791[IOI2008]Island岛屿(基环树+DP)

    题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1791 题目大意:给你一棵n条边的基环树森林,要你求出所有基环树/树的直径之和.n< ...

随机推荐

  1. 记一次RSA解密过程

    有问题可以评论 openssl rsa -pubin -text -modulus -in warmup -in pub.key

  2. 一个简单的方法去掉angular application中URLs的hashtag

    本文转载自:Pretty URLs in AngularJS: Removing the # By default, AngularJS will route URLs with a hashtag. ...

  3. 文本编辑器之kindeditor

    摘要:最近在自己学习搭建网站的时候,突然要搭建网站的时候发现了一个好东西,那就是kindeditor这个文本编辑器,这个编辑器简单好用,而且很小,并且是开源的. 文本编辑器介绍 KindEditor ...

  4. xpath模块使用

    xpath模块使用 一.什么是xml(百度百科解释如下) 可扩展标记语言,标准通用标记语言的子集,简称XML.是一种用于标记电子文件使其具有结构性的标记语言. 在电子计算机中,标记指计算机所能理解的信 ...

  5. Android之注册界面练习

    今天要分享的是一个安卓注册小练习,记录一下自己的学习. 做一个注册页面. 要求填入用户如下信息: 用户名.密码.确认密码.性别(单选).爱好(多选,包括至少六个选项,如音乐.美术.阅读.篮球等).em ...

  6. 洛谷 P5639 【CSGRound2】守序者的尊严 题解

    原题链接 简要题意: 从 \(1\) 号位开始走,可以连续走过一段连续的 \(0\) ,每走一次,所有位置取反. (即 \(0 \gets 1\),\(1 \gets 0\)). 算法一 模拟暴力即可 ...

  7. java-TreeMap

    2019-12-17 10:34:55 //返回小于key的第一个键: K lowerKey(K key); //返回大于key的第一个键: K higherKey(K key); //返回小于等于k ...

  8. 浅析jdbc建立连接方式与背后的java类加载

    关于jdbc的连接方式#1Connection conn;Class.forName("com.mysql.jdbc.Driver"); //2conn=DriverManager ...

  9. 1.如何运行一个Vue项目

    如何运行一个Vue项目 需要的环境: node.js环境(npm包管理器) vue-cli 脚手架构建工具 cnpm npm的淘宝镜像 1. 安装node.js 从node.js官网下载并安装node ...

  10. Convert JS object to JSON string

    Modern browsers (IE8, FF3, Chrome etc.) have native JSON support built in (Same API as with JSON2). ...