图论习题

本文通过三道图论题目展示了图论在竞赛编程中的应用。第一题求解军队走遍所有城市的最短时间,利用深度优先搜索和贪心策略;第二题涉及树上两点间的最短路径期望值计算,通过边权更新和动态规划解决;第三题探究有向图中增加流量的操作次数,通过路径合并和容量调整策略得出答案。这些题目揭示了图论在解决实际问题中的强大能力。
摘要由CSDN通过智能技术生成

2020CCPC秦皇岛 K. Kingdom’s Power

链接:https://codeforces.com/gym/102769/problem/K

题意: n 个城市可以看做一棵以 1 为根的树,在城市 1 有无穷支军队。每天可以派遣一支军队移动一次。问最少需要几天才能使军队走遍所有的城市

思路:容易想到直接贪心,在叶节点的位置的军队,要么就是从其他叶节点走过来,要么就是从 1 号节点走过来。

  • 首先将每个节点的子节点按照叶节点的深度排序,按贪心的思维,肯定是先访问深度浅的点,然后再考虑,是从浅的点移动还是从根节点移动
  • 在dfs2 的时候是需要实现:具体的 dis 来源于叶节点还是根节点,根节点就是 depth[u] ,来自叶节点的需要获得往下递归,然后返回的值。

总结:思路不难要善于实现和总结

#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1e6+5;

int t,n;
vector<int> e[maxn];
int depth[maxn],a[maxn],d[maxn];

bool cmp(int a,int b)
{
    return d[a]<d[b];
}

int dfs1(int u,int fa)
{
    int dis=0;
    for(auto v: e[u])
    {
        depth[v]=depth[u]+1;
        d[v]=dfs1(v,u);
        dis=max(dis,d[v]+1);
    }
    sort(e[u].begin(),e[u].end(),cmp);
    return dis;
}

int dfs2(int u,int dis)
{
    if(e[u].empty())
    {
        a[u]=dis;
        return 1;
    }
    int mindis=dis;
    for(auto v: e[u])
        mindis=min(depth[u],dfs2(v,mindis+1));
    return mindis+1;
}

int main()
{
    int Case=0;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(int i=1; i<=n; ++i) e[i].clear(),a[i]=d[i]=0;
        for(int v=2; v<=n; ++v)
        {
            int u;
            scanf("%d",&u);
            e[u].push_back(v);
        }
        dfs1(1,0);
        dfs2(1,0);
        ll ans=0;
        for(int i=1; i<=n; ++i)
            if(e[i].size()==0) ans+=a[i];
        printf("Case #%d: %lld\n",++Case,ans);
    }
    return 0;
}

2020CCPC威海 C Rencontre

题意:给定一棵树带有边权和3个点集。分别从 3 个点集中任意选择 1 个点 u 1 , u 2 , u 3 u_1,u_2,u_3 u1,u2,u3,求它们的期望值:
f ( u 1 , u 2 , u 3 ) = m i n v ∈ T ( d i s ( u 1 , v ) + d i s ( u 2 , v ) + d i s ( u 3 , v ) ) f(u_1,u_2,u_3)=min_{v\in T}(dis(u_1,v)+dis(u_2,v)+dis(u_3,v)) f(u1,u2,u3)=minvT(dis(u1,v)+dis(u2,v)+dis(u3,v))

思路:简单分析,可以知道这个 v 肯定是在 u 1 , u 2 , u 3 u_1,u_2,u_3 u1,u2,u3 最短路的相交处

f ( u 1 , u 2 , u 3 ) = 1 2 ( d i s ( u 1 , u 2 ) + d i s ( u 2 , u 3 ) + d i s ( u 3 , u 1 ) ) f(u_1,u_2,u_3)=\frac 12(dis(u_1,u_2)+dis(u_2,u_3)+dis(u_3,u_1)) f(u1,u2,u3)=21(dis(u1,u2)+dis(u2,u3)+dis(u3,u1))

  • 然后就可以枚举每条边,然后计算每条边的贡献
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=2e5+5;

int n;
vector<pair<int,int> > e[maxn];
int m[4],sz1[maxn],sz2[maxn],sz3[maxn];
long double ans;
ll tot;

void dfs1(int u,int fa)
{
    for(auto x: e[u])
    {
        int v=x.fi;
        if(v==fa) continue;
        dfs1(v,u);
        sz1[u]+=sz1[v];
        sz2[u]+=sz2[v];
        sz3[u]+=sz3[v];
    }
}
void dfs2(int u,int fa)
{
    for(auto x: e[u])
    {
        int v=x.fi,w=x.se;
        if(v==fa) continue;
        ans+=(long double)(sz1[1]-sz1[v])*sz2[v]*sz3[v]/tot*w;
        ans+=(long double)(sz2[1]-sz2[v])*sz1[v]*sz3[v]/tot*w;
        ans+=(long double)(sz3[1]-sz3[v])*sz1[v]*sz2[v]/tot*w;
        ans+=(long double)sz1[v]*(sz2[1]-sz2[v])*(sz3[1]-sz3[v])/tot*w;
        ans+=(long double)sz2[v]*(sz1[1]-sz1[v])*(sz3[1]-sz3[v])/tot*w;
        ans+=(long double)sz3[v]*(sz1[1]-sz1[v])*(sz2[1]-sz2[v])/tot*w;
        dfs2(v,u);
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1; i<=n-1; ++i)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        e[u].push_back({v,w});
        e[v].push_back({u,w});
    }
    int x;
    scanf("%d",&m[1]);
    for(int i=1; i<=m[1]; ++i) scanf("%d",&x),sz1[x]=1;
    scanf("%d",&m[2]);
    for(int i=1; i<=m[2]; ++i) scanf("%d",&x),sz2[x]=1;
    scanf("%d",&m[3]);
    for(int i=1; i<=m[3]; ++i) scanf("%d",&x),sz3[x]=1;
    tot=1ll*m[1]*m[2]*m[3];
    dfs1(1,0);
    dfs2(1,0);
    cout<<fixed<<setprecision(8)<<ans<<"\n";
    return 0;
}

2019ICPC西安 E. Flow

题意:给定 n 个点 m 条边的有向图,从 1 到 n 会形成 k 条等长的路径,每条边上都有容量。现在可以将其中一条边容量 -1 ,另一条边的容量 + 1 。问至少操作几次可以使从 1 到 n 流过的流量最大

思路

  • 第一眼看到:感觉每条边的流量应该是:总流量平均到每一条边 = t o t m \frac {tot}{m} mtot,但其实这样是错的,多余的部分,依然可以在集中在一条路径上。一条路径的边数为: c n t = m k cnt = \frac mk cnt=km ,那么 k 条路径合在一起达到的平均值应该是: t o t c n t \frac {tot}{cnt} cnttot,这个才是最终的最大流量。
  • 那么就可以将 k 条路径当成一条来看。把每条路径的容量,从小到大排序,然后把 k 条路径合在一起。此时,平均流量和总容量的差值,就是需要操作的次数。
  • 这里我们无需关心,这个操作是怎么实现的,只需要关注当前状态和最终状态之间的差值即可
#include <bits/stdc++.h>
#define fi first
#define se second
#define ll long long
using namespace std;
const int maxn=1e5+10;
int n,m;
vector<pair<int,int> > e[maxn];
vector<int> we[maxn];
int main()
{
    scanf("%d%d",&n,&m);
    ll tot=0;
    for(int i=1;i<=m;++i)
    {
        int u,v,w;
        scanf("%d%d%d",&u,&v,&w);
        e[u].push_back({v,w});
        tot+=w;
    }
    int cnt=e[1].size();
    for(int i=0;i<cnt;++i)
    {
        auto x=e[1][i];
        int u=x.fi;
        int w=x.se;
        we[i].push_back(w);
        while(u!=n)
        {
            w=e[u][0].se;
            u=e[u][0].fi;
            we[i].push_back(w);
        }
    }
    for(int i=0;i<cnt;++i) sort(we[i].begin(),we[i].end());
    ll ans=0;
    ll avg=tot/we[0].size();

    for(int i=0;i<we[0].size();++i)
    {
        ll sum=0;
        for(int j=0;j<cnt;++j) sum+=we[j][i];
        ans+=max(0ll,avg-sum);
    }
    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值