【题目】D. Roads in Yusland

【题意】给定n个点的树,m条从下往上的链,每条链代价ci,求最少代价使得链覆盖所有边。n,m<=3*10^5,ci<=10^9,time=4s。

【算法】树形DP+线段树||可并堆

【题解】从每条边都需要一条链来覆盖的角度出发,令f[i]表示覆盖子树 i 以及 i到fa[i]的边(i->fa[i])的最小代价,整个过程通过dfs从下往上做。

由于f[son[i]]已知,所以f[i]的转移实际上是考虑覆盖i->fa[i]的链,定义这条链为主链。那么f[i]=min(c+Σf[k]),c是主链代价,k是主链上在i子树内的所有点的子节点(不含主链上点),所有起点在子树i内终点在i的祖先的链都可以作为主链,取最小值

自然地,可以在递归的过程中将Σf[k]并入c中。具体而言,对于每个点x:

1.删。将终点在x的链删除。

2.加。记sum=Σf[son[i]],son[i]子树内所有的链c+=sum-f[son[i]](就是把Σf[k]并入c中),特别地,起点在i的链c+=sum。

3.取。f[i]是子树i中所有的链c的最小值。

现在需要快速支持子树加值和子树求最小值的操作,可以用线段树按dfs序维护所有链实现(把链按起点的dfs序作为线段树下标)。

复杂度O(n log n)。

#include<cstdio>
#include<cctype>
#include<vector>
#include<algorithm>
#define ll long long
using namespace std;
int read(){
char c;int s=,t=;
while(!isdigit(c=getchar()))if(c=='-')t=-;
do{s=s*+c-'';}while(isdigit(c=getchar()));
return s*t;
}
const int maxn=;
const ll inf=1e15;
struct tree{int l,r;ll delta,mins;}t[maxn*];
struct edge{int v,from;}e[maxn*];
vector<int>v[maxn];
int n,m,ku[maxn],kv[maxn],kw[maxn],kp[maxn],tot=,dfsnum=,first[maxn],be[maxn],ed[maxn];
ll a[maxn],f[maxn];
void ins(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void dfs_order(int x,int fa){
be[x]=dfsnum+;
for(int i=;i<(int)v[x].size();i++){
kp[v[x][i]]=++dfsnum;
a[dfsnum]=kw[v[x][i]];
}
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
dfs_order(e[i].v,x);
}
ed[x]=dfsnum;
if(be[x]>ed[x]){printf("-1");exit();}
}
void modify(int k,ll x){t[k].mins+=x;t[k].delta+=x;}
void up(int k){t[k].mins=min(t[k<<].mins,t[k<<|].mins);}
void down(int k){
if(t[k].delta){
modify(k<<,t[k].delta);
modify(k<<|,t[k].delta);
t[k].delta=;
}
}
void build(int k,int l,int r){
t[k].l=l;t[k].r=r;t[k].delta=;
if(l==r){t[k].mins=a[l];}else{
int mid=(l+r)>>;
build(k<<,l,mid);
build(k<<|,mid+,r);
up(k);
}
}
void add(int k,int l,int r,ll x){
if(l<=t[k].l&&t[k].r<=r){modify(k,x);return;}
down(k);
int mid=(t[k].l+t[k].r)>>;
if(l<=mid)add(k<<,l,r,x);
if(r>mid)add(k<<|,l,r,x);
up(k);
}
ll ask(int k,int l,int r){
if(l<=t[k].l&&t[k].r<=r){return t[k].mins;}
down(k);
int mid=(t[k].l+t[k].r)>>;
ll ans=inf;
if(l<=mid)ans=ask(k<<,l,r);
if(r>mid)ans=min(ans,ask(k<<|,l,r));
return ans;
}
ll dp(int x,int fa){
f[x]=;ll sum=;
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa)sum+=dp(e[i].v,x);
for(int i=;i<(int)v[x].size();i++)add(,v[x][i],v[x][i],inf);
add(,be[x],ed[x],sum);
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa){
add(,be[e[i].v],ed[e[i].v],-f[e[i].v]);
}
f[x]=ask(,be[x],ed[x]);
if(x!=&&f[x]>=inf){printf("-1");exit();}
return f[x];
}
int main(){
n=read();m=read();
for(int i=;i<n;i++){
int u=read(),v=read();
ins(u,v);ins(v,u);
}
for(int i=;i<=m;i++){
ku[i]=read(),kv[i]=read(),kw[i]=read();
v[ku[i]].push_back(i);
}
dfsnum=;
dfs_order(,);
build(,,dfsnum);
for(int i=;i<=m;i++)v[ku[i]].clear();
for(int i=;i<=m;i++)v[kv[i]].push_back(kp[i]);
dp(,);
ll ans=;
for(int i=first[];i;i=e[i].from)ans+=f[e[i].v];
printf("%lld",ans);
return ;
}

可并堆写法:

核心思想仍是——每条边都需要一条链来覆盖。

整个过程通过dfs从下往上做,对于每个点x,维护一个堆包含所有起点在子树x内终点为x的祖先的链(按价值从小到大)。

维护的过程只需要将所有儿子的堆合并过来,然后删除终点在x的链。(堆的删除不需要真的删除,只需要在调用堆顶是判断是否已被删除)

接下来考虑选用哪些链,考虑点x时,子树x内所有边都已经被覆盖,所以实际上是在考虑x->fa[x]这条边的覆盖,那么此时堆x中的链都可以随意选用,但是选用哪条对未来更优当前并不知道。

采用反悔的思想,先选用代价w最小的链,并将堆整体标记-w,之后考虑选用其它边实际上就是“更换”的操作了,当然选用的代价w的链不移除(在堆中代价为0)将一直发挥作用直至其终点。

这样做的正确性就在于,在标记-w时,堆中所有的链可以随意换用,因为都会影响x->fa[x]而子树x已经完全覆盖了无须考虑。

总结起来,对于点x:

1.合并所有son[x]。

2.找到堆顶w加入答案。(不需要特别做删除)

3.整体标记-w。

复杂度O(n log n),常数优势明显。

#include<cstdio>
#include<cctype>
#include<cctype>
#include<algorithm>
#define ll long long
using namespace std;
int read(){
char c;int s=,t=;
while(!isdigit(c=getchar()))if(c=='-')t=-;
do{s=s*+c-'';}while(isdigit(c=getchar()));
return s*t;
}
const int maxn=;
struct edge{int v,from;}e[maxn*];
int tot,first[maxn],l[maxn],r[maxn],d[maxn],root[maxn],n,m,top[maxn];
ll delta[maxn],w[maxn],ans=;
bool vis[maxn]; void insert(int u,int v){tot++;e[tot].v=v;e[tot].from=first[u];first[u]=tot;}
void modify(int k,int x){delta[k]+=x;w[k]+=x;}
void down(int x){
if(delta[x]){
if(l[x])modify(l[x],delta[x]);
if(r[x])modify(r[x],delta[x]);//make 0 no influence!
delta[x]=;
}
}
int merge(int x,int y){
if(!x||!y)return x^y;
if(w[x]>w[y])swap(x,y);
down(x);r[x]=merge(r[x],y);
if(d[l[x]]<d[r[x]])swap(l[x],r[x]);
d[x]=d[r[x]]+;
return x;
}
void dfs(int x,int fa){
for(int i=first[x];i;i=e[i].from)if(e[i].v!=fa)dfs(e[i].v,x),root[x]=merge(root[x],root[e[i].v]);
vis[x]=;
if(x==)return;
while(vis[top[root[x]]])root[x]=merge(l[root[x]],r[root[x]]);
if(!root[x]){printf("-1");exit();}
ans+=w[root[x]];modify(root[x],-w[root[x]]);
}
int main(){
n=read();m=read();
for(int i=;i<n;i++){
int u=read(),v=read();
insert(u,v);insert(v,u);
}
for(int i=;i<=m;i++){
int u=read();top[i]=read();w[i]=read();
root[u]=merge(root[u],i);
}
ans=;
dfs(,);
printf("%lld",ans);
return ;
}

【CodeForces】671 D. Roads in Yusland的更多相关文章

  1. 【CodeForces】671 C. Ultimate Weirdness of an Array

    [题目]C. Ultimate Weirdness of an Array [题意]给定长度为n的正整数序列,定义一个序列的价值为max(gcd(ai,aj)),1<=i<j<=n, ...

  2. 【CodeForces】671 B. Robin Hood

    [题目]B. Robin Hood [题意]给定n个数字的序列和k次操作,每次将序列中最大的数-1,然后将序列中最小的数+1,求最终序列极差.n<=5*10^5,0<=k<=10^9 ...

  3. 【Codeforces】Round #491 (Div. 2) 总结

    [Codeforces]Round #491 (Div. 2) 总结 这次尴尬了,D题fst,E没有做出来.... 不过还好,rating只掉了30,总体来说比较不稳,下次加油 A:If at fir ...

  4. 【Codeforces】Round #488 (Div. 2) 总结

    [Codeforces]Round #488 (Div. 2) 总结 比较僵硬的一场,还是手速不够,但是作为正式成为竞赛生的第一场比赛还是比较圆满的,起码没有FST,A掉ABCD,总排82,怒涨rat ...

  5. Codeforces 671 D. Roads in Yusland

    题目描述 Mayor of Yusland just won the lottery and decided to spent money on something good for town. Fo ...

  6. 【CodeForces】601 D. Acyclic Organic Compounds

    [题目]D. Acyclic Organic Compounds [题意]给定一棵带点权树,每个点有一个字符,定义一个结点的字符串数为往下延伸能得到的不重复字符串数,求min(点权+字符串数),n&l ...

  7. 【Codeforces】849D. Rooter's Song

    [算法]模拟 [题意]http://codeforces.com/contest/849/problem/D 给定n个点从x轴或y轴的位置p时间t出发,相遇后按对方路径走,问每个数字撞到墙的位置.(还 ...

  8. 【CodeForces】983 E. NN country 树上倍增+二维数点

    [题目]E. NN country [题意]给定n个点的树和m条链,q次询问一条链(a,b)最少被多少条给定的链覆盖.\(n,m,q \leq 2*10^5\). [算法]树上倍增+二维数点(树状数组 ...

  9. 【CodeForces】925 C.Big Secret 异或

    [题目]C.Big Secret [题意]给定数组b,求重排列b数组使其前缀异或和数组a单调递增.\(n \leq 10^5,1 \leq b_i \leq 2^{60}\). [算法]异或 为了拆位 ...

随机推荐

  1. Java中的网络编程-1

    计算机网络:将分布在不同地区的计算机与专门的外部设备用通信线路互连成一个规模大.功能强的网络系统, 从而使众多计算机 可以方便的互相传递信息, 共享硬件.软件.数据信息等资源. 计算机网络的主要功能: ...

  2. 201621123037 《Java学习设计》 第五周学习总结

    Week05-继承.多态.抽象类与接口 1. 本周学习总结 1.1 写出你认为本周学习中比较重要的知识点关键词 关键词:接口."has-a".多态.comparable.Compa ...

  3. java之静态代理与动态代理

    先看看静态代理是如何操作的 定义接口: public interface Person { public void sayHello(String content, int age); public ...

  4. QT样式表

    QT样式表 一.QT样式表简介 1.QT样式表简介 QSS的主要功能是使界面的表现与界面的元素分离,使得设计皮肤与界面控件分离的软件成为可能. QT样式表是允许用户定制widgets组件外观的强大机制 ...

  5. 分治FFT

    目录 分治FFT 目的 算法 代码 分治FFT 目的 解决这样一类式子: \[f[n] = \sum_{i = 0}^{n - 1}f[i]g[n - i]\] 算法 看上去跟普通卷积式子挺像的,但是 ...

  6. 【BZOJ5338】[TJOI2018]异或(主席树)

    [BZOJ5338][TJOI2018]异或(主席树) 题面 洛谷 题解 很明显的是\(Trie\)树上暴力判断答案 因为要支持区间,用主席树的结构存\(Trie\)树就好了 #include< ...

  7. Round 403 div. 2

    B 可以二分相遇的坐标:也可以二分时间,判断是否存在两个人的区间没有交. An easy way to intersect a number of segments [l1, r1], ..., [l ...

  8. [pool www] user has not been defined

    [02-Dec-2014 00:28:58] ALERT: [pool www] user has not been defined [02-Dec-2014 00:28:58] ERROR: fai ...

  9. MapReduce(五) mapreduce的shuffle机制 与 Yarn

    一.shuffle机制 1.概述 (1)MapReduce 中, map 阶段处理的数据如何传递给 reduce 阶段,是 MapReduce 框架中最关键的一个流程,这个流程就叫 Shuffle:( ...

  10. 【bzoj3173】最长上升子序列

    Portal --> bzoj3173 Solution 感觉自己需要智力康复qwq 首先题目给的这个序列肯定是一个\(1-n\)的排列,并且插入的顺序是从小到大 仔细思考一下会发现如果知道了最 ...