算法竞赛入门经典 写题笔记(第五章 图论算法与模型2)
本节内容——
- 2-SAT
- dijstra算法的一些应用
- SPFA算法的一些应用
例题9 飞机调度
有n架飞机需要着陆。每架飞机都可以选择“早着陆"和”晚着陆“两种方式之一,且必须选择一种。第i架飞机的早着陆时间为\(E_i\),晚着陆时间为\(L_I\),不得在其他时间着陆。现在需要安排这些飞机的着陆方式,使得整个着陆计划尽量安全。换句话说,如果把所有飞机的实际着陆时间按照从早到晚的顺序排列,相邻两个着陆时间间隔的最小值(成为安全间隔)尽量大。
也就是二分这个时间,然后判断该2-SAT是否有解。(因为这个间隔时间越小就也可能有合法解,反之越不可能,所以可以二分答案)
构图的时候,如果两个决策(比如说\(i_l,j_l\))间隔小于二分的答案,我们就给\(i_l,j_r\)和\(i_r,j_l\)连有向边。
然后跑tarjan判断就行了,如果同一个飞机的两个决策在一个强联通分量里面,就没有合法解了。
顺便一提,刘汝佳书上写的那个做法复杂度是假的qwq
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define MAXN 4010
#define INF 0x3f3f3f3f
using namespace std;
int n,m,t,tot,cnt,top;
int head[MAXN],dfn[MAXN],low[MAXN],in[MAXN],st[MAXN],c[MAXN];
struct Edge{int nxt,to;}edge[MAXN*MAXN];
struct Node{int l,r;}node[MAXN];
inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
inline void tarjan(int x)
{
dfn[x]=low[x]=++tot;
in[x]=1;
st[++top]=x;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);
else if(in[v]) low[x]=min(low[x],dfn[v]);
}
if(dfn[x]==low[x])
{
int v;++cnt;
do{v=st[top--],c[v]=cnt,in[v]=0;}while(x!=v);
}
}
inline bool check(int x)
{
memset(head,0,sizeof(head));
t=tot=top=cnt=0;
for(int i=1;i<=(n>>1);i++)
for(int j=1;j<=(n>>1);j++)
{
if(i==j) continue;
int a=abs(node[i].l-node[j].l);
int b=abs(node[i].l-node[j].r);
int c=abs(node[i].r-node[j].l);
int d=abs(node[i].r-node[j].r);
if(a<x) add(i*2-1,j*2);
if(b<x) add(i*2-1,j*2-1);
if(c<x) add(i*2,j*2);
if(d<x) add(i*2,j*2-1);
}
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(in,0,sizeof(in));
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
for(int i=1;i<n;i+=2)
if(c[i]==c[i+1])
return false;
return true;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
while(scanf("%d",&n)!=EOF)
{
for(int i=1;i<=n;i++) scanf("%d%d",&node[i].l,&node[i].r);
n<<=1;
int l=0,r=INF,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
例题10 宇航员分组
有A,B,C三种任务要分配给n个宇航员,其中每个宇航员恰好要分配一个任务。设所有n个宇航员的平均年龄为x,只有年龄大于或等于x的宇航员才能分配任务A;只有年龄严格小于x的宇航员才能分配任务B,而任务C没有限制。有m对宇航员相互讨厌,因此不能分配到统一任务。现在需要找出一个满足上诉所有要求的任务分配方案。
3-SAT???不可能的。我们只要处理一下年龄,对于每个宇航员,照样是2-SAT.
然后就......和上面那题一样做就行了啊??
但是为什么会RE啊......搞不懂......先把代码贴上,回来找锅(咕咕咕)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define MAXN 100010
using namespace std;
int n,m,all,kkk,cnt,tot,top,t;
int head[MAXN],done[MAXN];
int dfn[MAXN],low[MAXN],in[MAXN],st[MAXN],c[MAXN];
char op[MAXN];
struct Node{int l,r,age;}node[MAXN];
struct Edge{int nxt,to;}edge[MAXN<<1];
struct Line{int u,v;}line[MAXN<<1];
inline void add(int from,int to){edge[++t].nxt=head[from],edge[t].to=to,head[from]=t;}
inline void tarjan(int x)
{
low[x]=dfn[x]=++tot;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(!dfn[v]) tarjan(v),low[x]=min(low[x],low[v]);
else if(in[v]) low[x]=min(low[x],dfn[v]);
}
if(low[x]==dfn[x])
{
int v;++cnt;
do{v=st[top--];in[v]=0;c[v]=++cnt;}while(x!=v);
}
}
inline bool check()
{
memset(head,0,sizeof(head));
memset(in,0,sizeof(in));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
top=tot=t=cnt=0;
for(int i=1;i<=m;i++)
{
int u=line[i].u,v=line[i].v;
if(node[u].age^node[v].age)
{
add(node[u].r,node[v].l),printf("%d %d\n",node[u].r,node[v].l);
add(node[v].r,node[u].l),printf("%d %d\n",node[v].r,node[u].l);
}
else
{
add(node[u].l,node[v].r),printf("%d %d\n",node[u].l,node[v].r);
add(node[u].r,node[v].l),printf("%d %d\n",node[u].r,node[v].l);
add(node[v].l,node[u].r),printf("%d %d\n",node[v].l,node[u].r);
add(node[v].r,node[u].l),printf("%d %d\n",node[v].r,node[u].l);
}
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i),cout<<i<<endl;
for(int i=1;i<=n;i++)
if(c[node[i].l]==c[node[i].r]&&c[node[i].l]!=0)
return false;
return true;
}
inline bool solve(int x,int c)
{
done[x]=c,done[x^1]=3-c;
printf("done[%d]=%d done[%d]=%d\n",x,done[x],x^1,done[x^1]);
cout<<endl;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(done[v]&&done[v]==c) return false;
else if(!done[v]) solve(v,c);
}
return true;
}
inline void print()
{
cout<<"yes"<<endl;
for(int i=1;i<=n;i++)
{
if(done[node[i].l]==1) printf("%c\n",op[node[i].l]);
else if(done[node[i].l]==0&&done[node[i].r]==0) printf("%c\n",op[node[i].l]);
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
while(scanf("%d%d",&n,&m)==2)
{
if(n==0&&m==0) break;
memset(head,0,sizeof(head));
top=tot=t=cnt=all=0;
for(int i=1;i<=n;i++) scanf("%d",&node[i].age),all+=node[i].age;
all/=n;
for(int i=1;i<=n;i++)
{
if(node[i].age<all) node[i].age=0;
else node[i].age=1;
}
for(int i=1;i<=n;i++) scanf("%d%d",&line[i].u,&line[i].v);
for(int i=1;i<=n;i++)
if(node[i].age==0)
node[i].l=++kkk,op[kkk]='B',node[i].r=++kkk,op[kkk]='C';
else
node[i].l=++kkk,op[kkk]='A',node[i].r=++kkk,op[kkk]='C';
for(int i=1;i<=n;i++)
printf("%c %c\n",op[node[i].l],op[node[i].r]);
if(check()==false) {printf("No solution.\n");continue;}
memset(done,0,sizeof(done));
bool flag=true;
for(int i=1;i<=n;i++)
if(!done[i])
if(solve(node[i].l,1))
flag=false;
if(flag==true) {print();continue;}
memset(done,0,sizeof(done));
flag=true;
for(int i=1;i<=n;i++)
if(!done[i])
solve(node[i].r,1);
print();
}
return 0;
}
例题11 机场快线
机场快线分为经济线和商业线两种,线路、速度和价钱都不同。现在你有一张商业线的车票,可以坐一站商业线,而其他时候只能乘坐经济线。假设换成时间忽略不计,你的任务是找一条取机场最快的线路,然后输出方案。(保证最优解唯一)
因为商业线只能坐一站,而且数据范围在1000以内,所以我们可以枚举坐的是哪一站。
假设我们用商业线车票从车站a坐到b,则从起点到a,从b到终点这两部分的路线对于只存在经济线的图中一定是最短路。所以我们只需要从起点、终点开始做两次最短路,记录下从起点到每个点x的最短时间\(f(x)\)和它到终点的最短时间\(g(x)\),那么总时间就是\(f(a)+time(a,b)+g(b)\)。
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<vector>
#include<stack>
#include<bitset>
#include<cstdlib>
#include<cmath>
#include<set>
#include<list>
#include<deque>
#include<map>
#include<queue>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int mod = 1000000000 + 7;
const int INF = 1000000000;
const int maxn = 500 + 10;
int T,n,m,S,d1[maxn], p1[maxn], d2[maxn], p2[maxn], vis[maxn], ok, dist;
struct node {
int u, val;
node(int u=0, int val=0):u(u), val(val) {}
bool operator < (const node& rhs) const {
return val > rhs.val;
}
};
void print(int root, int p[], int id, int S) {
vector<int> ans;
while(root != S) {
ans.push_back(root);
root = p[root];
}
ans.push_back(root);
int len = ans.size();
if(id == 1) for(int i=len-1;i>=0;i--) {
if(i != len-1) printf(" ");
printf("%d",ans[i]);
}
else for(int i=0;i<len;i++) {
if(i != 0) printf(" ");
printf("%d",ans[i]);
}
}
vector<node> g[maxn];
void BFS(int haha, int d[], int p[]) {
priority_queue<node> q;
q.push(node(haha, 0));
for(int i=1;i<=n;i++) {
d[i] = INF;
}
d[haha] = 0;
memset(vis, false, sizeof(vis));
while(!q.empty()) {
node u = q.top(); q.pop();
if(vis[u.u]) continue;
vis[u.u] = true;
int len = g[u.u].size();
for(int i=0;i<len;i++) {
node v = g[u.u][i];
if(d[v.u] > d[u.u] + v.val) {
d[v.u] = d[u.u] + v.val;
p[v.u] = u.u;
q.push(node(v.u, d[v.u]));
}
}
}
}
int a,b,c,kase=0;
int main() {
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
while(~scanf("%d%d%d",&n,&S,&T)) {
scanf("%d",&m);
for(int i=1;i<=n;i++) g[i].clear();
while(m--) {
scanf("%d%d%d",&a,&b,&c);
g[a].push_back(node(b, c));
g[b].push_back(node(a, c));
}
scanf("%d",&m);
ok = -1;
BFS(S, d1, p1);
BFS(T, d2, p2);
int s = -1,t = -1,ans = d1[T], res = 0;
for(int i=0;i<m;i++) {
scanf("%d%d%d",&a,&b,&c);
if(cur < ans) {
ans = cur;
s = b; t = a;
}
}
if(kase) printf("\n");
else ++kase;
if(s > 0) {
print(s, p1, 1, S); printf(" ");
print(t, p2, 2, T); printf("\n");
printf("%d\n",s);
}
else {
print(T, p1, 1, S); printf("\n");
printf("Ticket Not Used\n");
}
printf("%d\n",ans);
}
return 0;
}
书上还提到了dij算法的路径统计,在这里就简单说一下吧
枚举两点之间的所有最短路:先求出所有点到目标点的最短路长度\(d[i]\),然后从起点开始出发,只沿着\(d[i]=d[j]+dist(i,j)\)的边走。
两点之间的最短路计数:令\(f[i]\)表示从i到目标点的最短路的条数,则\(f[i]=\sum f[j] | d[i]=d[j]+dist(i,j)\)(这里书上写错了)
例题12 林中漫步
对于一张图,只沿着满足如下条件的道路(A,B)走:存在一条从B出发回家的路径,比所有从A出发回家的路径都短。问不同的回家路径条数。
先跑完以家为源点的最短路,然后如果一个点a的最短路比b的小,那么连边,这就是一个DAG了,然后再DP计个数就行了。
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 100010
using namespace std;
int n,m,t;
int head[MAXN<<1],done[MAXN],dis[MAXN],dp[MAXN];
struct Node
{
int u,d;
friend bool operator < (Node x,Node y)
{return x.d>y.d;}
};
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
struct Line{int u,v,w;}line[MAXN<<1];
inline void add(int from,int to,int dis)
{
edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
}
inline void dij(int x)
{
priority_queue<Node>q;
memset(done,0,sizeof(done));
memset(dis,0x3f,sizeof(dis));
q.push((Node){x,0});
dis[x]=0;
while(!q.empty())
{
int u=q.top().u;q.pop();
if(done[u]) continue;
done[u]=1;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].dis)
dis[v]=dis[u]+edge[i].dis,q.push((Node){v,dis[v]});
}
}
}
inline int solve(int x)
{
if(x==2) return dp[x]=1;
int cur_ans=0;
for(int i=head[x];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(!dp[v]) dp[v]=solve(v);
cur_ans+=dp[v];
}
return cur_ans;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
while(scanf("%d%d",&n,&m)==2&&n)
{
memset(head,0,sizeof(head));
memset(dp,0,sizeof(dp));
t=0;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&line[i].u,&line[i].v,&line[i].w);
add(line[i].u,line[i].v,line[i].w);
add(line[i].v,line[i].u,line[i].w);
}
dij(2);
// for(int i=1;i<=n;i++) printf("dis[%d]=%d\n",i,dis[i]);
memset(head,0,sizeof(head));
t=0;
for(int i=1;i<=m;i++)
{
if(dis[line[i].u]>dis[line[i].v]) add(line[i].u,line[i].v,line[i].w);
if(dis[line[i].v]>dis[line[i].u]) add(line[i].v,line[i].u,line[i].w);
}
printf("%d\n",solve(1));
}
return 0;
}
最短路树:用dij算法可以求出单元最短路树,方法是在发现\(d[i]+w(i,j)<d[j]\)时除了更新\(d[j]\)之外还要设置\(p[j]=i\)。这样,所有点就形成了一棵树。
要从起点出发沿着最短路走到其他任意点,只需要顺着树上的边走即可。
例题13 战争和物流
给出一个n个节点m条边的无向图(n<=100,m<=1000),每条边上有一个正权。令c等于每对节点的最短路长度之和。要求删除一条边后使得新的c值c'最大。不联通的两点的最短路长度视为L。
在源点确定的情况下,只要最短路树不被破坏,起点到所有点的距离都不会发生改变。换句话说,只有删除最短路树上的n-1条边(中的任意条),最短路树才需要重新计算。
这样的话,我们对于每个源点,最多只需要求n次而不是m次最短路,时间复杂度为\(O(n^2mlogn)\)(dij算法的时间复杂度为\(O(mlogn)\))
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 2010
using namespace std;
int n,m,l,t=1;
long long ans1,ans2;
int head[MAXN<<1],pre[MAXN],ex[MAXN<<1][MAXN],done[MAXN];
long long dis[MAXN],dist[MAXN];
struct Node
{
int u;long long d;
friend bool operator < (Node x,Node y)
{return x.d>y.d;}
};
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
inline void add(int from,int to,long long dis)
{
edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
edge[++t].nxt=head[to],edge[t].to=from,edge[t].dis=dis,head[to]=t;
}
inline long long dij(int x,int op)
{
priority_queue<Node>q;
memset(dis,127,sizeof(dis));
memset(done,0,sizeof(done));
memset(pre,-1,sizeof(pre));
q.push((Node){x,0});
dis[x]=0;
while(!q.empty())
{
int u=q.top().u;q.pop();
if(done[u]) continue;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if((i==op)||(i^1)==op) continue;
if(dis[v]>dis[u]+edge[i].dis)
{
dis[v]=dis[u]+edge[i].dis;
q.push((Node){v,dis[v]});
pre[v]=i;
}
}
}
for(int i=1;i<=n;i++)
if(pre[i]!=-1&&op==0)
ex[pre[i]][x]=ex[pre[i]^1][x]=1;
long long cur_ans=0;
for(int i=1;i<=n;i++)
{
if(dis[i]<1e17) cur_ans+=dis[i];
else cur_ans+=l;
}
return cur_ans;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
freopen("ce.out","w",stdout);
#endif
while(scanf("%d%d%d",&n,&m,&l)==3)
{
memset(head,0,sizeof(head));
memset(ex,0,sizeof(ex));
t=1;
ans1=0,ans2=0;
for(int i=1;i<=m;i++)
{
int x,y;long long w;
scanf("%d%d%lld",&x,&y,&w);
add(x,y,w);
}
for(int i=1;i<=n;i++)
dist[i]=dij(i,0),ans1+=dist[i];
for(int i=2;i<=t;i+=2)
{
long long cur_ans=0;
for(int j=1;j<=n;j++)
{
if(ex[i][j]) cur_ans+=dij(j,i);
else cur_ans+=dist[j];
}
ans2=max(ans2,cur_ans);
}
printf("%lld %lld\n",ans1,ans2);
}
return 0;
}
例题14 过路费(加强版)
运送货物需要缴纳过路费。进入一个寻装需要缴纳一个单位的货物,进入一个城镇时,每20个单位的货物中就要上缴1个单位(比如,携带70个单位的货物进入城镇,需要缴纳4个单位的货物)。现在给定你一张图,请找出一条缴纳过路费最少的路线,并输出。如果有多条路线,输出字典序最小的。
逆推的dij算法。令\(d[i]\)表示进入节点i之后,至少需要\(d[i]\)个单位的货物,到达目的地时货物数量才足够。则每次选择一个\(d[i]\)最小的未标号的节点,更新它的所有前驱节点的d值就行了。输出路径的时候根据d值就可以构造出字典序最小的解。
emmmm为什么我把书上的解释全抄下来了......因为我觉得写得确实比较清晰啊qwq
这个题我的代码还是不知道为什么锅了。。。先敷衍一下放个maomao的吧......
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 200 + 5;
const int INF = 0x3f3f3f3f3f3f3f3f;
vector <int> G[N];
int n, p, kase, dis[N], done[N]; char s, t;
int get_tot (int w) {
int l = 0, r = 1e17;
while (l < r) {
int mid = (l + r) >> 1;
if (mid - ceil ((1.0 * mid) / 20.0) >= w) {
r = mid;
} else {
l = mid + 1;
}
}
return r;
}
struct HeapNode {
int u, d;
bool operator < (HeapNode rhs) const {
return d > rhs.d;
}
};
priority_queue <HeapNode> q;
void solve () {
memset (done, 0, sizeof (done));
memset (dis, 0x3f, sizeof (dis));
dis[(int)t] = isupper (t) ? get_tot (p) : p + 1;
q.push ((HeapNode) {t, dis[(int)t]});
while (!q.empty ()) {
HeapNode now = q.top (); q.pop ();
if (done[now.u]) continue;
for (int i = 0; i < (int) G[now.u].size (); ++i) {
int v = G[now.u][i];
int _w = isupper (v) ? get_tot (dis[now.u]) : dis[now.u] + 1;
if (dis[v] > _w) {
dis[v] = _w;
q.push ((HeapNode) {v, dis[v]});
}
}
done[now.u] = true;
}
}
bool cmp (int lhs, int rhs) {
return dis[lhs] == dis[rhs] ? lhs < rhs : dis[lhs] < dis[rhs];
}
signed main () {
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
while (cin >> n) {
if (n == -1) break;
cout << "Case " << ++kase << ":" << endl;
for (int i = 0; i < N; ++i) G[i].clear ();
for (int i = 1; i <= n; ++i) {
static char u, v;
cin >> u >> v;
G[(int)u].push_back ((int)v);
G[(int)v].push_back ((int)u);
}
cin >> p >> s >> t;
solve ();
int min_dis = 0x7fffffffffffffffll;
for (int i = 0; i < (int)G[(int)s].size (); ++i) {
int v = G[(int)s][i];
min_dis = min (min_dis, dis[v]);
}
for (int i = 0; i < N; ++i) {
if (!G[i].empty ()) {
sort (G[i].begin (), G[i].end (), cmp);
}
}
if (s == t) {
cout << p << endl;
cout << (char) t << endl;
continue;
}
cout << min_dis << endl;
int now = s; cout << (char) s << "-";
while (now != t) {
now = G[now][0];
cout << (char) now;
if (now != t) cout << "-";
}
cout << endl;
}
}
例题15 在环中
给定一个n个点m条边(n<=50)的加权有向图,求平均权值最小的回路。输入没有自环。
二分答案。假设存在一个包含k条边的回路,回路上各条边的权值为\(w_1,w_2,...,w_k\),那么平均值小于mid意味着\((w_1-mid)+(w_2-mid)+...+(w_k-mid)<0\),那么就直接给每条边减去mid,然后用SPFA判断一下是否有负环就行了。
注意小数二分的时候精度不要取太高......常数太大会T的.......
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 110
#define eps 1e-7
using namespace std;
int n,m,t,T,kase;
int head[MAXN],done[MAXN],cnt[MAXN];
double dis[MAXN];
struct Line{int u,v;double w;}line[MAXN*MAXN];
struct Edge{int nxt,to;double dis;}edge[MAXN*MAXN];
inline void add(int from,int to,double dis)
{
edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
}
inline bool spfa(int x)
{
queue<int>q;
for(int i=0;i<=n;i++) dis[i]=1e9,done[i]=cnt[i]=0;
q.push(x);done[x]=1;dis[x]=0;
while(!q.empty())
{
int u=q.front();q.pop();done[u]=0;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].dis)
{
dis[v]=dis[u]+edge[i].dis;
if(!done[v])
{
done[v]=1;
q.push(v);
if(++cnt[v]>=n) return false;
}
}
}
}
return true;
}
inline bool check(double x)
{
bool flag=false;
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=edge[j].nxt)
edge[j].dis-=x;
for(int i=1;i<=n;i++)
if(!spfa(i)==true)
flag=true;
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=edge[j].nxt)
edge[j].dis+=x;
// puts("oh no");
return flag;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
scanf("%d",&T);
while(T--)
{
memset(head,0,sizeof(head));
t=0;
scanf("%d%d",&n,&m);
double l=1e9,r=0.0;
for(int i=1;i<=m;i++)
{
scanf("%d%d%lf",&line[i].u,&line[i].v,&line[i].w);
add(line[i].u,line[i].v,line[i].w);
r=max(r,line[i].w);
l=min(l,line[i].w);
}
printf("Case #%d: ",++kase);
if(!check(r+1)) printf("No cycle found.\n");
else
{
while(l+eps<r)
{
double mid=(l+r)/2.0;
// printf("l=%.2lf r=%.2lf mid=%.2lf\n",l,r,mid);
if(check(mid)) r=mid;
else l=mid;
}
printf("%.2lf\n",r);
}
}
return 0;
}
例题16 Halum操作
给定一个有向图,每条边都有一个权值。每次你可以选择一个节点v和一个整数d,把所有以v为终点的边的权值减小d,把所有以v为起点的边的权值增加d,最后要让所有边权的最小值为正且尽量大。
因为不同的操作互不影响,所以可以按照任意顺序实施这些操作。另外,对于同一个节点的多次操作也可以合并,所以我们可以令sum(u)表示作用于节点u之上的所有d之和。然后二分答案x,问题转化成为是否可以让所有操作完成后每条边的权值均不小于x。对于边a->b,操作完之后应该是\(w(a,b)+sum[a]-sum[b] \ge x\),也就是\(sum[b]-sum[a]<=w(a,b)-x\)。这样,就是一个差分约束系统了,我们连a到b,权值为\(w(a,b)-x\)的边。然后用负环来判断这个差分约束系统是否有解。
对了,书上的翻译感觉有问题。依AC代码来看,应该是让所有边的最小值为正且尽量大......
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define MAXN 510
using namespace std;
int n,m,t;
int head[MAXN],done[MAXN],dis[MAXN],cnt[MAXN];
struct Edge{int nxt,to,dis;}edge[3000];
struct Line{int u,v,w;}line[3000];
inline void add(int from,int to,int dis)
{
edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
}
inline bool spfa()
{
queue<int>q;
for(int i=1;i<=n;i++)
q.push(i),done[i]=1,dis[i]=0x3f3f3f3f,cnt[i]=0;
while(!q.empty())
{
int u=q.front();q.pop();done[u]=0;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].dis)
{
dis[v]=dis[u]+edge[i].dis;
if(!done[v])
{
done[v]=1;
cnt[v]++;
q.push(v);
if(cnt[v]>=n) return false;
}
}
}
}
return true;
}
inline bool check(int x)
{
bool flag=false;
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=edge[j].nxt)
edge[j].dis-=x;
if(spfa()) flag=true;
for(int i=1;i<=n;i++)
for(int j=head[i];j;j=edge[j].nxt)
edge[j].dis+=x;
return flag;
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(head,0,sizeof(head));
t=0;
int l=0,r=-0x3f3f3f3f,ans;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&line[i].u,&line[i].v,&line[i].w);
add(line[i].u,line[i].v,line[i].w);
r=max(r,line[i].w);
}
if(!check(1)) {printf("No Solution\n");continue;}
else if(check(r)) {printf("Infinite\n");continue;}
while(l<=r)
{
int mid=(l+r)>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}
例题17 蒸汽式压路机
翻译还是去看书吧.......(反正我大概也是每次都抄一遍)
拆点的最短路,因为一个位置表示的状态不一样,所以我们要拆点来分别代表这些状态
把每个点\((r,c)\)拆成8个点\((r,c,dir,double)\),分别表示上一步从上下左右的哪个方向(dir)移动到这个点,以及移动到这个点的这条边的权值是否已经加倍(doubled)
题解也还是去看书吧.......(反正图我也是画不出来的......)
#include<iostream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<queue>
#include<vector>
#define MAXN 100010
#define INF 0x3f3f3f3f
using namespace std;
inline int read()
{
int f=1,x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48); ch=getchar();}
return x;
}
int n,R,C,r1,c1,r2,c2,kase,t;
int inv[4]={2,3,0,1},dr[4]={-1,0,1,0},dc[4]={0,-1,0,1};
int grid[110][110][4],id[110][110][4][2];
int dis[MAXN],done[MAXN],head[MAXN];
const int Up=0;
const int Left=1;
const int Down=2;
const int Right=3;
struct Edge{int nxt,to,dis;}edge[20000010];
struct Node
{
int u,d;
friend bool operator < (struct Node x,struct Node y)
{return x.d>y.d;}
};
inline void add(int from,int to,int dis)
{
// printf("[%d %d] %d\n",from,to,dis);
edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
}
inline int ID(int r,int c,int dir,int doubled)
{
int &x=id[r][c][dir][doubled];
if(x==0) x=++n;
return x;
}
inline bool cango(int r,int c,int dir)
{
if(r<1||r>R||c<1||c>C) return false;
return grid[r][c][dir]>0;
}
inline void dij(int s)
{
priority_queue<Node>q;
memset(done,0,sizeof(done));
memset(dis,0x3f,sizeof(dis));
dis[s]=0;q.push((Node){s,0});
while(!q.empty())
{
int u=q.top().u;q.pop();
if(done[u]) continue;
done[u]=1;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(dis[v]>dis[u]+edge[i].dis)
{
dis[v]=dis[u]+edge[i].dis;
q.push((Node){v,dis[v]});
}
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
while(scanf("%d%d%d%d%d%d",&R,&C,&r1,&c1,&r2,&c2)==6)
{
if(R==0&&C==0) break;
memset(head,0,sizeof(head));
t=0;
for(int r=1;r<=R;r++)
{
for(int c=1;c<=C-1;c++)
grid[r][c][Right]=grid[r][c+1][Left]=read();
if(r!=R)
for(int c=1;c<=C;c++)
grid[r][c][Down]=grid[r+1][c][Up]=read();
}
n=0;
memset(id,0,sizeof(id));
for(int dir=0;dir<4;dir++)
if(cango(r1,c1,dir))
add(0,ID(r1+dr[dir],c1+dc[dir],dir,1),grid[r1][c1][dir]*2);
for(int r=1;r<=R;r++)
for(int c=1;c<=C;c++)
for(int dir=0;dir<4;dir++) if(cango(r,c,inv[dir]))
for(int newdir=0;newdir<4;newdir++) if(cango(r,c,newdir))
for(int doubled=0;doubled<2;doubled++)
{
int newr=r+dr[newdir];
int newc=c+dc[newdir];
int v=grid[r][c][newdir],newdoubled=0;
if(dir!=newdir)
{
if(!doubled) v+=grid[r][c][inv[dir]];
newdoubled=1;
v+=grid[r][c][newdir];
}
add(ID(r,c,dir,doubled),ID(newr,newc,newdir,newdoubled),v);
}
dij(0);
int ans=INF;
for(int dir=0;dir<4;dir++) if(cango(r2,c2,inv[dir]))
for(int doubled=0;doubled<2;doubled++)
{
int v=dis[ID(r2,c2,dir,doubled)];
if(!doubled) v+=grid[r2][c2][inv[dir]];
ans=min(ans,v);
}
printf("Case %d: ",++kase);
if(ans==INF) printf("Impossible\n");
else printf("%d\n",ans);
}
return 0;
}
例题18 低价空中旅行
给你一些票,每张联票上标明上面以此经过的站,以及本票的价钱。必须从头开始坐,可以在中途任何一站下飞机,下飞机后票上缴,不可以再次使用本票。
现在给出一些行程单,问如何买票能使得总花费最小(同一种票能够买多张)。输入保证行程总是可行的,行程单上的城市必须按照顺序到达,但是中间可以经过一些辅助城市。
输入保证每组数据最多包含20种联票和20个行程单,联票或者行程单上的相邻城市保证不同。票和行程单都从1开始编号。
#include<cstdio>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#include<stack>
#include<algorithm>
using namespace std;
const int maxn=4100;
const int inf=1e9;
struct HeapNode
{
int d,u;
bool operator<(const HeapNode& rhs)const
{return d>rhs.d;}
};
struct Edge{int from,to,dist,id;};
struct Dijkstra
{
int n,m;
vector<Edge>edges;
vector<int>G[maxn];
bool done[maxn];
int d[maxn],p[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,int id)
{
edges.push_back((Edge){from,to,dist,id});
m=edges.size();
G[from].push_back(m-1);
}
void dij(int s)
{
priority_queue<HeapNode>Q;
for(int i=1;i<=n;i++)d[i]=inf;
d[s]=0;
memset(done,0,sizeof(done));
Q.push((HeapNode){0,s});
while(!Q.empty())
{
HeapNode x=Q.top();Q.pop();
int u=x.u;
if(done[u])continue;
done[u]=true;
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];
Q.push((HeapNode){d[e.to],e.to});
}
}
}
}
void print(int s,int t)
{
stack<int>stk;
while(t!=s)
{
stk.push(edges[p[t]].id);
t=edges[p[t]].from;
}
while(!stk.empty())
{
printf(" %d",stk.top());
stk.pop();
}
printf("\n");
}
}solver;
int id[12][410],res,tot;
vector<int>ticket[21];
map<int,int>mp;
inline int ID(int x)
{
if(!mp.count(x)) mp[x]=++tot;
return mp[x];
}
inline int ID2(int x,int y)
{
if(id[x][y]==0) id[x][y]=++res;
return id[x][y];
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
int n,m,q,u,v,money,kase=0;
while(~scanf("%d",&n)&&n)
{
int cost[21];
mp.clear();
tot=0;
for(int i=1;i<=n;i++)
{
ticket[i].clear();
scanf("%d%d",&cost[i],&m);
for(int j=1;j<=m;j++)
{
scanf("%d",&u);
ticket[i].push_back(ID(u));
}
}
kase++;
scanf("%d",&q);
vector<int>line;
for(int p=1;p<=q;p++)
{
int num;
line.clear();
memset(id,0,sizeof(id));
res=0;
scanf("%d",&num);
for(int i=1;i<=num;i++)
{
scanf("%d",&u);
line.push_back(ID(u));
}
solver.init(num*tot+1);
for(int tic=1;tic<=n;tic++)
for(int pos=1;pos<num;pos++)
{
int next=pos;
for(int i=1;i<ticket[tic].size();i++)
{
if(ticket[tic][i]==line[next])next++;
solver.AddEdge(ID2(pos,ticket[tic][0]),ID2(next,ticket[tic][i]),cost[tic],tic);
if(next==num) break;
}
}
solver.dij(ID2(1,line[0]));
int ans=solver.d[ID2(num,line[num-1])];
printf("Case %d, Trip %d: Cost = %d\n",kase,p,ans);
printf(" Tickets used:");
solver.print(ID2(1,line[0]),ID2(num,line[num-1]));
}
}
}
例题19 动物园大逃亡
平面图转对偶图,用最短路求最小割。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define S 0
#define T tot+1
#define MAXN 5000010
using namespace std;
int n,m,t,cur,kase,tot;
int dis[MAXN],done[MAXN],head[MAXN],id[1010][1010][2];
struct Node
{
int u,d;
friend bool operator <(struct Node x,struct Node y)
{return x.d>y.d;}
};
struct Edge{int nxt,to,dis;}edge[MAXN<<1];
inline void add(int from,int to,int dis)
{
// printf("[%d %d] %d\n",from,to,dis);
edge[++t].nxt=head[from],edge[t].to=to,edge[t].dis=dis,head[from]=t;
edge[++t].nxt=head[to],edge[t].to=from,edge[t].dis=dis,head[to]=t;
}
inline void dij()
{
priority_queue<Node>q;
memset(dis,0x3f,sizeof(dis));
memset(done,0,sizeof(done));
q.push((Node){S,0});dis[S]=0;
while(!q.empty())
{
int u=q.top().u; q.pop();
if(done[u]) continue;
done[u]=1;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(dis[u]+edge[i].dis<dis[v])
dis[v]=dis[u]+edge[i].dis,q.push((Node){v,dis[v]});
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("ce.in","r",stdin);
#endif
while(scanf("%d%d",&n,&m)==2&&n+m)
{
memset(head,0,sizeof(head));
t=tot=0;
n--,m--;
// printf("n=%d m=%d\n",n,m);
for(int i=0;i<=n+1;i++)
for(int j=0;j<=m+1;j++)
for(int k=0;k<=1;k++)
id[i][j][k]=++tot;
for(int i=1;i<=n+1;i++)
{
int x;
for(int j=1;j<=m;j++)
{
scanf("%d",&x);
add(id[i][j][1],id[i-1][j][0],x);
}
}
for(int i=1;i<=n;i++)
{
int x;
for(int j=1;j<=m+1;j++)
{
scanf("%d",&x);
add(id[i][j][0],id[i][j-1][1],x);
}
}
for(int i=1;i<=n;i++)
{
int x;
for(int j=1;j<=m;j++)
{
scanf("%d",&x);
add(id[i][j][1],id[i][j][0],x);
}
}
for(int i=1;i<=n;i++) add(S,id[i][0][1],0);
for(int j=1;j<=m;j++) add(S,id[n+1][j][1],0);
for(int j=1;j<=m;j++) add(id[0][j][0],T,0);
for(int i=1;i<=n;i++) add(id[i][m+1][0],T,0);
dij();
printf("Case %d: Minimum = %d\n",++kase,dis[T]);
// printf("%d\n",dis[T]);
}
return 0;
}