Luogu P3931 SAC E#1 - 一道难题 Tree 题解

闲扯

为了学习最小割到了这道题,但是看题解的时候发现做法还有树形 \(DP\) 毕竟没怎么认真想过题,写完最小割之后,顺手来了发树形 \(DP\) ,然后就过了,感觉真爽啊~~

题面

题面

Solution

解法一:最小割最大流

因为要求是所有的叶节点都不能到达根结点,而且可以删除边,要求删边的最小代价,看着就很像最小割。但是仔细一看,发现割完之后叶节点组成了很多个点集,怎么办?

我们只需要再虚拟一个超级汇点,将所有的根结点全部连向该节点,且边的流量均为 \(INF\) 。(因为这些边是我们人为加的,肯定不能选这些边割去)

这时候我们可以看出,当所有的边割完之后,实际上是将这幅图变成了两个不相交的点集,且 \(S\) 和 \(T\) 分别处于一侧。这样就变成了最小割的模板了,直接跑最大流即可(最小割在数值上等于最大流,不清楚的自行百度)。

解法二:树形 \(DP\)

因为叶节点不能到根结点,我们考虑树形 \(DP\) 从下向上统计。这是什么逻辑(雾

考虑 \(dp_u\) 表示以 \(u\) 为根节点时,所需要的最小代价,转移方程为: \(dp_u=\sum \min(dp_i,dis_i)\) 。其中 \(i\) 为 \(u\) 的子节点, \(dis\) 表示 \(i\) 到 \(u\) 的距离。

理解:对于包含于以 \(i\) 为根结点的子树的叶节点,割去他们所需的最小代价为直接割去这颗子树和保持原来做法的最小值,而一个 \(u\) 可能有多个子树,所以要求和(不同子树相互独立,互不影响)。

需要注意的时候,当 \(u\) 的子节点 \(i\) 就为叶节点时, \(dp_i\) 要设为 \(INF\) ,这样可以保证删去该点的值一定为 \(dis_i\) 。

Code

解法一:

#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
    int f=1;char k=getchar();x=0;
    for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
    for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
    x*=f;
}
template<class T>il print(T x){
    if(x/10) print(x/10);
    putchar(x%10+'0');
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
    int res=1,bas=x%mod;
    while(m){
        if(m&1) res=(res*bas)%mod;
        bas=(bas*bas)%mod,m>>=1;
    }
    return res%mod;
}
const int MAXN = 1e5+5;
int n,s,t,u,v,d,cur[MAXN],head[MAXN],num_edge=-1,dis[MAXN];
struct Edge{
    int next,to,w;
    Edge(){}
    Edge(int next,int to,int w):next(next),to(to),w(w){}
}edge[MAXN<<2];
il add_edge(int u,int v,int w){
    edge[++num_edge]=Edge(head[u],v,w),head[u]=num_edge;
    edge[++num_edge]=Edge(head[v],u,w),head[v]=num_edge;
}
il build(int u,int fa){
    bool fla=0;
    for(ri i=head[u];i!=-1;i=edge[i].next){
        if(edge[i].to==fa) continue;
        edge[i^1].w=0;
        build(edge[i].to,u),fla=1;
    }
    if(!fla) add_edge(u,t,INF);
}
inl bool BFS(int s,int t){
    del(dis,0),dis[s]=1;
    queue<int> q;q.push(s);
    while(!q.empty()){
        ri pos=q.front();q.pop();
        for(ri i=head[pos];i!=-1;i=edge[i].next)
            if(!dis[edge[i].to]&&edge[i].w>0){
                dis[edge[i].to]=dis[pos]+1;
                if(edge[i].to==t) return true;
                q.push(edge[i].to);
            }
    }
    return false;
}
it DFS(int now,int t,int flow){
    if(now==t) return flow;
    ri s=0,k;
    for(ri &i=cur[now];i!=-1;i=edge[i].next)
        if(dis[edge[i].to]==dis[now]+1&&edge[i].w>0){
            k=DFS(edge[i].to,t,min(flow-s,edge[i].w));
            s+=k,edge[i].w-=k,edge[i^1].w+=k;
            if(s==flow) break;
        }
    if(s==0) dis[now]=0;
    return s;
}
it Dinic(int s,int t){
    ri res=0;
    while(BFS(s,t)){
        memcpy(cur,head,sizeof(head));
        res+=DFS(s,t,INF);
    }
    return res;
}
int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    read(n),read(s),t=n+1,del(head,-1);
    for(ri i=1;i<n;++i) read(u),read(v),read(d),add_edge(u,v,d);
    build(s,0);
    printf("%d",Dinic(s,t));
    return 0;
}

解法二:

#include<bits/stdc++.h>
#define del(a,i) memset(a,i,sizeof(a))
#define ll long long
#define inl inline
#define il inl void
#define it inl int
#define ill inl ll
#define re register
#define ri re int
#define rl re ll
#define mid ((l+r)>>1)
#define lowbit(x) (x&(-x))
#define INF 0x3f3f3f3f
using namespace std;
template<class T>il read(T &x){
    int f=1;char k=getchar();x=0;
    for(;k>'9'||k<'0';k=getchar()) if(k=='-') f=-1;
    for(;k>='0'&&k<='9';k=getchar()) x=(x<<3)+(x<<1)+k-'0';
    x*=f;
}
template<class T>il print(T x){
    if(x/10) print(x/10);
    putchar(x%10+'0');
}
ll mul(ll a,ll b,ll mod){long double c=1.;return (a*b-(ll)(c*a*b/mod)*mod)%mod;}
it qpow(int x,int m,int mod){
    int res=1,bas=x%mod;
    while(m){
        if(m&1) res=(res*bas)%mod;
        bas=(bas*bas)%mod,m>>=1;
    }
    return res%mod;
}
const int MAXN = 1e5+5;
int n,s,dp[MAXN],u,v,d,head[MAXN],num_edge;
struct Edge{
    int next,to,dis;
    Edge(){}
    Edge(int next,int to,int dis):next(next),to(to),dis(dis){}
}edge[MAXN<<1];
il add_edge(int u,int v,int dis){
    edge[++num_edge]=Edge(head[u],v,dis),head[u]=num_edge;
    edge[++num_edge]=Edge(head[v],u,dis),head[v]=num_edge;
}
il DFS(int u,int fa){
    bool fla=0;
    for(ri i=head[u];i;i=edge[i].next){
        if(edge[i].to==fa) continue;fla=1;
        DFS(edge[i].to,u),dp[u]+=min(dp[edge[i].to],edge[i].dis);
    }
    if(!fla) dp[u]=INF;
}
int main()
{
//  freopen(".in","r",stdin);
//  freopen(".out","w",stdout);
    read(n),read(s);
    for(ri i=1;i<n;++i) read(u),read(v),read(d),add_edge(u,v,d);
    DFS(s,0);
    printf("%d",dp[s]);
    return 0;
}

总结

最小割也是网络流的一个重要分支,也是一个建图游戏,多做题,找到感觉,学习不同的技巧,才能够自己熟练掌握。

原文地址:https://www.cnblogs.com/TheShadow/p/11375198.html

时间: 2024-07-31 10:37:45

Luogu P3931 SAC E#1 - 一道难题 Tree 题解的相关文章

l洛谷 P3926 SAC E#1 - 一道不可做题 Jelly

P3926 SAC E#1 - 一道不可做题 Jelly 题目背景 SOL君(炉石主播)和SOL菌(完美信息教室讲师)是好朋友. 题目描述 SOL君很喜欢吃蒟蒻果冻.而SOL菌也很喜欢蒟蒻果冻. 有一天,他们在一起搓炉石,而SOL菌则要拿出蒟蒻果冻招待他的客人. 蒟蒻果冻一般在a度下保存在冰箱里.但是刚拿出来的时候太冰了,需要加热.SOL菌打算用一种神奇的电炉加热蒟蒻果冻.根据观察,它有一个特点: 1.蒟蒻果冻小于c度的时候,每p单位时间加热1单位温度: 2.当蒟蒻果冻等于c度的时候,需要q单位

HDU 1754 I hate it 分段树Segment Tree题解

给出一段数据,然后要更新单个数据,会询问一段数据中的最大值. 又是一道分段树操作.渐渐熟手了. #pragma once #include <cstdio> #include <algorithm> using namespace std; class IHateIt_1754_1 { static const int SIZE = 200001; int *segTree; //不要使用segTree[SIZE<<2] inline int lChild(int r)

HDU 1251 统计难题 Trie题解

基本上是标准的寻找前缀的问题,只需要insert和search函数就可以了. 我这里主要是修改一下n的记录方法,这里的n代表的不是叶子节点的标志,而是有多少单词经过了这条路径的标志. 然后是查找需要查找的前缀单词,如果没有找到,就返回0,表示没有单词以这个前缀单词为前缀,如果找到,直接返回n就是答案了.因为有n个单词经过了这条路径. 查找效率是常数. 使用静态分配空间的办法. #include <stdio.h> #include <string.h> const int MAX_

POJ 2499 Binary Tree 题解

本题使用所谓的辗转相除法. 还需要逆过来遍历二叉树.可以想象给出的数据点是根节点,然后遍历到根节点(1,1). 考的是根据给出的规则,总结规律的能力. #include <stdio.h> namespace BinaryTree2499_1 { int main() { int T, a, b, le, ri; scanf("%d", &T); for (int t = 1; t <= T; t++) { scanf("%d %d", &

LeetCode: Maximum Depth of Binary Tree 题解

Given a binary tree, find its maximum depth. The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. 题解: 题意比较清楚, 找到从root出发最长的一条路径的长度. 采用DFS即可. 相似的一道题: Minimux Depth of Binary Tree 解法: http://

HDU 1166 敌兵布阵 Segment Tree题解

本题是最基本的分段树操作了.或者一般叫线段树,不过好像和线段没什么关系,只是分段了. 不使用lazy标志,更新只是更新单点. 如果不使用分段树,那么更新时间效率只需要O(1),使用分段树更新效率就需要O(lgn)了. 但是不是用分段树,那么查询的时间效率是O(n),而分段树查询效率是O(lgn) 这就是amortize分摊了时间,而且lgn真的很快,数据不是非常巨大的时候,接近常数了. 故此本题需要使用分段树. #include <cstdio> class EnemyInfo { stati

luogu P3690 【模板】Link Cut Tree (动态树)

https://www.luogu.org/problemnew/show/3690 这大概还是一道模板题目 #include<cstdio> #include<algorithm> const int maxn = 320007; inline int read() { int x=0,f=1;char c=getchar(); while(c<'0'||c>'9') { if(c=='-')f=-1;c=getchar(); } while(c<='9'&am

一道难题

描述 Today, I want to take a few minutes to speak with you-directly and clearly-about Ebola: what we're doing about it, and what you need to know. Because meeting a public health challenge like this isn't just a job for government. All of us-citizens,

luogu 3413 SAC#1 - 萌数

题目描述 辣鸡蒟蒻SOL是一个傻逼,他居然觉得数很萌! 好在在他眼里,并不是所有数都是萌的.只有满足“存在长度至少为2的回文子串”的数是萌的——也就是说,101是萌的,因为101本身就是一个回文数:110是萌的,因为包含回文子串11:但是102不是萌的,1201也不是萌的. 现在SOL想知道从l到r的所有整数中有多少个萌数. 由于答案可能很大,所以只需要输出答案对1000000007(10^9+7)的余数. 输入输出格式 输入格式: 输入包含仅1行,包含两个整数:l.r. 输出格式: 输出仅1行