2020 Multi-University Training Contest(杭电多校图论好题)

2020杭电多校暑期集训

图论个人总结

Day 4:
补题的时候越发觉得杭电的图论题出的非常好,也是是我做的比较少
这是最近补的两题,比较菜,比赛的时候都没出,补完之后真的觉得题非常好

题目传送门
在这里插入图片描述

题意: 有n个节点、m条边构成的无向图,每个节点有特殊的要求,L只能用左手,R只能用右手,M都可以,左右换手需要额外的时间,让你求从s到t花费的最小时间。

比赛的时候觉得这是一道很裸的迪杰斯特拉,但是难点在于当前左右手的状态影响到下一次路径的距离,当时觉得在建图的时候可以把换手的时间算进权值里,但还是不好处理。题解就非常的妙,非常的妙。

讲每个节点拆分成两个点s1,s2,s1表示当前左手经过该节点,s2表示右手经过该节点,则可以对s1,s2分别建边,若该节点只能左手通过,则s2不存在,同理可建立一张新图,但是,这时候要至少跑4边Dijkstra才能得到最短路,因为不知道s、t的左右手状态。这时候,我们将S0连接s1、s2,S(2n+1)连接t1、t2,计算从S0到S(2n+1)就可以只通过1次Dijkstra得到所求的最短路径了。

这是真的妙啊,应该是我菜,想不到,希望以后就会了
贴一下代码

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1e6+5; //注意这里一定要开大一点,不然RE到吐
const ll mod=1e9+7;
typedef pair<ll,ll> P;
ll n,m,ss,t,x;
int fir[maxn],nex[maxn],to[maxn],cnt;
ll val[maxn];
char s[maxn];
void add_edge(int u,int v,ll w){  //前向星建图
    nex[++cnt]=fir[u];
    fir[u]=cnt;
    to[cnt]=v;
    val[cnt]=w;
}
struct node{ //堆优化的Dijkstra
    int idex;
    ll dis;
    node(){}
    node(int idex,ll dis) : idex(idex),dis(dis){}
    bool operator <(const node &rhs) const
    {
        return dis>rhs.dis;
    }
};
ll dis[maxn];
int vis[maxn];
void Dijkstra(int s){
    for(int i=0;i<=2*n+1;i++){
        vis[i]=0;
        dis[i]=inf;
    }
    dis[s]=0;
    priority_queue<node> q;
    q.push(node(s,0));
    while(!q.empty()){
        node u=q.top();
        q.pop();
        if(vis[u.idex]==1) continue;
        vis[u.idex]=1;
        for(int e=fir[u.idex];e;e=nex[e]){
            int v=to[e];
            ll w=val[e];
            if(u.dis+w<dis[v]){
                dis[v]=u.dis+w;
                q.push(node(v,dis[v]));
            }
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--){
        scanf("%lld %lld %lld %lld %lld",&n,&m,&ss,&t,&x);
        scanf(" %s",s);
     //   cout<<s<<'\n';
        for(int i=0;i<=cnt;i++){
            nex[i]=fir[i]=to[i]=val[i]=0;
        }
        cnt=0;
        while(m--){
            ll u,v,d;
            scanf("%lld %lld %lld",&u,&v,&d);
            if(s[u-1]=='L'&&s[v-1]=='L'){ //都是左手 花费为d
                add_edge(u,v,d);
                add_edge(v,u,d);
            }
            else if(s[u-1]=='L'&&s[v-1]=='R'){ //一左一右  花费d+x
                add_edge(u,n+v,d+x);
                add_edge(n+v,u,d+x);
            }
            else if(s[u-1]=='L'&&s[v-1]=='M'){
                add_edge(u,v,d);
                add_edge(v,u,d);
                add_edge(u,n+v,d+x);
                add_edge(n+v,u,d+x);
            }
            else if(s[u-1]=='R'&&s[v-1]=='R'){
                add_edge(n+u,v+n,d);
                add_edge(v+n,n+u,d);
            }
            else if(s[u-1]=='R'&&s[v-1]=='L'){
                add_edge(n+u,v,d+x);
                add_edge(v,n+u,d+x);
            }
            else if(s[u-1]=='R'&&s[v-1]=='M'){
                add_edge(u+n,v,d+x);
                add_edge(v,u+n,d+x);
                add_edge(u+n,n+v,d);
                add_edge(n+v,u+n,d);
            }
            else if(s[u-1]=='M'&&s[v-1]=='L'){
                add_edge(u,v,d);
                add_edge(v,u,d);
                add_edge(u+n,v,d+x);
                add_edge(v,u+n,d+x);
            }
            else if(s[u-1]=='M'&&s[v-1]=='R'){
                add_edge(u,v+n,d+x);
                add_edge(v+n,u,d+x);
                add_edge(u+n,v+n,d);
                add_edge(v+n,u+n,d);
            }
            else if(s[u-1]=='M'&&s[v-1]=='M'){
                add_edge(u,v,d);
                add_edge(v,u,d);
                add_edge(u+n,v,d+x);
                add_edge(v,u+n,d+x);
                add_edge(u,v+n,d+x);
                add_edge(v+n,u,d+x);
                add_edge(u+n,v+n,d);
                add_edge(v+n,u+n,d);
            }
        }
        add_edge(0,ss,0); //连接起点和。第一个点
        add_edge(ss,0,0);
        add_edge(0,n+ss,0);
        add_edge(n+ss,0,0);
        add_edge(2*n+1,t,0);//连接终点和最后一个点
        add_edge(t,2*n+1,0);
        add_edge(t+n,2*n+1,0);
        add_edge(2*n+1,t+n,0);
        Dijkstra(0);
        printf("%lld\n",dis[2*n+1]);
    }
    return 0;
}

Day 6:
这题比赛的时候想到跟最小生成树有关,但后续处理没有想到,再加上卡在01题,也是赛后补的。有一说一,对dfs还是要多写!多写!多写!
题目传送门
在这里插入图片描述

题意:给你一个n个节点m条边的无向图,题目要求所有白点和黑点最短路的值。 每条边的权值是2^i,由简单数学可知,所以如果两个点能通过前i-1条边到达,那肯定比通过第i条更优,所以我们从1到n按顺序建最小生成树。

但怎么计算每条边的贡献?一开始我也万脸懵逼,看到这句话才知道,建立最小生成树,树上的所有边对答案都是有贡献的,现在考虑单边对答案的贡献。我们可以用一遍DFS处理出每条边左边的1号点个数、右边的0号点个数,两者相乘再乘上这条边的权值,左右相反同理,这样就可以算出单边对答案的贡献。 这应该就是解这道题目的关键了。说实话,之前只会克鲁斯卡尔的裸题,这次算是学到了。希望下次比赛也能想到!
详细可见代码注释:

#include<iostream>
#include<vector>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<set>
#include<algorithm>
#include<queue>
#include<stack>
#include<list>
#include<map>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1e5+5;
const ll mod=1e9+7;
typedef pair<ll,ll> P;
int a[maxn],pre[maxn];
int num1,num0;//总0、1数,方便后面容斥
ll dp[maxn][2],ans;
vector<P> G[maxn];
void init(int n){   //基本裸的克鲁斯卡尔
    for(int i=1;i<=n;i++){
        pre[i]=i;
    }
}
int find(int x){
    if (pre[x] == x ) return x;
    else return pre[x]=find(pre[x]);
}
void dfs(int u,int pre){
    dp[u][0]=dp[u][1]=0;//dp[i][0]表示该节点后连着的0,dp[i][1]表示该节点后连着的1
    dp[u][a[u]]++;//加这个节点
    for(auto it=G[u].begin();it!=G[u].end();it++){
        int v=it->first;
        if(v==pre) continue; //这一步很重要,不然会将该节点前的0、1也计入
        dfs(v,u);  //得到下一节点v的0、1数
        dp[u][0]+=dp[v][0];
        dp[u][1]+=dp[v][1];
    }
    for(auto it=G[u].begin();it!=G[u].end();it++){
        int v=it->first;
        if(v==pre) continue; //不能少,否则重复计算
        ans=(ans+(1ll*(num1-dp[v][1])*dp[v][0]%mod)*it->second%mod)%mod;
        ans=(ans+(1ll*(num0-dp[v][0])*dp[v][1]%mod)*it->second%mod)%mod;
        //核心步骤  其中num1-dp[v][1]表示该节点之前的1,num0-dp[v][0]表示该节点前的1
        //容斥的思想,妙啊
    }
}
int main()
{
    int T;
    cin>>T;
    while(T--){
        int n,m;
        scanf("%d %d",&n,&m);
        init(n);
        num1=num0=0;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            dp[i][0]=dp[i][1]=0;
            G[i].clear();
            if(a[i]==1) num1++;
            else num0++;
        }
        ll w=1;
        ans=0;
        for(int i=1;i<=m;i++){
            w=w*2%mod;
            int u,v;
            scanf("%d %d",&u,&v);
            int fx=find(u),fy=find(v);
            if(fx!=fy){  //克鲁斯卡尔建图
                G[u].push_back({v,w});
                G[v].push_back({u,w});
                pre[fx]=fy;
            }
        }
        dfs(1,-1);
        printf("%lld\n",ans%mod);
    }
    return 0;
}

个人比较菜,能补的题补的都挺慢,接下来杭电的题会坚持补,如果有问题欢迎指出,一起讨论。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值