https://www.luogu.org/problemnew/show/P1073

C国有 n n个大城市和 mm 条道路,每条道路连接这 nn个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这 mm 条道路中有一部分为单向通行的道路,一部分为双向通行的道路,双向通行的道路在统计条数时也计为  1条。

C C国幅员辽阔,各地的资源分布情况各不相同,这就导致了同一种商品在不同城市的价格不一定相同。但是,同一种商品在同一个城市的买入价和卖出价始终是相同的。

商人阿龙来到 CC 国旅游。当他得知同一种商品在不同城市的价格可能会不同这一信息之后,便决定在旅游的同时,利用商品在不同城市中的差价赚回一点旅费。设 CC 国 n 个城市的标号从 ~ n1 n,阿龙决定从  1号城市出发,并最终在 nn 号城市结束自己的旅行。在旅游的过程中,任何城市可以重复经过多次,但不要求经过所有 nn 个城市。阿龙通过这样的贸易方式赚取旅费:他会选择一个经过的城市买入他最喜欢的商品――水晶球,并在之后经过的另一个城市卖出这个水晶球,用赚取的差价当做旅费。由于阿龙主要是来 CC 国旅游,他决定这个贸易只进行最多一次,当然,在赚不到差价的情况下他就无需进行贸易。

假设 C C国有 55个大城市,城市的编号和道路连接情况如下图,单向箭头表示这条道路为单向通行,双向箭头表示这条道路为双向通行。

假设 ~n1 n 号城市的水晶球价格分别为 ,,,,,,,,。

阿龙可以选择如下一条线路:->->->,并在  2号城市以  的价格买入水晶球,在 33号城市以  5的价格卖出水晶球,赚取的旅费数为 。

阿龙也可以选择如下一条线路 ->->->->,并在第1 1次到达  号城市时以  1的价格买入水晶球,在第  次到达  号城市时以  的价格卖出水晶球,赚取的旅费数为 。

现在给出 n n个城市的水晶球价格,mm 条道路的信息(每条道路所连接的两个城市的编号以及该条道路的通行情况)。请你告诉阿龙,他最多能赚取多少旅费。

题意

第一眼看觉得是先缩点然后DAG图上跑一边拓扑排序,除了敲起来手有点酸之外没有什么难度。就直接过了。

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
#define For(i, x, y) for(int i=x;i<=y;i++)
#define _For(i, x, y) for(int i=x;i>=y;i--)
#define Mem(f, x) memset(f,x,sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Sca2(x,y) scanf("%d%d",&x,&y)
#define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define Scl(x) scanf("%lld",&x);
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x);
#define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
#define LL long long
#define ULL unsigned long long
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
int read(){int x = ,f = ;char c = getchar();while (c<'' || c>''){if (c == '-') f = -;c = getchar();}
while (c >= ''&&c <= ''){x = x * + c - '';c = getchar();}return x*f;}
const double eps = 1e-;
const int maxn = 1e5 + ;
const int maxm = 5e5 + ;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + ;
int N,M,K;
int val[maxn];
struct Edge{
int to,next;
}edge[maxm * ];
int head[maxn],tot;
void init(){
for(int i = ; i <= N + ; i ++) head[i] = -;
tot = ;
}
void add(int u,int v){
edge[tot].to = v;
edge[tot].next = head[u];
head[u] = tot++;
}
int Low[maxn],dfn[maxn],Stack[maxn],Belong[maxn],num[maxn];
int Index,top,scc;
bool Instack[maxn];
void Tarjan(int u){
int v;
Low[u] = dfn[u] = ++Index;
Stack[top++] = u;
Instack[u] = true;
for(int i = head[u]; ~i; i = edge[i].next){
v = edge[i].to;
if(!dfn[v]){
Tarjan(v);
if(Low[u] > Low[v]) Low[u] = Low[v];
}else if(Instack[v] && Low[u] > dfn[v]){
Low[u] = dfn[v];
}
}
if(Low[u] == dfn[u]){
scc++;
do{
v = Stack[--top];
Instack[v] = false;
Belong[v] = scc;
num[scc]++;
}while(v != u);
}
}
int MAX[maxn],MIN[maxn],ind[maxn],ans[maxn];
vector<int>P[maxn];
int main(){
Sca2(N,M); init();
for(int i = ; i <= N ; i ++) Sca(val[i]);
for(int i = ; i <= M ; i ++){
int u,v,w; Sca3(u,v,w);
add(u,v);
if(w == ) add(v,u);
}
for(int i = ; i <= N ; i ++) if(!dfn[i]) Tarjan(i);
for(int i = ; i <= scc; i ++){
MAX[i] = -INF; MIN[i] = INF;
}
for(int i = ; i <= N ; i ++){
int u = Belong[i];
MAX[u] = max(MAX[u],val[i]);
MIN[u] = min(MIN[u],val[i]);
for(int j = head[i]; ~j; j = edge[j].next){
int v = Belong[edge[j].to];
if(u == v) continue;
P[u].push_back(v);
ind[v]++;
}
}
queue<int>Q;
for(int i = ; i <= scc; i ++) if(!ind[i]) Q.push(i);
while(!Q.empty()){
int u = Q.front(); Q.pop();
ans[u] = max(ans[u],MAX[u] - MIN[u]);
for(int i = ; i < P[u].size(); i ++){
int v = P[u][i];
ind[v]--;
MIN[v] = min(MIN[v],MIN[u]);
ans[v] = max(ans[v],ans[u]);
if(!ind[v]) Q.push(v);
}
}
Pri(ans[Belong[N]]);
return ;
}

缩点 + 拓扑排序

后来发现还有一种更加新奇的解法,构造分层图,将原图变为一个带权图,权的意思是商人走这条路要得到的钱(负数代表花费),一开始的图所有边权都是0,表示这个商人什么也不买,走来走去不亏也不赚。然后我们对于每个点v,构造一条边通向v + N,边权为-val[v],表示商人在这个点买了一个水晶球,1 + N ~ N + N就是出现的第二个图,对于第二图,也像第一条路一样构造出相同的边权为0的边,表示商人买了水晶球之后依然可以不买也不卖的在这个图上走来走去,同理,我们再v + N ~ v + N + N构造一条边权为val[v]的边,表示商人买完之后在这个点又卖出去了,同第二个图一样,构造出第三个图,最后题意要求到达终点,所以可取的点只有N和3 * N,即第一个图和第三个图的终点,跑一边最短长路即可。

由于边权有正有负的,不能用Dijkstra跑,无向图也不可以拓扑排序dp,所以只能SPFA(大概是这个做法的唯一缺点)

#include <map>
#include <set>
#include <ctime>
#include <cmath>
#include <queue>
#include <stack>
#include <vector>
#include <string>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sstream>
#include <iostream>
#include <algorithm>
#include <functional>
using namespace std;
#define For(i, x, y) for(int i=x;i<=y;i++)
#define _For(i, x, y) for(int i=x;i>=y;i--)
#define Mem(f, x) memset(f,x,sizeof(f))
#define Sca(x) scanf("%d", &x)
#define Sca2(x,y) scanf("%d%d",&x,&y)
#define Sca3(x,y,z) scanf("%d%d%d",&x,&y,&z)
#define Scl(x) scanf("%lld",&x);
#define Pri(x) printf("%d\n", x)
#define Prl(x) printf("%lld\n",x);
#define CLR(u) for(int i=0;i<=N;i++)u[i].clear();
#define LL long long
#define ULL unsigned long long
#define mp make_pair
#define PII pair<int,int>
#define PIL pair<int,long long>
#define PLL pair<long long,long long>
#define pb push_back
#define fi first
#define se second
typedef vector<int> VI;
int read(){int x = ,f = ;char c = getchar();while (c<'' || c>''){if (c == '-') f = -;c = getchar();}
while (c >= ''&&c <= ''){x = x * + c - '';c = getchar();}return x*f;}
const double eps = 1e-;
const int maxn = 3e5 + ;
const int maxm = 2e6 + ;
const int INF = 0x3f3f3f3f;
const int mod = 1e9 + ;
int N,M,K;
int head[maxn],tot;
struct Edge{
int to,next,dis;
}edge[maxm];
void init(){
for(int i = ; i <= * N + ; i ++) head[i] = -;
tot = ;
}
void add(int u,int v,int w){
edge[tot].to = v;
edge[tot].next = head[u];
edge[tot].dis = w;
head[u] = tot++;
}
int val[maxn];
int dis[maxn],vis[maxn];
int SPFA(int s,int t){
for(int i = ; i <= * N + ; i ++) dis[i] = -INF;
dis[] = ;
queue<int>Q;
Q.push(s);
while(!Q.empty()){
int u = Q.front(); Q.pop();
vis[u] = ;
for(int i = head[u]; ~i ; i = edge[i].next){
int v = edge[i].to;
if(dis[v] < dis[u] + edge[i].dis){
dis[v] = dis[u] + edge[i].dis;
if(!vis[v]){
vis[v] = ;
Q.push(v);
}
}
}
}
if(dis[t] == -INF) dis[t] = ;
return dis[t];
}
int main(){
Sca2(N,M); init();
for(int i = ; i <= N ; i ++){
Sca(val[i]);
add(i,i + N,-val[i]);
add(i + N,i + N + N,val[i]);
}
for(int i = ; i <= M ; i ++){
int u,v,w; Sca3(u,v,w);
add(u,v,);
add(u + N,v + N,);
add(u + N + N,v + N + N,);
if(w == ){
add(v,u,);
add(v + N,u + N,);
add(v + N,u + N + N,);
}
}
int t = * N + ,s = ;
add(N,t,); add( * N,t,);
Pri(SPFA(s,t));
return ;
}

洛谷P1073 Tarjan + 拓扑排序 // 构造分层图的更多相关文章

  1. 洛谷4400 BlueMary的旅行(分层图+最大流)

    qwq 首先,我们观察到题目中提到的每天只能乘坐一次航班的限制,很容易想到建分层图,也就是通过枚举天数,然后每天加入一层新的点. (然而我一开始想的却是erf) 考虑从小到大枚举天数,然后每次新建一层 ...

  2. 【题解】洛谷P1073 [NOIP2009TG] 最优贸易(SPFA+分层图)

    次元传送门:洛谷P1073 思路 一开始看题目嗅出了强连通分量的气息 但是嫌长没打 听机房做过的dalao说可以用分层图 从来没用过 就参考题解了解一下 因为每个城市可以走好几次 所以说我们可以在图上 ...

  3. 【bzoj1093】[ZJOI2007]最大半连通子图 Tarjan+拓扑排序+dp

    题目描述 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:对于u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v的有向路径或者从v到u的有向路径. ...

  4. 【bzoj5017】[Snoi2017]炸弹 线段树优化建图+Tarjan+拓扑排序

    题目描述 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足:  Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆.  现在 ...

  5. 洛谷P1155 双栈排序题解(图论模型转换+二分图染色+栈)

    洛谷P1155 双栈排序题解(图论模型转换+二分图染色+栈) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/1311990 原题地址:洛谷P1155 双栈排序 ...

  6. 【洛谷 P1073】 最优贸易 (Tarjan缩点+拓扑排序)

    题目链接 先\(Tarjan\)缩点,记录每个环内的最大值和最小值. 然后跑拓扑排序,\(Min[u]\)表示到\(u\)的最小值,\(ans[u]\)表示到\(u\)的答案,\(Min\)和\(an ...

  7. NOIP2017提高组Day1T3 逛公园 洛谷P3953 Tarjan 强连通缩点 SPFA 动态规划 最短路 拓扑序

    原文链接https://www.cnblogs.com/zhouzhendong/p/9258043.html 题目传送门 - 洛谷P3953 题目传送门 - Vijos P2030 题意 给定一个有 ...

  8. bzoj 1093 最大半连通子图 - Tarjan - 拓扑排序 - 动态规划

    一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:?u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v的有向路径或者从v到u的有向路径.若G'=(V ...

  9. 洛谷 P1073 最优贸易 解题报告

    P1073 最优贸易 题目描述 \(C\)国有\(n\)个大城市和\(m\)条道路,每条道路连接这\(n\)个城市中的某两个城市.任意两个城市之间最多只有一条道路直接相连.这\(m\)条道路中有一部分 ...

随机推荐

  1. Asp.Net Core 输出 Word

    In one of the ASP.NET Core projects we did in the last year, we created an OutputFormatter to provid ...

  2. Nginx IP地址透传

    L:119

  3. 【NLP】大白话讲解word2vec到底在做些什么

    转载自:http://blog.csdn.net/mylove0414/article/details/61616617 词向量 word2vec也叫word embeddings,中文名“词向量”, ...

  4. mysql 下载地址

    新浪的镜像站 http://mirrors.sohu.com/mysql yum安装: 首先要到MySQL yum库的下载页面http://dev.mysql.com/downloads/repo/y ...

  5. [UOJ86]mx的组合数——NTT+数位DP+原根与指标+卢卡斯定理

    题目链接: [UOJ86]mx的组合数 题目大意:给出四个数$p,n,l,r$,对于$\forall 0\le a\le p-1$,求$l\le x\le r,C_{x}^{n}\%p=a$的$x$的 ...

  6. BZOJ1208[HNOI2004]宠物收养场——treap

    凡凡开了一间宠物收养场.收养场提供两种服务:收养被主人遗弃的宠物和让新的主人领养这些宠物. 每个领养者都希望领养到自己满意的宠物,凡凡根据领养者的要求通过他自己发明的一个特殊的公式,得出该领养者希望领 ...

  7. POJ1442-查询第K大-Treap模板题

    模板题,以后要学splay,大概看一下treap就好了. #include <cstdio> #include <algorithm> #include <cstring ...

  8. SPHINX 文档写作工具安装简要指南 - windows 版 - 基于python

    此教程基于本地己安装好 PYTHON 并配置过全局变量:一定具备相应的基础再操作: 上传图片以免产生误导,以下为文字描述,按下列操作即可: 下载 get-pip.py脚本; python get-pi ...

  9. MT【294】函数定义的理解

    已知函数$f(x)$的定义域为$D,\pi\in D$.若$f(x)$的图像绕坐标原点逆时针旋转$\dfrac{\pi}{3}$后与原图像重合,则$f(\pi)$不可能是(    )A$\dfrac{ ...

  10. 树莓派播放网络磁盘MP3文件

    树莓派映射网络磁盘 sudo mount -t cifs -o username=jerry.huang,password=Hello2014 //10.10.0.1/Public/ESLPod.co ...