https://www.cnblogs.com/31415926535x/p/11611801.html
偶然看到的这个东西,可以说是第一次见到图论+数据结构的题了,,这题代码很简单,细节处理一下就没啥了,,,主要是一步一步的思路的推导很不错,,
cf-786 Legacy
以前做过的图论题就只是图论题,从来没想过和数据结构-线段树扯上关系,,
这题也算是一个经典的例题了吧,,应该就是那种知道的做过的就会做出来的类型,,
思路分析
题意很简单,就是一个简单的图,,给出一些建图的方式,,但是,和以往不同的是,以前的边的关系给的都是点与点间的关系,,这种题给的方式是区间,,比如说 u->[l, r]
表示的就是u和这个区间的所有点间都有一条边,,因为一个点也可以看成一个只有自己的区间,,所以我们可以将这类关系统一看成 \([l_1, r_1]->[l_2, r_2]\) ,,
容易想到的方法就是直接两个 for 上去,,建出每一条边,,数据很小的时候没问题,,,但是当n 很大时,,显然建图的复杂度可能就是 \(O(n^2m)\) 这样不管求最短路就炸了,,,
一种优化的方法是我们在这两个区间之间加一个点,,这样前面的区间(成为出区间)和后面的一个区间(称为入区间)都和这个点 \(p\) 连,,也就是 \(\forall u \in [l_1, r_1]: addedge(u, p, w)\) 而 \(\forall v \in [l_2, r_2]: addedge(p, v, 0)\) ( \([l_1, r_1]-_u>p-_0>[l_2, r_2]\) ) 这样子就可以降一维的建图,,复杂度就是 \(O(2nm)\) ,,但是这样还是很高,,
这时的建图是线性的建图方式,,线性+区间==线段树??!!,,这是我做这道题学习到的最有价值的一个处理方式,,在降了一维之后,虽然是线性的建图,,但是点还是很多,,而线段树恰好可以用很少的子区间来表示原来的区间,,,如果将线段树中的每一个表示的区间看成一个点,,那么我们就可以用很少的点来建图,,,这样就可以将上面的n次的建图降下去,,,
那么这时的问题就变成了该如何利用线段树来处理,,
我们需要两棵线段树,,一棵看成 入树 另一棵看成 出树 ,,
首先我们的目的是用少量的区间来表示原来的很大的区间,以达到用很少的点来表示原来的所有点,,优化的问题用线段树解决了,,但是,如何正确的表示原来的所有点呢,,,
线段树的每一个节点表示一个区间,,这个节点可以表示他下面的所有点,,也就是说,,我们可以从上向下的看,,定义选择了一个节点,,就选择了下面的所有点,,,按照这个思想,入树中的一个节点要向其儿子连一条指向儿子的有向边,,也就是说,,入树中所有的边指向下,,用 down
表示
同理,,对于出树,,我们要保证在一个节点要能表示所有的点,,于是就是一个节点下的所有节点都要指向它,,,这样看这棵树就是一个向上的树,,用 up
表示,,
这个样子的:
这样最后在这样初始图加上题目给的一些条件的边跑一边最短路就可以了,,
加上题目的边后的图大致是这样的:
实际上,,这里的线段树的作用只是一个建树和查询其子区间的作用,,这个思想有点像是分块,,,只要能找到一个合理的区间分块,,用一些合理的、数量少的区间表示原来的区间,,就能达到减少点数的作用,,,,而线段树恰好是一个熟悉的、好操作的区间划分模型,,所以很多人都对于 区间图的最短路问题都是套一个线段树的板子,,
回到这道题,,题目的加边方式只有 点对区间 和 区间对点 两种,,所以我们可以先预留出那n个点,,可以想象成放在这两棵树之间的一排点(不用再将两棵树的叶子节点相连,,),,,
然后再处理出出树、入树的边后,,对于 u->[l, r]
和 u->v
的边,,从点 u
向入树的符合条件的节点连边即可,,因为之前说的入树保证了每一个节点是可以到其下面的叶子节点的,,所以我们这样连边就相当于是点 u
向区间的每一个点连边,,,
同理对于 [l, r]->u
这样的边,,我们将入树的对应的节点和点 u
相连,,这样就保证入树中这个区间下的叶子节点可以通过这些区间到点 u
,,这样也满足了题意的同时减少的连边的复杂度,,,
最后跑最短路,,前n个点的 dis[i]
即为源图的那些点的最短路,,,
于是我们通过加点减边的方式减小了建图的时间复杂度,,
关于处理出树、入树的操作,,也就是线段树的建树过程,,其实线段树并不维护任何信息,,我们只是用它自己每个节点表示一个区间这个自身的性质,,所以为了建图,,,我们需要对每一个节点连一些边,,,也就是用一个 id[rt]
标记一下每一个节点的标号即可,,,
最后的代码:
#include <bits/stdc++.h>
#define aaa cout<<233<<endl;
#define endl '\n'
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
// mt19937 rnd(time(0));
const int inf = 0x3f3f3f3f;//1061109567 > 1e9
const ll linf = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-6;
const double pi = 3.14159265358979;
const int maxn = 1e6 + 5;
const int maxm = 1e7 + 233;
const int mod = 1e9 + 7;
struct Dijkstra
{
struct edge
{
int to, nxt; ll w;
}edge[maxm];
int tot, head[maxm];
void init()
{
tot = 0;
memset(head, -1, sizeof head);
}
void addedge(int u, int v, ll w)
{
edge[tot].to = v;
edge[tot].w = w;
edge[tot].nxt = head[u];
head[u] = tot++;
}
struct node
{
int v; ll w;
node(){}
node(int _v, ll _w):v(_v), w(_w){}
const bool operator<(const node &r)const
{
return w > r.w;
}
};
bool vis[maxn];
ll dis[maxn];
priority_queue<node> pq;
void dijkstra(int s, int n)
{
memset(vis, false, sizeof vis);
memset(dis, inf, sizeof dis);
while(!pq.empty())pq.pop();
pq.push(node(s, 0));
dis[s] = 0;
node t;
int u;
while(!pq.empty())
{
t = pq.top(); pq.pop();
u = t.v;
if(vis[u])continue;
vis[u] = true;
for(int i = head[u]; ~i; i =