两道二分图建模的题。感觉脑子已经被掏空

本文介绍了两道算法题目,一是寻找螃蟹图的最大数量,二是计算完成所有任务所需的最少天数。通过图论与网络流算法解决,涉及拆点构建网络流及匈牙利算法。

Crab Graphs

题目大意:

定义螃蟹图是这样的无向图:有n+1个点,其中一个点(head)连接其他的n个点(foot),另外n个点与head点相连。

给你一个无向图和整数k,问其中至多有几个**不超过**k个feet的不相交的螃蟹图?

题目分析:

之前在hihocoder里面见过一个拆点构建网络流的题,这里又见到一道。不过还是没太挖掘出这类题目的共性。

算法如下:
1. 把原图中每个点i拆成2i和2i+1两个点
2. 如果原图中存在边 <i,j> ,则在新图里连上 <2i,2j+1>,<2j,2i+1> ,流量为1
3. 取源点为1,连接1和所有2i的点,流量为k
4. 取汇点为2n+2,连接2i+1和汇点,流量为1
5. 最大流即为所求。

其实知道了这个模型,好像也确实是这么回事,不过想到这个建模应该是本题的难点,剩下的都是套模板,没啥好说的。这方面的题还是做得太少,而且网络流在笔试面试中只见过一道,一般出现在ACM中~

#include <bits/stdc++.h>
using namespace std;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
#define INF 0x3f3f3f3f
typedef long long ll;
int c,n,t,m;
struct Edge {
    int next,to,flow;
}edge[40005];
int head[1230],p,level[1230];
int deg[123];
void addedge(int u,int v,int w) {
    edge[p].to=v;
    edge[p].flow=w;
    edge[p].next=head[u];
    head[u]=p;p++;

    edge[p].to=u;
    edge[p].flow=0;
    edge[p].next=head[v];
    head[v]=p;p++;
}
void init() {
    memset(head,-1,sizeof(head));
    memset(deg,0,sizeof(deg));
    memset(edge,0,sizeof(edge));
    p=0;
}
bool bfs(int s,int t) {
    memset(level,0,sizeof(level));
    queue<int> q;
    q.push(s);
    level[s]=1;
    while(!q.empty()) {
        int u=q.front();
        q.pop();
        if(t==u)
            return true;
        for(int i=head[u];i!=-1;i=edge[i].next) {
            int v=edge[i].to,f=edge[i].flow;
            if(level[v] == 0 && f!=0) {
                q.push(v);
                level[v]=level[u]+1;
            }
        }
    }
    return false;
}
int dfs(int u,int maxf,int t) {
    if(u==t)
        return maxf;
    int temp=0;
    for(int i=head[u];i!=-1&&temp<maxf;i=edge[i].next) {
        int v=edge[i].to,f=edge[i].flow;
        if(level[v] == level[u]+1 && f!=0) {
            f=dfs(v,min(maxf-temp,f),t);
            edge[i].flow-=f;edge[i^1].flow+=f;temp+=f;
        }
    }
    if(!temp)
        level[u]=INF;
    return temp;
}
int dinic(int s,int t) { //源点s,汇点t,求最大流
    ll ans=0;
    while(bfs(s,t))
        ans+=dfs(s,INF,t);
    return ans;
}
int main() {
    cin>>c;
    while(c--) {
        init();
        cin>>n>>t>>m;
        int u,v;
        for(int i=0;i<m;i++) {
            cin>>u>>v;
            deg[u]++;deg[v]++;
            addedge(2*u,2*v+1,1);
            addedge(2*v,2*u+1,1);
        }
        for(int i=1;i<=n;i++) {
            addedge(1,2*i,t);
            addedge(2*i+1,2*n+2,1);
        }
        cout<<dinic(1,2*n+2)<<endl;
    }

}

problem-solving

题目大意:

有n道题,第i道题的难度为i,评分为r[i].

每一天,你可以选择其中的若干道题来做,有两个要求:
* 难度必须是递增的;
* 相邻两道题的评分差距不能小于K

问至少几天完成全部的题?

题目分析:

把这n道题看做n个点,对 i<j,|r[i]r[j]|k的点对加有向边,然后得到一张dag图,(不要问我为什么没环,自己想去!!)答案就是这个图的最小路径覆盖。

拆点构建二分图,跑匈牙利算法或者网络流,都行。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define RE(x) freopen(x,"r",stdin)
#define WR(x) freopen(x,"w",stdout)
#define INF 0x3f3f3f3f
const int M = 1e9+7;
const double PI = acos(-1.0);
int t,n,k;
int v[1005];
int f[2005];
bool vis[2005];
vector<int> g[2010];
void addedge(int i,int j) {
    g[2*i-1].push_back(2*j);
    g[2*j].push_back(2*i-1);
}
bool dfs(int i) {
    vis[i]=1;
    for(int j=0;j<g[i].size();j++) {
        int u=g[i][j];
        if(f[u]==-1 || (!vis[f[u]] && dfs(f[u]))) {
            f[u]=i;
            f[i]=u;
            return true;
        }
    }
    return false;
}
int main() {
    //RE("in.txt");WR("out.txt");
    cin>>t;
    while (t--) {
        cin>>n>>k;
        memset(g,0,sizeof(g));
        for(int i=1;i<=n;i++) {
            cin>>v[i];
        }
        for(int i=1;i<=n;i++) {
            for(int j=i+1;j<=n;j++) {
                if(abs(v[i]-v[j])>=k)
                    addedge(i,j);
            }
        }
        memset(f,-1,sizeof(f));
        int cnt=0;
        for(int i=1;i<=n*2;i++) {
            if(f[i]==-1) {
                memset(vis,0,sizeof(vis));
                cnt+=dfs(i);
            }
        }
        cout<<n-cnt<<endl;
    }
}

总结一下,这俩题在算法实现上都非常简单,难点在于如何用图建模,感觉自己脑子已经生锈了。。。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值