《浅谈图论模型的建立与应用》
图论的建模,就是要抓住问题的本质,把问题抽象为点、边、权的关系。
Place the Robots (ZOJ 1654)
-
方案一:只关心空地与空地之间的联系,发生冲突的节点,连一条边,转换为求最大独立集。时间复杂度为:n*2^n
-
方案二:将所有横向方块,纵向方块分成X,Y部分,每个方块只能放一个机器人,那么有冲突的方块连一条边,问题转换为求最大匹配,最大匹配=最大流。
Source Code:
#include <cstdio>
#include <vector>
#include <queue>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int maxn = 1250;
const int INF = 0x3f3f3f3f;
int n,m;
char maps[maxn][maxn];
int col[maxn][maxn],row[maxn][maxn];
int head[maxn*maxn];
struct Edge
{
int to;
int next;
} edges[maxn];
int cnt;
void add(int u,int v)
{
edges[cnt].to = v;
edges[cnt].next = head[u];
head[u] = cnt++;
}
bool vis[maxn];
int match[maxn];
bool dfs(int u) {
for(int i=head[u];~i;i=edges[i].next) {
int v = edges[i].to;
if(!vis[v]) {
vis[v] = 1;
if(match[v]==-1||dfs(match[v])) {
match[v] = u;
return true;
}
}
}
return false;
}
int R,C;
int solve() {
int num = 0;
memset(match,-1,sizeof(match));
for(int i=0;i<R;i++) {
memset(vis,0,sizeof(vis));
if(dfs(i))
num++;
}
return num;
}
int main()
{
//freopen("in.txt","r",stdin);
int t;
scanf("%d",&t);
for(int z=0; z<t; z++)
{
cnt = 0;
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++)
scanf("%s",maps[i]);
R= 0,C=0;
memset(col,0,sizeof(col));
memset(row,0,sizeof(row));
memset(head,-1,sizeof(head));
for(int i=0; i<n; i++)
{
for(int j=0; j<m; j++)
{
if(maps[i][j]=='o')
{
int tmp = j;
while(tmp>=0)
{
if(maps[i][tmp]!='#')
{
row[i][tmp] = R;
tmp--;
}
else break;
}
while(j<m)
{
if(maps[i][j]!='#')
{
row[i][j] = R;
j++;
}
else break;
}
R++;
}
}
}
for(int j=0; j<m; j++)
{
for(int i=0; i<n; i++)
{
if(maps[i][j]=='o')
{
int tmp = i;
while(tmp>=0)
{
if(maps[tmp][j]!='#')
{
col[tmp][j] = C;
tmp--;
}
else break;
}
while(i<n)
{
if(maps[i][j]!='#')
{
col[i][j] = C;
i++;
}
else break;
}
C++;
}
}
}
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
if(maps[i][j]=='o')
add(row[i][j],col[i][j]);
printf("Case :%d\n%d\n",z+1,solve());
}
return 0;
}
差分约束:
x_1-x_2<=b_1
x_2-x_3<=b_2
x_3-x_4<=b_3
x_4-x_5<=b_4
\to x_1-x_5<=4\tag{1}
如果存在负环:
x_5-x_1<=-5\tag{2}
(1)(2)相加
0<=-1\tag{3}
无解,在图上表示,及存在负环。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 30;
int r[maxn];
int t[maxn];
struct Edge {
int from,to,dist;
};
struct BellmanFord {
int n,m;
vector<Edge> edges;
vector<int> G[maxn];
bool inq[maxn];
int d[maxn];
int p[maxn];
int cnt[maxn];
void init(int n) {
this->n = n;
for(int i=0;i<n;i++) G[i].clear();
edges.clear();
}
void addEdge(int from,int to,int dist) {
edges.push_back((Edge){from,to,dist});
m = edges.size();
G[from].push_back(m-1);
}
bool negativeCycle() {
queue<int> Q;
memset(inq,0,sizeof(inq));
memset(cnt,0,sizeof(cnt));
for(int i=0;i<n;i++) {
d[i] = 0;
inq[0] = true;
Q.push(i);
}
while(!Q.empty()) {
int u = Q.front();Q.pop();
inq[u] = false;
for(int i=0;i<G[u].size();i++) {
Edge& e = edges[G[u][i]];
if(d[e.to]>d[u]+e.dist) {
d[e.to] = d[u] + e.dist;
p[e.to] = G[u][i];
if(!inq[e.to]) {
Q.push(e.to);
inq[e.to] = true;
if(++cnt[e.to]>n)
return true;
}
}
}
}
return false;
}
}sol;
void build(int ans) {
sol.init(25);
for(int i=1;i<=24;i++) {
sol.addEdge(i-1,i,t[i]);
sol.addEdge(i,i-1,0);
}
for(int i=8;i<=24;i++)
sol.addEdge(i,i-8,-r[i]);
for(int i=1;i<8;i++)
sol.addEdge(i,24+i-8,ans-r[i]);
sol.addEdge(24,0,-ans);
}
int main()
{
freopen("in.txt","r",stdin);
int tt;
scanf("%d",&tt);
for(int z=0;z<tt;z++) {
bool flag = false;;
for (int i = 1; i <= 24; ++ i)
scanf("%d", &r[i]);
scanf("%d", &n);
memset(t,0,sizeof(t));
int x;
for(int i=0;i<n;i++) {
scanf("%d",&x);
x++;
t[x]++;
}
int l= 0,r=n;
int ans = -1;
while(l<=r) {
int mid = (l+r)>>1;
build(mid);
if(sol.negativeCycle()) {
ans = mid;
r = mid-1;
}
else l = mid+1;
}
printf("%d\n",ans);
}
return 0;
}
本题的卡片总数有十万之多,而最终要选取的卡片数不超过100张。如果在构图之前,把没有用的卡片先删掉,必将大大提高效率。
什么样的卡片是没有用的呢?
先考虑第一种能力的选取:如果把全部卡片按第一种能力值从大到小排序,显然我们应该尽量从前面选A张出来,由于每张卡片只能使用一次,所以有可能会和其他的两种能力发生冲突,而冲突的卡片数最多是B+C张,所以实际上对我们有用的卡片只是前面的A+B+C张。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 500, INF = 1000000000;
struct Edge
{
int from, to, cap, flow, cost;
};
struct MCMF
{
int n, m;
vector<Edge> edges;
vector<int> G[maxn];
bool inq[maxn]; // 是否在队列中
int d[maxn]; // Bellman-Ford
int p[maxn]; // 上一条弧
int a[maxn]; // 可改进量
void init(int n)
{
this->n = n;
for(int i = 0; i < n; i++) G[i].clear();
edges.clear();
}
void AddEdge(int from, int to, int cap, int cost)
{
edges.push_back((Edge)
{
from, to, cap, 0, cost
});
edges.push_back((Edge)
{
to, from, 0, 0, -cost
});
m = edges.size();
G[from].push_back(m-2);
G[to].push_back(m-1);
}
bool BellmanFord(int s, int t, int &flow, long long& cost)
{
memset(inq,0,sizeof(inq));
for(int i=0; i<n; i++)
d[i] = INF;
d[s] = 0;
inq[s] = true;
p[s] = 0;
a[s] = INF;
queue<int> Q;
Q.push(s);
while(!Q.empty())
{
int u = Q.front();
Q.pop();
inq[u] = false;
for(int i = 0; i < G[u].size(); i++)
{
Edge& e = edges[G[u][i]];
if(e.cap > e.flow && d[e.to] > d[u] + e.cost)
{
d[e.to] = d[u] + e.cost;
p[e.to] = G[u][i];
a[e.to] = min(a[u], e.cap - e.flow);
if(!inq[e.to])
{
Q.push(e.to);
inq[e.to] = true;
}
}
}
}
if(d[t] == INF) return false; //s-t 不连通,失败退出
flow += a[t];
cost += (long long)d[t] * (long long)a[t];
int u = t;
while(u != s)
{
edges[p[u]].flow += a[t];
edges[p[u]^1].flow -= a[t];
u = edges[p[u]].from;
}
return true;
}
pair<long long,int>Mincost(int s, int t)
{
long long cost = 0;
int flow = 0;
while(BellmanFord(s, t, flow, cost));
return pair<long long,int> {cost,flow};
}
} sol;
int a[100105],b[100105],c[100105],rk[100105];
int id[310];
bool cmpa(int i,int j)
{
if(a[i]==a[j])return a[i]+b[i]+c[i]>a[j]+b[j]+c[j];
return a[i]>a[j];
}
bool cmpb(int i,int j)
{
if(b[i]==b[j])return a[i]+b[i]+c[i]>a[j]+b[j]+c[j];
return b[i]>b[j];
}
bool cmpc(int i,int j)
{
if(c[i]==c[j])return a[i]+b[i]+c[i]>a[j]+b[j]+c[j];
return c[i]>c[j];
}
int main()
{
// freopen("in.txt","r",stdin);
int t;
scanf("%d",&t);
for(int i=0; i<t; i++)
{
int n;
scanf("%d",&n);
int A,B,C;
scanf("%d%d%d",&A,&B,&C);
for(int i=1; i<=n; i++)
scanf("%d%d%d",&a[i],&b[i],&c[i]);
for(int i=1; i<=n; i++)rk[i]=i;
sort(rk+1,rk+1+n,cmpa);
for(int i=1; i<=A+B+C; i++)id[i]=rk[i];
for(int i=1; i<=n; i++)rk[i]=i;
sort(rk+1,rk+1+n,cmpb);
for(int i=1; i<=A+B+C; i++)id[i+A+B+C]=rk[i];
for(int i=1; i<=n; i++)rk[i]=i;
sort(rk+1,rk+1+n,cmpc);
for(int i=1; i<=A+B+C; i++)id[i+2*(A+B+C)]=rk[i];
int m=3*(A+B+C);
sort(id+1,id+1+m);
m=unique(id+1,id+1+m)-id-1;
sol.init(m+5);
int s = 0;
int p1 = m+1;
int p2 = m+2;
int p3 = m+3;
int t = m+4;
sol.AddEdge(s,p1,A,0);
sol.AddEdge(s,p2,B,0);
sol.AddEdge(s,p3,C,0);
for(int i=1; i<=m; i++)
{
sol.AddEdge(p1,i,1,-a[id[i]]);
sol.AddEdge(p2,i,1,-b[id[i]]);
sol.AddEdge(p3,i,1,-c[id[i]]);
}
for(int i=1;i<=m;i++)
sol.AddEdge(i,t,1,0);
printf("%I64d ",-sol.Mincost(s,t).first);
int k = 6*m+7;
long long ans2 = 0;
for(int i=1; i<=m; i++)
{
if(sol.edges[k].flow!=0)
{
ans2 += (a[id[i]]+b[id[i]]+c[id[i]]);
}
k+=2;
}
printf("%I64d\n",ans2);
}
return 0;
}