4.17题目总结(图论题目)
Ezio_0420
2018-04-17 16:43:23
## [POJ 1511 Invitation Cards](http://poj.org/problem?id=1511)
给定一张带正权的有向图,有n个人要从点1出发走到1..n的所有点,然
后再走回点1,求他们走过的所有路径长度和的最小值。
n, m ≤ 1000000,时限8s
解法:
经典最短路变形,分别建出正向边和反向边以1为源点跑最短路。
如果用Dijkstra的话,复杂度是O((n + m) log n)
假如常数太大,这样做有可能会超时。换用SPFA就能以很快的速度通过了。复杂度是O(玄学)。
```cpp
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
const int N=1e6+3;
const long long inf=0x7ffffffff;
int s=1,cnt1,cnt2,head1[N],head2[N],n,m,t;
ll d1[N],d2[N],ans;
struct tu{
int to,ne;
ll w;
}e1[N],e2[N];
inline void add1(int u,int v,ll w){
cnt1++;
e1[cnt1].to=v;
e1[cnt1].w=w;
e1[cnt1].ne=head1[u];
head1[u]=cnt1;
}
inline void add2(int u,int v,ll w){
cnt2++;
e2[cnt2].to=v;
e2[cnt2].w=w;
e2[cnt2].ne=head2[u];
head2[u]=cnt2;
}
queue<int>q;
bool inq[N];
void spfa1(){
fill(d1+1,d1+n+1,inf);
d1[s]=0;
q.push(s);
inq[s]=true;
while(!q.empty()){
int i = q.front();
q.pop();
inq[i]=false;
for(int j = head1[i];j;j=e1[j].ne){
int v=e1[j].to;
if(d1[v]>d1[i]+e1[j].w){
d1[v]=d1[i]+e1[j].w;
if(!inq[v]){
q.push(v);
inq[v]=true;
}
}
}
}
}
void spfa2(){
fill(d2+1,d2+n+1,inf);
d2[s]=0;
q.push(s);
inq[s]=true;
while(!q.empty()){
int i = q.front();
q.pop();
inq[i]=false;
for(int j = head2[i];j;j=e2[j].ne){
int v=e2[j].to;
if(d2[v]>d2[i]+e2[j].w){
d2[v]=d2[i]+e2[j].w;
if(!inq[v]){
q.push(v);
inq[v]=true;
}
}
}
}
}
int main(){
scanf("%d",&t);
while(t--){
scanf("%d%d",&n,&m);
for(int i = 1;i<=m;i++){
int u,v;
ll w;
scanf("%d%d%lld",&u,&v,&w);
add1(u,v,w);
add2(v,u,w);
}
spfa1();
spfa2();
ans=0;
// for(int i = 1;i<=n;i++){
// printf("%lld ",d1[i]);
// }
// puts(" ");
// for(int i = 1;i<=n;i++){
// printf("%lld ",d2[i]);
// }
// puts(" ");
for(int i = 1;i<=n;i++){
ans+=d1[i]+d2[i];
}
printf("%lld\n",ans);
memset(e1,0,sizeof(e1));
memset(e2,0,sizeof(e2));
memset(head1,0,sizeof(head1));
memset(head2,0,sizeof(head2));
memset(d1,0,sizeof(d1));
memset(d2,0,sizeof(d2));
cnt1=cnt2=0;
}
return 0;
}
```
------------
## [POJ1062 昂贵的聘礼](http://poj.org/problem?id=1062)
有n件物品,你想要获得其中的物品1。每个物品都有个直接购买的价
格,你也有一定的金币,但不幸的是你的钱可能不够。还好,可以用
别的物品来换购;具体来说,每件物品i都有若干种(可能为0)换购方
式,表示如果你有某件其他物品j,那么用j加上额外的一些钱,就可以
买到i了。
此外,每件物品都有一个等级。你购买的任意两件物品的等级差都不
能超过一个给定的值m。求在这些条件下,要想买到物品1,最少需要
多少金币。
n ≤ 100,价格及等级均为不超过1e9的正整数
解法:
枚举所购买的物品中的最低等级,相应算出最高等级,然后只用这些
物品建图跑最短路。复杂度O(n^3)。
------------
##[小K的农场](https://www.luogu.org/problemnew/show/P1993)
小K在MC里面建立很多很多的农场,总共n个,以至于他自己都忘记了每个农场中种植作物的具体数量了,他只记得一些含糊的信息(共m个),以下列三种形式描述:
农场a比农场b至少多种植了c个单位的作物,
农场a比农场b至多多种植了c个单位的作物,
农场a与农场b种植的作物数一样多。
但是,由于小K的记忆有些偏差,所以他想要知道存不存在一种情况,使得农场的种植作物数量与他记忆中的所有信息吻合。
输入输出格式
输入格式:
第一行包括两个整数 n 和 m,分别表示农场数目和小 K 记忆中的信息数目。
接下来 m 行:
如果每行的第一个数是 1,接下来有 3 个整数 a,b,c,表示农场 a 比农场 b 至少多种植了 c 个单位的作物。
如果每行的第一个数是 2,接下来有 3 个整数 a,b,c,表示农场 a 比农场 b 至多多种植了 c 个单位的作物。
如果每行的第一个数是 3,家下来有 2 个整数 a,b,表示农场 a 种植的数量和 b 一样多。
输出格式:
如果存在某种情况与小 K 的记忆吻合,输出“Yes”,否则输出“No”。
解法:
差分约束,没有负环即可
对于形如a-b<=c这样的式子,我们可以建立一条从b到a边权为c的有向边,然后先拓扑排序找出所有环,再对于每个环DFS一遍判断是不是负环即可。
~~这题我做的时候迷之多跑一遍bfs的spfa,后来观察数据发现用spfa的输出的都是Yes,于是又思考了一下,发现果然根本不用跑spfa~~
~~然后我又发现。。。根本不用拓扑排序~~
直接DFS!
```cpp
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<climits>
using namespace std;
const int N = 1e5+3;
const int inf=INT_MAX;
int n,m,head[N],cnt;
int d[N];
struct tu{
int to,ne,w;
}e[4*N+1];
void add(int u,int v,int w){
cnt++;
e[cnt].w=w;
e[cnt].to=v;
e[cnt].ne=head[u];
head[u]=cnt;
}
//queue<int>q;
//bool inq[2*N+1];
//bool spfa(int s){
// fill(d+1,d+n+1,inf);
// d[s]=0;
// q.push(s);
// inq[s]=true;
// while(!q.empty()){
// int i = q.front();
// q.pop();
// inq[i]=false;
// for(int j = head[i];j;j=e[j].ne){
// int v = e[j].to;
// if(d[v]>d[i]+e[j].w){
// d[v]=d[i]+e[j].w;
// if(!inq[v]){
// q.push(v);
// inq[v]=true;
// }
// }
// }
// }
// return true;
//}
int in[N+1];
bool vis[N+1];
queue<int>p;
void topsort(){
for(int i = 1;i<=n+1;i++){
if(!in[i]) p.push(i);
}
while(!p.empty()){
int i = p.front();
vis[i]=1;
p.pop();
for(int j = head[i];j;j=e[j].ne){
int v = e[j].to;
in[v]--;
if(!in[v]) p.push(v);
}
}
}
bool ever[N+1];
bool dfs(int i){
ever[i]=true;
for(int j=head[i];j;j=e[j].ne){
int v=e[j].to,dis=d[i]+e[j].w;
if(vis[v]) continue;
if(dis<d[v]){
if(ever[v]) return true;
d[v]=dis;
if(dfs(v)) return true;
}
}
ever[i]=false;
return false;
}
int main(){
scanf("%d%d",&n,&m);
int x,a,b,c;
for(int i = 1;i<=m;i++){
scanf("%d",&x);
if(x==1){
scanf("%d%d%d",&a,&b,&c);
//a-b>=c
add(a,b,-c);
in[b]++;
}
if(x==2){
scanf("%d%d%d",&a,&b,&c);
//a-b<=c;
add(b,a,c);
in[a]++;
}
if(x==3){
scanf("%d%d",&a,&b);
add(a,b,0);
in[b]++;
add(b,a,0);
in[a]++;
}
}
for(int i = 1;i<=n;i++){
add(n+1,i,0);
in[i]++;
}
topsort();
for(int i = 1;i<=n+1;i++){
if(!vis[i]){
if(dfs(i)){
puts("No");
return 0;
}
}
}
//bool ans=spfa(n+1);
//if(ans)
puts("Yes");
//else printf("No\n");
return 0;
}
```
```cpp
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<climits>
using namespace std;
const int N = 1e5+3;
const int inf=INT_MAX;
int n,m,head[N],cnt;
int d[N];
struct tu{
int to,ne,w;
}e[2*N+1];
void add(int u,int v,int w){
cnt++;
e[cnt].w=w;
e[cnt].to=v;
e[cnt].ne=head[u];
head[u]=cnt;
}
bool ever[N+1];
bool dfs(int i){
ever[i]=true;
for(int j=head[i];j;j=e[j].ne){
int v=e[j].to;
if(d[i]+e[j].w<d[v]){
if(ever[v]) return true;
d[v]=d[i]+e[j].w;
if(dfs(v)) return true;
}
}
ever[i]=false;
return false;
}
int main(){
scanf("%d%d",&n,&m);
int x,a,b,c;
for(int i = 1;i<=m;i++){
scanf("%d",&x);
if(x==1){
scanf("%d%d%d",&a,&b,&c);
//a-b>=c
add(a,b,-c);
}
if(x==2){
scanf("%d%d%d",&a,&b,&c);
//a-b<=c;
add(b,a,c);
}
if(x==3){
scanf("%d%d",&a,&b);
add(a,b,0);
add(b,a,0);
}
}
for(int i = 1;i<=n;i++){
add(n+1,i,0);
}
fill(d+1,d+n+2,inf);
d[n+1]=0;
if(dfs(n+1)){
puts("No");
return 0;
}
puts("Yes");
return 0;
}
```