Codeforces 1097 G. Vladislav and a Great Legend
一道好题。
题意:给定一棵\(n\)个点的树,求:
\]
其中\(f(S)\)代表用树边将点集\(S\)连通需要的最少边数。\(n\leq10^5\),\(k\leq 200\),对\(10^9+7\)取模。
吐槽:比赛的时候看到这道题想到的是组合数做法,然而是\(O(nk^2)\)的,但可以用任意模数\(\text{NTT}\)优化到\(O(nk\log k)\),当然由于常数巨大结果可想而知。可以说是第一次感受到被1e9+7支配的恐惧。我觉得出题人模数不出\(998244353\)就是为了卡这种做法...当然正解的做法还是让人心服口服并且受益良多的。
题解:首先有一种套路叫做幂转下降幂,即这个等式:
\]
考虑它的一个等价形式:
\]
应用到这道题的式子中,我们要求的就是:
\]
考虑交换求和的顺序:
\]
为了方便起见,我们在下文不严谨的称连通一个点集\(S\)的最小边集叫做点集\(S\)的虚树。
从组合意义上来讲,后面的式子等价于枚举所有大小为\(i\)的边集,计算虚树包含这个边集的点集数目并相加得到的结果。根据这一点,我们可以进行dp。我们设答案数组\(g_i=\sum_{S\subseteq \{1,2,\dots,n\}}{f(S)\choose i}\),那么只要知道\(g\)就很容易知道答案。
先考虑如何简化判断虚树的边的过程。首先,假如点集中所有点的\(lca\)就是根,那么存在一种非常简单的方法:对于每一个非根节点,假如点集至少包含一个它的子树中的点,那么它向父亲的边就会被包含。于是我们可以考虑枚举点集中所有点的\(lca\),设为\(x\),对于是\(x\)的子树的子集的所有非空点集,用上述方法来判断边数,我们不妨称这样得到的虚树为关于\(x\)的伪虚树。当然会有一部分不合法的,即所有点都在以\(x\)的某个儿子\(y\)的子树内,我们将关于\(x\)的伪虚树的贡献去掉即可。
我们设\(f_{x,i}\)表示所有大小为\(i\)的边集被多少个关于\(x\)的伪虚树包含。
为了方便,我们不妨在开始时将空集也计算在内,最后在去掉空集的影响,容易得知去掉空集只要在最后将\(f_{x,0}\)减\(1\)即可。
那么初始时只有\(x\)本身,考虑是否选择\(x\),两种情况都只有\(0\)条边,因此可以设\(f_{x,0}=2\)。
接下来考虑在原来的基础上如何添加一棵以\(y\)为根的子树。我们先考虑如何将关于\(y\)的伪虚树改造成关于\(x\)的伪虚树。我们设辅助数组\(tmp\),\(tmp_i\)表示所有大小为\(i\)的边集被多少个关于\(x\)的伪虚树中只含以\(y\)为根的子树中的点的伪虚树包含。如果是非空的子集,那么实际上就是多包含了\(y\)和\(x\)之间的边。于是我们根据边集是否包含新的这条边,得出转移\(tmp_i+=f_{y,i}+f_{y,i-1}\)。最后考虑空集的情况,那么\(tmp_0++\)。可以发现这里计算的伪虚树除空集以外都是不合法的,于是令\(g_i-=tmp_i\),最后加上额外减去的空集,即\(g_0++\)。
接下来只要根据乘法原理将\(f_x\)与\(tmp\)合并就可以得到新的\(f'_x\),转移方程为:
\]
在算完所有子树以后就得到了所有关于\(x\)的伪虚树的贡献,我们先去掉空集,即\(f_{x,0}--\),再将贡献加入到答案数组中,即\(g_i+=f_{x,i}\)。
这样做的复杂度看上去是\(O(nk^2)\)的,但是考虑到合并\(x\)和\(y\)时,设\(size_x\)表示当前处理好的\(x\)的子树的点数,那么合并的代价实际上是\(\min(k,size_x)*\min(k,size_y)\)。
首先考虑若干个\(<k\)的子树进行合并直到合并为一个\(\geq k\)的子树,代价是\(O(k^2)\)的,最多合并成\(\frac{n}{k}\)个这样的,于是复杂度是\(O(nk)\)。
再考虑\(<k\)的与\(\geq k\)的合并,复杂度是\(O(size*k)\)的,那么由于每一点最多经历一次这个过程,复杂度也是\(O(nk)\)。
最后是两个\(\geq k\)的子树合并,由于只有最多\(\frac{n}{k}\)个这样的子树因此操作次数不超过\(\frac{n}{k}\),因此复杂度是\(O(nk)\)。
于是最终的复杂度是\(O(nk)\),降低复杂度的关键是合并的代价可以和子树大小取\(\min\)。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using std::min;
using std::vector;
const int mod=1e9+7;
inline int add(int a,int b)
{
return (a+=b)>=mod?a-mod:a;
}
inline int sub(int a,int b)
{
return (a-=b)<0?a+mod:a;
}
inline int mul(int a,int b)
{
return (long long)a*b%mod;
}
inline int qpow(int a,int b)
{
int res=1;
for(;b;a=mul(a,a),b>>=1)
if(b&1)
res=mul(res,a);
return res;
}
const int N=1e5+5,K=205;
int n,k,ans;
int S[K][K],fact[K];
int size[N];
vector<int> e[N];
int f[N][K];
int tmp[K],tmp2[K];
int g[K];
void dfs(int x,int father)
{
int kx,ky;
register int i,j;
f[x][0]=2;size[x]=1;
for(auto y:e[x])
if(y!=father)
{
dfs(y,x);kx=min(k,size[x]);ky=min(k,size[y]);
memcpy(tmp,f[y],sizeof(int)*(ky+1));
for(i=ky;i>0;i--)
tmp[i]=add(tmp[i],tmp[i-1]);
for(i=0;i<=ky;i++)
g[i]=sub(g[i],tmp[i]);
tmp[0]=add(tmp[0],1);
memset(tmp2,0,sizeof(int)*(k+1));
for(i=0;i<=kx;i++)
for(j=0;j<=ky&&i+j<=k;j++)
tmp2[i+j]=add(tmp2[i+j],mul(f[x][i],tmp[j]));
memcpy(f[x],tmp2,sizeof(int)*(k+1));
size[x]+=size[y];
}
f[x][0]=sub(f[x][0],1);
kx=min(k,size[x]);
for(i=0;i<=kx;i++)
g[i]=add(g[i],f[x][i]);
return;
}
signed main()
{
int x,y;
register int i,j;
scanf("%d%d",&n,&k);
fact[0]=1;
for(i=1;i<=k;i++)
fact[i]=mul(fact[i-1],i);
S[0][0]=1;
for(i=1;i<=k;i++)
{
S[i][0]=0;S[i][i]=1;
for(j=1;j<i;j++)
S[i][j]=add(S[i-1][j-1],mul(j,S[i-1][j]));
}
for(i=1;i<=n-1;i++)
{
scanf("%d%d",&x,&y);
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
for(i=1;i<=k;i++)
ans=add(ans,mul(mul(S[k][i],fact[i]),g[i]));
printf("%d\n",ans);
return 0;
}
Codeforces 1097 G. Vladislav and a Great Legend的更多相关文章
- [codeforces 549]G. Happy Line
[codeforces 549]G. Happy Line 试题描述 Do you like summer? Residents of Berland do. They especially love ...
- CodeForces 794 G.Replace All
CodeForces 794 G.Replace All 解题思路 首先如果字符串 \(A, B\) 没有匹配,那么二元组 \((S, T)\) 合法的一个必要条件是存在正整数对 \((x,y)\), ...
- Codeforces 1207 G. Indie Album
Codeforces 1207 G. Indie Album 解题思路 离线下来用SAM或者AC自动机就是一个单点加子树求和,套个树状数组就好了,因为这个题广义SAM不能存在 \(len[u] = l ...
- Codeforces 1097G Vladislav and a Great Legend [树形DP,斯特林数]
洛谷 Codeforces 这题真是妙的很. 通过看题解,终于知道了\(\sum_n f(n)^k\)这种东西怎么算. update:经过思考,我对这题有了更深的理解,现将更新内容放在原题解下方. ...
- CodeForces 1097G. Vladislav and a Great Legend
题目简述:给定$n \leq 10^5$个节点的树$T = (V, E)$,令$X \subseteq V$表示一个非空节点集合,定义$f(X)$为包含$X$的最小子树的边数.求 $$ \sum_{\ ...
- Codeforces 1097G - Vladislav and a Great Legend(第二类斯特林数+树上背包)
Codeforces 题目传送门 & 洛谷题目传送门 首先看到这题我的第一反应是:这题跟这题长得好像,不管三七二十一先把 \(k\) 次方展开成斯特林数的形式,\(f(X)^k=\sum\li ...
- codeforces 659 G. Fence Divercity 组合数学 dp
http://codeforces.com/problemset/problem/659/G 思路: f(i,0/1,0/1) 表示到了第i个,要被切的块开始了没有,结束了没有的状态的方案数 递推看代 ...
- Codeforces 803 G. Periodic RMQ Problem
题目链接:http://codeforces.com/problemset/problem/803/G 大致就是线段树动态开节点. 然后考虑到如果一个点还没有出现过,那么这个点显然未被修改,就将这个点 ...
- Codeforces 954 G. Castle Defense
http://codeforces.com/problemset/problem/954/G 二分答案 检验的时候,从前往后枚举,如果发现某个位置的防御力<二分的值,那么新加的位置肯定是越靠后越 ...
随机推荐
- JavaScript HTML DOM,BOM
DOM DOM 是一个 W3C (万维网联盟) 标准. DOM 定义了用于访问文档的标准: "W3C 文档对象模型 (DOM) 是一个平台和与语言无关的界面, 允许程序和脚本动态访问和更新文 ...
- nodejs 模板引擎ejs的使用
1.test.ejs文件 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- rownum与row_number() OVER (PARTITION BY COL1 ORDER BY COL2)
1)rownum 为查询结果排序.使用rownum进行排序的时候是先对结果集加入伪列rownum然后再进行排序 select rownum n, a.* from ps_user a order by ...
- Windows10安装vmware和centos7
1. 下载vmware安装包 地址(vmware版本:vmware workstation 14):http://xzc.197746.com/vmware-workstation-full1413. ...
- Java面试题,Java三大特性之一——多态的理解
首先我们知道Java是一门面向对象的语言 面向对象三大特性,封装.继承.多态. 封装.继承.多态 ↓ 无论是学习路线,还是众人的口语习惯,都是按照这个这样进行排序,这是有原因的.因为封装好了才能继承, ...
- .net 设置Webbowser 版本
.net 里的Webbowser控件默认情况是用IE7来渲染 可修改注册表试用是最新的版本来渲染: using System; using System.Collections.Generic; us ...
- Django Rest Framework源码剖析(八)-----视图与路由
一.简介 django rest framework 给我们带来了很多组件,除了认证.权限.序列化...其中一个重要组件就是视图,一般视图是和路由配合使用,这种方式给我们提供了更灵活的使用方法,对于使 ...
- Centos 定时任务发送smtp邮件
接着上一篇文章...... 1.首先创建一个sheel的脚本命令,我是在home文件夹下面创建的命令: touch a.sh 2.编辑a.sh脚本 vim a.sh ,键入键盘 i 键 准备插入 ...
- Educational Codeforces Round 41 (Rated for Div. 2)(A~D)
由于之前打过了这场比赛的E题,而后面两道题太难,所以就手速半个多小时A了前4题. 就当练手速吧,不过今天除了C题数组开小了以外都是1A A Tetris 题意的抽象解释可以在Luogu里看一下(话说现 ...
- 由于未能创建 Microsoft Visual C# 2008 编译器,因此未能打开项目 "..."的解决方法
如果遇到这种问题,我们通常只要在 Visual Studio 2017 的命令提示符工具里执行下列命令即可: devenv /resetskippkgs 如果还是不行的话,可以先把 Visual St ...