区间->点,点->区间,线段树优化建图+dijstra Codeforces Round #406 (Div. 2) D

http://codeforces.com/contest/787/problem/D

题目大意:有n个点,三种有向边,这三种有向边一共加在一起有m个,然后起点是s,问,从s到所有点的最短路是多少?

第一种边:u->v w 表示节点u到v有连接一条有向边,权值为w

第二种边:u->[l,r] w  表示节点u到区间[l,r]连接一条有向边,权值为w

第三种边:[l,r]->u w  表示区间[l, r]所有的节点到u都有一条有向边,权值为w

思路:

我们知道,对于dijstra都是用priority_queue来优化的,这样才保证了每个节点值访问一次的复杂度。

算法一:

对于第二种边和第三种边分别对[l,r]->u的这些点进行建图,再跑dij。但是最坏情况的建图是O(n*n),所以肯定会TLE

从算法一中看出,这道题的瓶颈在于如何处理区间和点之间的边的关系。

我们仔细的分析一下dijstra以后发现,每个节点的访问次数只有1,也就是说,优先队列里面取出来的点,取出来以后就不可能有比他更小的点了,而且这个点只会访问一次。

对于第一种边,u->v,那么很明显就是d[v] = d[u] +w,这个符合线段树中的单点更新

对于第二种边u->[l,r],那么也就是说,从u出发,到区间[l,r]所有的节点的val都修改为d[u] + w,这个符合线段树中的成段更新

对于第三种边[l,r]->u来说

从[l,r]出发->u的路径可以有无数条,那么如何做才能保证d[u]是从区间[l,r]里面得来的最小的呢?

根据priority_queue,那么我们最先访问过的点,就是必然是权值最小的,那么也就是说,假设当前我们访问到节点x,那么x属于区间[l,r]中,那么x一定是区间[l,r]中权值最小的,所以我们只需要利用这个d[x]去更新即可,即d[u] = d[x] + w。当然,如果这个[l,r]区间使用过了,就不需要再使用了,pop掉即可,因为后续再也用不到这个区间了!

所以这个也是线段树中的单点更新

que就是priority_queue,tree保存目前节点能往右延伸到的最大值。

然后对一个值从队列里面访问过了,那么就不需要再次访问了

//看看会不会爆int!数组会不会少了一维!
//取物问题一定要小心先手胜利的条件
#include <bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
#define LL long long
#define ALL(a) a.begin(), a.end()
#define pb push_back
#define mk make_pair
#define fi first
#define se second
#define haha printf("haha\n")
const int maxn = 1e5 + 5;
const LL inf = 1e16;
int n, q, s;
vector<pair<int, LL> > one[maxn];
vector<pair<pair<int, int>, LL> > er[maxn], san[maxn];
///tree保存目前节点能往右边延伸到的最右端,que保存目前节点的最小值
int tree[maxn << 2];
pair<LL, int> que[maxn << 2];
LL lazy[maxn << 2];
LL d[maxn];

inline void update_queue(int o, LL val){
    if (que[o].fi == inf + 1) return;
    que[o].fi = min(que[o].fi, val);
    if (lazy[o] == -1) lazy[o] = val;
    lazy[o] = min(lazy[o], val);
}

void push_down(int o){
    int lb = o << 1, rb = o << 1 | 1;
    if (lazy[o] != -1){
        update_queue(lb, lazy[o]);
        update_queue(rb, lazy[o]);
        lazy[o] = -1;
    }
}
/*
因为对于第二种情况:v->[l,r]来说,[l,r]的值是相同的,所以我们就假定按照优先级来计算即可
对于第三种情况:[l, r]->v来说,[l,r]中只能是最小的那个点到v,因为只有这样才能保证最短
*/
void update(int ql, int qr, int l, int r, int o, LL val){
    //printf("ql = %d qr = %d l = %d r = %d\n", ql, qr, l, r);
    if (ql <= l && qr >= r){
        if (val == inf + 1) que[o] = mk(val, l);
        else update_queue(o, val);
        return ;
    }
    push_down(o);
    int mid = (l + r) / 2;
    if (ql <= mid) update(ql, qr, l, mid, o << 1, val);
    if (qr > mid) update(ql, qr, mid + 1, r, o << 1 | 1, val);
    que[o] = min(que[o << 1], que[o << 1 | 1]);
   // printf("que[%d] = %lld %d val = %lld\n", o, que[o], val);
}

vector<pair<int, LL> > ve;
void find_point(int x, int l, int r, int o){
    if (l > x || tree[o] < x) return ;
    if (l == r){
        while (!san[l].empty() && san[l].back().fi.fi >= x){
            ve.push_back(mk(san[l].back().fi.se, san[l].back().se));
            san[l].pop_back();
        }
        tree[o] = -1;
        if (!san[l].empty()) tree[o] = san[l].back().fi.fi;
        return ;
    }
    int mid = (l + r) / 2;
    find_point(x, l, mid, o << 1);
    find_point(x, mid + 1, r, o << 1 | 1);
    tree[o] = max(tree[o << 1], tree[o << 1 | 1]);
}

void solve(){
    for (int i = 1; i <= n; i++) d[i] = inf;
    update(s, s, 1, n, 1, 0);///更新初始点
    while (true){
        int u = que[1].se;
        LL cost = que[1].fi;
        if (cost == inf + 1) break;
        d[u] = min(cost, d[u]);
        update(u, u, 1, n, 1, inf + 1);///如果从队列里面访问过了,就不需要再访问了
        ///第一种,单点更新u->v
        for (int i = 0; i < one[u].size(); i++){
            int v = one[u][i].fi; LL w = one[u][i].se;
            update(v, v, 1, n, 1, d[u] + w);
        }
        ///第二种,成段更新,u->[l,r]
        for (int i = 0; i < er[u].size(); i++){
            int ql = er[u][i].fi.fi, qr = er[u][i].fi.se; LL w = er[u][i].se;
            update(ql, qr, 1, n, 1, d[u] + w);
        }
        ///第三种,单点更新,[l, r]->v,其中[l,r]包含了u节点
        ve.clear();
        find_point(u, 1, n, 1);
        for (int i = 0; i < ve.size(); i++){
            int v = ve[i].fi; LL w = ve[i].se;
            update(v, v, 1, n, 1, d[u] + w);
        }
    }
    for (int i = 1; i <= n; i++){
        if (d[i] == inf) printf("-1 ");
        else printf("%lld ", d[i]);
    }
    cout << endl;
}

void build(int l, int r, int o){
    if (l == r){
        que[o] = mk(inf, l);
        tree[o] = -1;
        if (!san[l].empty()) tree[o] = san[l].back().fi.fi;
        return ;
    }
    int mid = (l + r) / 2;
    if (l <= mid) build(l, mid, o << 1);
    if (r > mid) build(mid + 1, r, o << 1 | 1);
    tree[o] = max(tree[o << 1], tree[o << 1 | 1]);
    que[o] = min(que[o << 1], que[o << 1 | 1]);
}

int main(){
    cin >> n >> q >> s;
    for (int i = 1; i <= q; i++){
        int ty; scanf("%d", &ty);
        if (ty == 1){
            int u, v; LL val; scanf("%d%d%lld", &u, &v, &val);
            one[u].pb(mk(v, val));
        }
        else {
            int u, l, r; LL val; scanf("%d%d%d%lld", &u, &l, &r, &val);
            if (ty == 2) er[u].pb(mk(mk(l, r), val));
            else san[l].pb(mk(mk(r, u), val));
        }
    }
    for (int i = 1; i <= n; i++) sort(ALL(san[i]));
    memset(lazy, -1, sizeof(lazy));
    build(1, n, 1);
    solve();
    return 0;
}

时间: 2024-08-08 02:39:26

区间->点,点->区间,线段树优化建图+dijstra Codeforces Round #406 (Div. 2) D的相关文章

【bzoj4276】[ONTAK2015]Bajtman i Okr?g?y Robin 线段树优化建图+费用流

题目描述 有n个强盗,其中第i个强盗会在[a[i],a[i]+1],[a[i]+1,a[i]+2],...,[b[i]-1,b[i]]这么多段长度为1时间中选出一个时间进行抢劫,并计划抢走c[i]元.作为保安,你在每一段长度为1的时间内最多只能制止一个强盗,那么你最多可以挽回多少损失呢? 输入 第一行包含一个正整数n(1<=n<=5000),表示强盗的个数. 接下来n行,每行包含三个正整数a[i],b[i],c[i](1<=a[i]<b[i]<=5000,1<=c[i]

【bzoj3073】[Pa2011]Journeys 线段树优化建图+堆优化Dijkstra

题目描述 Seter建造了一个很大的星球,他准备建造N个国家和无数双向道路.N个国家很快建造好了,用1..N编号,但是他发现道路实在太多了,他要一条条建简直是不可能的!于是他以如下方式建造道路:(a,b),(c,d)表示,对于任意两个国家x,y,如果a<=x<=b,c<=y<=d,那么在xy之间建造一条道路.Seter保证不会有一个国家与自己之间有道路. Seter好不容易建好了所有道路,他现在在位于P号的首都.Seter想知道P号国家到任意一个国家最少需要经过几条道路.当然,Se

【ARC069F】Flags 2-sat+线段树优化建图+二分

Description ? 数轴上有 n 个旗子,第 ii 个可以插在坐标 xi或者 yi,最大化两两旗子之间的最小距离. Input ? 第一行一个整数 N. ? 接下来 N 行每行两个整数 xi,yi Output ? 一个整数表示答案. Sample Input Sample #1 3 1 3 2 5 1 9 Sample #2 5 2 2 2 2 2 2 2 2 2 2 Sample #3 22 93 6440 78 6647 862 11 8306 9689 798 99 801 52

Codeforces 787D. Legacy 线段树优化建图+最短路

output standard output Rick and his co-workers have made a new radioactive formula and a lot of bad guys are after them. So Rick wants to give his legacy to Morty before bad guys catch them. There are n planets in their universe numbered from 1 to n.

一个神秘的oj2587 你猜是不是dp(线段树优化建图)

哇 这难道不是happiness的翻版题嘛? 从\(S\)向一个点连染成白色的收益 从这个点向\(T\)连染成黑色的收益 对于额外的收益,建一个辅助点,跟区间内的每个点连\(inf\),然后向S/T,连流量为收益 这不就结束了吗? 自信写完,提交 woc!!只有40分? #include<iostream> #include<cstdio> #include<algorithm> #include<cstring> #include<cmath>

机房测试5:reverse(bfs+set 或 线段树优化建图)

题目: 分析: 首先画样例分析一下,会发现如果要求一个位置要多少次翻转,就将这个位置向与它关联的点连边(关联点指的是可能与它值互换的位置),一直连到起点为止,连边的次数即为它所需步数. 所以转换成求单源最短路,因为边权为1,可以用bfs. 但是这道题n的范围很大,刚刚的做法是n*k的,考虑优化. 法1:在建图上优化 题目要求的是区间翻转,所以也对应着相关性质:每个点连边一定是都连的奇数点或偶数点(画图可知),且这些奇数偶数点都对应着一段连续的区间. 如果可以将点向点连边优化成点向区间连边,复杂度

bzoj5017 [Snoi2017]炸弹 (线段树优化建图+)tarjan 缩点+拓扑排序

题目传送门 https://lydsy.com/JudgeOnline/problem.php?id=5017 题解 这个题目方法挺多的. 线段树优化建图 线段树优化建图的做法应该挺显然的,一个炸弹能够引爆的炸弹的显然应该是一个区间里面的,直接对这个区间进行线段树优化建图. 这样可以得到一个带环图,缩点以后这个炸弹能够炸到的炸弹就是从这个点能够走到的点. 但是这个不太好做,不过可以发现最终的炸弹也是一个区间,所以可以通过缩点后的 DAG 来求出左右端点. 时间复杂度 \(O(n\log n)\)

【BZOJ3681】Arietta 树链剖分+可持久化线段树优化建图+网络流

[BZOJ3681]Arietta Description Arietta 的命运与她的妹妹不同,在她的妹妹已经走进学院的时候,她仍然留在山村中.但是她从未停止过和恋人 Velding 的书信往来.一天,她准备去探访他.对着窗外的阳光,临行前她再次弹起了琴.她的琴的发声十分特殊.让我们给一个形式化的定义吧.所有的 n 个音符形成一棵由音符 C ( 1 号节点) 构成的有根树,每一个音符有一个音高 Hi .Arietta 有 m 个力度,第 i 个力度能弹出 Di 节点的子树中,音高在 [Li,R

[bzoj5017][Snoi2017]炸弹 tarjan缩点+线段树优化建图+拓扑

5017: [Snoi2017]炸弹 Time Limit: 30 Sec  Memory Limit: 512 MBSubmit: 608  Solved: 190[Submit][Status][Discuss] Description 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足: Xi?Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆. 现在,请你帮忙计算一下,先把第 i 个炸弹引爆,将引爆多少个炸弹呢? Inp