图论题集

本文深入探讨了多种图论算法,包括求解图的补图最小生成树、寻找第k短路径、处理互斥关系的01背包问题,以及如何在仙人掌图中找到删除边的所有可能方式。通过实例解析,提供了详细的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://acm.hdu.edu.cn/showproblem.php?pid=6736

题意:

仙人掌定义:无重边、自环,每条边被最多一个简单环包含

给定一个图,图中各连通块都是仙人掌

然后让你删除一些边,使得这个图是都是一些树,问你删除的方式有多少种

解析:

删除环上的至少1边,设环长为len,种类有ans1=2^(len)-1种,减掉不删的情况

每个环都要删除ans=ans1*ans2*ans3*....

最后剩下k条边,剩下的边可删可不删,种类有2^k种

转化为求图中各环上边的数目

我们先将入度为1的点及连接的边删除,用一个时间戳来计算,标记点,标记边

ac:

#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
#define MAXN 500005
using namespace std;
int to[MAXN<<1],nxt[MAXN<<1],head[MAXN<<1];
int tot=0;
int dfn[MAXN<<1];
int vis[MAXN<<1];
int vis2[MAXN<<1];
ll ans;
ll res;
int in[MAXN<<1];

void init()
{
    memset(head,0,sizeof(head));
    for(int i=0;i<MAXN;i++)
        in[i]=vis[i]=vis2[i]=dfn[i]=0;
    ans=1;
    res=0;
    tot=1;
}

void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=head[u];
    head[u]=tot;
}

ll qpow(ll a,ll b)
{
    ll ans=1;
    while(b)
    {
        if(b&1) ans=(ans*a)%mod;
        a=(a*a)%mod;
        b>>=1;
    }
    return ans;
}

void dfs(int x,int sum)
{
    dfn[x]=sum;
    vis[x]=1;
    for(int i=head[x];i;i=nxt[i])
    {
        int v=to[i];
        if(vis2[i^1]==1)//每条边只走一次
            continue;
        vis2[i]=1;
        if(vis[v]!=0)//该点走过计数
        {
            res+=dfn[x]-dfn[v]+1;
            ans=(ans*(qpow(2,(dfn[x]-dfn[v]+1))-1+mod))%mod;
        }
        else{
            dfs(v,sum+1);
        }
    }
}

int u[MAXN],v[MAXN];

int main()
{
    int t,n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init();
        for(int i=1;i<=m;i++)
            scanf("%d%d",&u[i],&v[i]),in[u[i]]++,in[v[i]]++;
        for(int i=1;i<=m;i++)
        {
            if(in[u[i]]>1&&in[v[i]]>1)
                add(u[i],v[i]),add(v[i],u[i]);
        }
        for(int i=1;i<=n;i++)
        {
            if(vis[i]==0)
                dfs(i,0);
        }
        ans=(ans*qpow(2,m-res))%mod;
        printf("%lld\n",ans);
    }
    return 0;
}

https://nanti.jisuanke.com/t/39271

题意:

给定n个物品,然后给定m种互斥关系,每个互斥关系有两个物品,这两个物品不能在同一堆

你将这n个物品分成两堆,让这两堆的差尽可能的小,输出大的呢堆的价值

解析:

二分图染色+01背包

首先要差尽可能的小,呢么我们就求sum/2容量下,最多装多少,然后用sum减去,就是答案

这么处理互斥关系呢

我们用二分图染色处理

对于每个连通块,同一种颜色的可以合并,最后得到num个合并后的物品

最后我们求一下容积为sum/2的背包即可,注意这里物品价值都是100的倍数,所以求背包的时候不如将a[i]全部/100,最后在乘回去

降低100倍复杂度

ac:

#include<bits/stdc++.h>
#define ll long long
#define MAXN 200005
using namespace std;
int to[MAXN<<1],head[MAXN<<1],nxt[MAXN<<1];
int tot;
int a[MAXN];
int vis[MAXN];
int val[MAXN],num;
int dp[MAXN];
ll aa=0,bb=0;
 
void init()
{
    memset(vis,0,sizeof(vis));
    num=0;
    memset(head,0,sizeof(head));
    memset(dp,0,sizeof(dp));
    tot=1;
}
 
void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=head[u];
    head[u]=tot;
}
 
void dfs(int x,int sign)
{
    vis[x]=1;
    
    if(sign)
        aa+=a[x];
    else
        bb+=a[x];
    for(int i=head[x];i;i=nxt[i])
    {
        int v=to[i];
        if(vis[v])
            continue;
        dfs(v,1-sign);
    }
 
}
 
int main()
{
    int t,n,m,u,v,x;
    scanf("%d",&t);
    while(t--)
    {
        init();
        scanf("%d%d",&n,&m);
        ll sum=0;
        for(int i=1;i<=n;i++)
            scanf("%d",&x),a[i]=x/100,sum+=a[i];
        for(int i=1;i<=m;i++)
            scanf("%d%d",&u,&v),add(u,v),add(v,u);
        for(int i=1;i<=n;i++)//对于每个连通块,同一种颜色的可以合并,最后得到num个合并后的物品
        {
            aa=0,bb=0;
            if(vis[i]==0)
                dfs(i,1);
            if(aa!=0)
                val[++num]=aa;
            if(bb!=0)
                val[++num]=bb;
        }
        int m=sum/2;
        for(int i=1;i<=num;i++)//进行01背包
            for(int j=m;j>=val[i];j--)
                dp[j]=max(dp[j],dp[j-val[i]]+val[i]);
        int ans=(sum-dp[m])*100;
        printf("%d\n",ans);
    }
    return 0;
}

http://acm.hdu.edu.cn/showproblem.php?pid=6705

题意:

给定一个图,问图中第k短的路径的长度,单条边可以重复走,不同路径不能完全相同.

解析:

对从同一点出发的边排序,然后装入vector中,方便调用

用广搜处理这题,怎么按顺序得最短是难点

对于一个已知的路径

1.可以选择加一条边得新路径

2.可以现在将上次加的边删去,加上比上次加的边长一些的边,得到新路径(按顺序加边,长度依次递增)

以优先队列处理,第i个弹出的,即为第k小路径

ac:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define MAXN 100005
using namespace std;
ll ans[MAXN];
int query[MAXN];
int tot=0;
struct edge{
    int v,len;
    friend bool operator< (edge a,edge b)
    {
        return a.len<b.len;
    }
};
vector<edge> vc[MAXN];

struct node
{
    int id,u;
    ll len;
    friend bool operator <(node a,node b)
    {
        return a.len>b.len;
    }
};

int main()
{
    int t,n,m,q,u,v,w;
    scanf("%d",&t);
    while(t--)
    {
        for(int i=0;i<MAXN;i++)
            vc[i].clear();
        scanf("%d%d%d",&n,&m,&q);
        for(int i=1;i<=m;i++)
            scanf("%d%d%d",&u,&v,&w),vc[u].pb(edge{v,w});
        for(int i=1;i<=n;i++)
            sort(vc[i].begin(),vc[i].end());//对同出发点的边排序
        int maxs=0;
        for(int i=1;i<=q;i++)
        {
            scanf("%d",&query[i]);
            maxs=max(maxs,query[i]);
        }
        priority_queue<node> que;
        for(int i=1;i<=n;i++){
            if(vc[i].size())
                que.push(node{0,i,vc[i][0].len});
        }
        int cnt=0;
        while(que.size())
        {
            node x=que.top();
            que.pop();
            ans[++cnt]=x.len;
            if(cnt==maxs)
                break;
            if(x.id<(vc[x.u].size()-1)){//原出发点还有未加的且更长的边,删除最近加的边,然后加上一个更长的边
                que.push(node{x.id+1,x.u,x.len-vc[x.u][x.id].len+vc[x.u][x.id+1].len});
            }
            int v=vc[x.u][x.id].v;
            if(vc[v].size()){//重现选择一条新边加上
                que.push(node{0,v,x.len+vc[v][0].len});
            }
        }
        for(int i=1;i<=q;i++)
            printf("%lld\n",ans[query[i]]);
    }
    return 0;
}

补图的最小生成树(set建边)

https://codeforces.com/contest/1243/problem/D

题意:

求你个图的补图的最小生成树

解析:

一开始所以点都没连边,我们把点全部加入一个set中,代表联通块的个数

然后我们bfs,怎么没有实边就是有虚边,我们用set建边,如果set中不存在,就是有边

ac:

#include<bits/stdc++.h>
#define pb push_back
#define MAXN 100005
using namespace std;
set<int> st,vc[MAXN];
int vis[MAXN];

void bfs(int x)
{
    queue<int> que,vg;
    que.push(x);
    st.erase(x);
    while(que.size())
    {
        int x=que.front();
        que.pop();
        vis[x]=1;
        for(auto it=st.begin();it!=st.end();it++)
        {
            int v=*it;
            if(vc[x].find(v)==vc[x].end()){
                que.push(v);
                vg.push(v);
            }
        }
        while(vg.size()){
            int v=vg.front();
            st.erase(v);
            vg.pop();
        }
    }
}

int main()
{
    int n,m,u,v;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        st.insert(i);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&u,&v);
        vc[u].insert(v);
        vc[v].insert(u);
    }
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        if(vis[i]==0){
            bfs(i);
            ans++;
        }
    }
    printf("%d\n",ans-1);
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值