题目

题目大意

给你一棵带点权的树,求将树变成一堆不相交的链,而且这些链的权值和非负的方案数。


正解

显然这道题是个\(DP\)。

首先求个前缀和\(sum\)。

为了后面讲述方便,我这样设:\(f_{i,j}\)表示以\(i\)为根的子树,其中某条链从\(x\)伸出到\(i\)的方案数,而且\(sum_x=j\)。

还有设\(g_i\)表示以\(i\)为根的,没有伸出去的链的方案数。

显然有这样的转移:

\[\prod g_i\to f_{x,sum_x}\\
f_{y,j}\prod_{i\neq y} g_i\to f_x,j\]

\[f_{x,j} \to g_x \ (j-sum_{fa_x}\geq 0)\\
f_{y,j}f_{z,k}\prod_{i\neq y,i\neq z}g_i\to g_x \ (j+k-2sum_x+a_x\geq 0)
\]

如果直接这样搞肯定会爆炸。所以考虑用线段树来维护\(f\)。

由于可能会出现\(g\)值为\(0\)的情况,所以不能直接用逆元来搞。

要维护个前缀积和后缀积。

首先要求出重儿子,把重儿子作为第一个儿子,然后线段树合并之前也启发式合并。

具体来说,我们钦定\(j<k\)。在合并的时候(设前面子树合并出来的线段树为\(A\),这个线段树为\(B\))当前的儿子作为\(k\),遍历\(B\)的所有叶子节点,并在\(A\)中区间询问。这时候记得要乘上后缀积。将询问出来的东西加在\(g_x\)中。

然后两个合并在一起。记得在合并之前,整个\(A\)乘子树的\(g_x\),整个\(B\)乘前缀积

。搞完这个再合并。

最后你就会愉快地发现,所有子树合并之后就是上面第二行式子。这样只需要把第一行的加进去。第四行的式子已经计算完了,只需要再加上第一个式子就可以了。

然而这不是题解的做法,作为一个小蒟蒻,表示看不懂题解……


代码

using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cassert>
#define N 100010
#define INF 1000000000
#define mo 1000000007
int n;
int a[N];
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N];
struct Node *null;
struct Node{
Node *l,*r;
int sum,tag;
inline void get_tag(int c){sum=(long long)sum*c%mo;tag=(long long)tag*c%mo;}
inline void pushdown(){
if (tag!=1){
l->get_tag(tag);
r->get_tag(tag);
tag=1;
}
}
inline void update(){sum=l->sum+r->sum;sum>=mo?sum-=mo:0;}
} d[N*40];
int cnt;
Node *rt[N];
inline Node *newnode(){return &(d[++cnt]={null,null,0,1});}
void add(Node *&t,int l,int r,int x,int c){
if (t==null)
t=newnode();
if (l==r){
(t->sum+=c)%=mo;
return;
}
t->pushdown();
int mid=l+r>>1;
if (x<=mid)
add(t->l,l,mid,x,c);
else
add(t->r,mid+1,r,x,c);
t->update();
}
int query(Node *t,int l,int r,int st,int en){
if (t==null)
return 0;
if (st<=l && r<=en)
return t->sum;
t->pushdown();
int mid=l+r>>1,res=0;
if (st<=mid)
res+=query(t->l,l,mid,st,en);
if (mid<en)
res+=query(t->r,mid+1,r,st,en);
return res>=mo?res-mo:res;
}
Node *merge(Node *a,Node *b){
if (a==null)
return b;
if (b==null)
return a;
a->pushdown(),b->pushdown();
a->l=merge(a->l,b->l);
a->r=merge(a->r,b->r);
a->sum+=b->sum;
a->sum>=mo?a->sum-=mo:0;
return a;
}
int calc(Node *t,int l,int r,Node *rt,int bor){
if (t==null)
return 0;
if (l==r)
return (long long)query(rt,-INF,INF,bor-l,INF)*t->sum%mo;
t->pushdown();
int mid=l+r>>1,res=0;
res+=calc(t->l,l,mid,rt,bor);
res+=calc(t->r,mid+1,r,rt,bor);
return res>=mo?res-mo:res;
}
int fa[N],sum[N],siz[N],hs[N];
int son[N],ns,pre[N],suc[N];
int g[N];
void dp(int x){
sum[x]=sum[fa[x]]+a[x];
siz[x]=1;
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[x]){
fa[ei->to]=x;
dp(ei->to);
siz[x]+=siz[ei->to];
if (siz[ei->to]>siz[hs[x]])
hs[x]=ei->to;
}
if (!hs[x]){
rt[x]=newnode();
add(rt[x],-INF,INF,sum[x],1);
g[x]=(a[x]>=0);
return;
}
son[ns=1]=hs[x];
pre[0]=1,pre[1]=g[hs[x]];
for (EDGE *ei=last[x];ei;ei=ei->las)
if (ei->to!=fa[x] && ei->to!=hs[x]){
son[++ns]=ei->to;
pre[ns]=(long long)pre[ns-1]*g[ei->to]%mo;
}
suc[ns+1]=1;
for (int i=ns;i>=1;--i)
suc[i]=(long long)suc[i+1]*g[son[i]]%mo;
rt[x]=rt[hs[x]];
for (int i=2;i<=ns;++i){
g[x]=(g[x]+(long long)calc(rt[son[i]],-INF,INF,rt[x],sum[fa[x]]*2+a[x])*suc[i+1])%mo;
rt[son[i]]->get_tag(pre[i-1]);
rt[x]->get_tag(g[son[i]]);
rt[x]=merge(rt[x],rt[son[i]]);
}
add(rt[x],-INF,INF,sum[x],pre[ns]);
g[x]+=query(rt[x],-INF,INF,sum[fa[x]],INF);
g[x]>=mo?g[x]-=mo:0;
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%d",&a[i]);
for (int i=1;i<n;++i){
int u,v;
scanf("%d%d",&u,&v);
e[ne]={v,last[u]};
last[u]=e+ne++;
e[ne]={u,last[v]};
last[v]=e+ne++;
}
null=d;
*null={null,null,0,0};
dp(n>>1);
printf("%d\n",g[n>>1]);
return 0;
}

总结

好多树形DP都可以用线段树合并来优化啊……

[JZOJ4331] 【清华集训模拟】树的更多相关文章

  1. 洛谷P4247 序列操作 [清华集训] 线段树

    正解:线段树 解题报告: 传送门! 通过这题我get了一个神奇的,叫,线段树五问的东西hhhh 听起来有点中二但感觉真正做题的时候还是比较有用的,,,?感觉会让条理清晰很多呢,所以放一下QwQ →每个 ...

  2. [JZOJ4330] 【清华集训模拟】几何题

    题目 题目大意 也懒得解释题目大意了-- 正解 正解居然是\(FFT\)? 不要看题目的那个式子这么长,也不要在那个式子上下手. 其实我们会发现,不同的\((x_i-x_j,y_i-y_j,z_i-z ...

  3. 【题解】P4247 [清华集训]序列操作(线段树修改DP)

    [题解]P4247 [清华集训]序列操作(线段树修改DP) 一道神仙数据结构(DP)题. 题目大意 给定你一个序列,会区间加和区间变相反数,要你支持查询一段区间内任意选择\(c\)个数乘起来的和.对1 ...

  4. Loj 2320.「清华集训 2017」生成树计数

    Loj 2320.「清华集训 2017」生成树计数 题目描述 在一个 \(s\) 个点的图中,存在 \(s-n\) 条边,使图中形成了 \(n\) 个连通块,第 \(i\) 个连通块中有 \(a_i\ ...

  5. 「清华集训2015」V

    「清华集训2015」V 题目大意: 你有一个序列,你需要支持区间加一个数并对 \(0\) 取 \(\max\),区间赋值,查询单点的值以及单点历史最大值. 解题思路: 观察发现,每一种修改操作都可以用 ...

  6. [LOJ#2330]「清华集训 2017」榕树之心

    [LOJ#2330]「清华集训 2017」榕树之心 试题描述 深秋.冷风吹散了最后一丝夏日的暑气,也吹落了榕树脚下灌木丛的叶子.相识数年的Evan和Lyra再次回到了小时候见面的茂盛榕树之下.小溪依旧 ...

  7. [LOJ#2324]「清华集训 2017」小Y和二叉树

    [LOJ#2324]「清华集训 2017」小Y和二叉树 试题描述 小Y是一个心灵手巧的OIer,她有许多二叉树模型. 小Y的二叉树模型中,每个结点都具有一个编号,小Y把她最喜欢的一个二叉树模型挂在了墙 ...

  8. [LOJ#2323]「清华集训 2017」小Y和地铁

    [LOJ#2323]「清华集训 2017」小Y和地铁 试题描述 小Y是一个爱好旅行的OIer.一天,她来到了一个新的城市.由于不熟悉那里的交通系统,她选择了坐地铁. 她发现每条地铁线路可以看成平面上的 ...

  9. uoj266[清华集训2016]Alice和Bob又在玩游戏(SG函数)

    uoj266[清华集训2016]Alice和Bob又在玩游戏(SG函数) uoj 题解时间 考虑如何求出每棵树(子树)的 $ SG $ . 众所周知一个状态的 $ SG $ 是其后继的 $ mex $ ...

随机推荐

  1. python调用tushare获取股票日线实时行情数据

    接口:daily 数据说明:交易日每天15点-16点之间.本接口是未复权行情,停牌期间不提供数据. 调取说明:基础积分每分钟内最多调取200次,每次4000条数据,相当于超过18年历史,具体请参阅本文 ...

  2. python将字典列表导出为Excel文件的方法

    将如下的字典列表内容导出为Excel表格文件形式: ​ 关于上图字典列表的写入,请参考文章:https://blog.csdn.net/weixin_39082390/article/details/ ...

  3. eduCF#61 C. Painting the Fence /// DP 选取k段能覆盖的格数

    题目大意: 给定n m 接下来给定m个在n范围内的段的左右端 l r 求选取m-2段 最多能覆盖多少格 #include <bits/stdc++.h> using namespace s ...

  4. 【Codeforces】450 B(div2)

    题目链接:http://codeforces.com/problemset/problem/450/B 题意: 求这个的第n项. 题解:$f_{i+1} = f_i - f_{i-1} $ \begi ...

  5. mysql数据库 --数据类型、约束条件

    今日内容 表的详细使用 1.创建表的完成语法 2.字段类型 整型.浮点型.字符类型.日期类型.枚举与集合类型 3.约束条件 primary key.unique.not null.default 一. ...

  6. Java 基础 - JDK 和 JRE 有什么区别

    总结 JRE(Java Runtime Environment),就是 Java 运行环境,包括JVM虚拟机(java.exe等)和基本的类库(rt.jar等). JDK (Java Developm ...

  7. Nginx的动静分离

    Nginx的动静分离 在之前我们的负载均衡中,我们再jsp中设置了一个背景,这是一个静态资源,Tomcat处理静态资源的效率并没有Nginx高,我们可以通过动静分离将静态资源和动态资源分割开来,Tom ...

  8. CF 848E(动态规划+分治NTT)

    传送门: http://codeforces.com/problemset/problem/848/E 题解: 假设0-n一定有一条边,我们得到了一个方案,那么显然是可以旋转得到其他方案的. 记最大的 ...

  9. 经典换根dp——hdu2196

    给定一棵边权树,求距离每个点最远的点,输出这个距离 #include<bits/stdc++.h> using namespace std; #define N 10005 ]; int ...

  10. delphi 可以自定义边框的文本框TSkinNormalEdit思路(QQ2011风格)

    需求: QQ我的资料中基本资料窗体中的文本框: 正常状态下,文本框只有一条看起来只有一个像素的边框,边框的颜色从上到下由深到浅的渐变,当鼠标定位到该文本框时,其边框会变粗,而且边框的颜色加亮显示 如下 ...