codeforces 787d[补]

感觉这套题强行撸下来真是超神了,头发都不知道掉了多少。。
一道线段树+最短路的混合使用,强行给CF教育了一发


题目:http://codeforces.com/problemset/problem/787/D
题目大意:
简单点来说就是构造一个图,构造的方法有
1、u v
2、u [l,r]
3、[l,r] u
这三种情况,每种情况都会可以构成权值为c的边
然后问随意一个点到所有点的最短路是多少

由于点数为1e5个,总的构图次数为1e5次
有可能构成的边为1e10,首先点数为1e5,构成之后所用dijkstra时间复杂度是O( n2 )远远不够
于是想到了djikstra+heap优化的O( nlogn )的算法,但是复杂度还是不够,乘上点数总时间复杂度为O( n2logn )
所以。。还需要继续优化,怎么优化呢
看到区间,应该能yy到线段树这种鬼畜的数据结构吧。。没错就是线段树
我们通过线段树来减少边的条数。。至于怎么减少呢。。

我们可以构建两种线段树,一种是从上指到下的,一种是从下指到上的,他们互相访问的权值为0,
当遇到u [l,r]这种情况的时候,让他去指向线段树中相应的区间,即可减少边数,我们把线段树的每一个区间都看成一个点,这样就能构成一个最短路。。。
然后同样的[l,r] u 这种情况,用线段树的区间去指向点,同样把线段树的区间看成点。
线段树所组成的线段,基本上可以看成是O(2)级别的,平均算作是O(2)吧。。
那基本上,你每次无论是点连区间还是区间连点,所构成的线大概也就2条上下

所以,建图的时候,点数应该*5倍,但是由于线少了,被log了一下,所以时间复杂度应该改为O( nlog2n )
这个时间复杂度,对于这题就够了。。简直鬼畜。。


敢问世间何时对我们这种弱渣连DIV2都要虐成狗,强行教育了一波。
好像对于pair在G++ 5.0版本不能这么使用,代码只能在G++11.0下编译。
dalao们都说。敲个线段树再敲个dijkstra就过了。。。确实。但是脑洞很大Orz

/*
@resouces: codeforces 787D
@date: 2017-3-27
@author: QuanQqqqq
@algorithm: 线段树 + dijkstra nlogn 
*/
#include <bits/stdc++.h>

#define MAXN 500005
#define ll long long

using namespace std;

const ll LLINF = 0x3f3f3f3f3fLL;
typedef pair<ll,int> pli;

struct Edge{
    int to,weight;
    Edge(int _to = 0,int _weight = 0) : to(_to),weight(_weight){}
};

priority_queue<pli > Q;
vector<vector<Edge> > G(MAXN);
bool vis[MAXN];
int id[MAXN][2];
vector<int> vs;
ll d[MAXN];
int idx;

void addEdge(int from,int to,int weight){
    G[from].push_back(Edge(to,weight));
}

void build(int l,int r,int rt,int wh){  //wh0为从上到下的线段树,wh1为从下到上的线段树 
    id[rt][wh] = ++idx;
    if(l == r){
        if(wh == 0){
            addEdge(id[rt][wh],l,0);
        } else {
            addEdge(l,id[rt][wh],0);
        }
        return;
    }
    int mid = l + r >> 1;
    build(l,mid,rt << 1,wh);
    build(mid + 1,r,rt << 1 | 1,wh);
    if(wh == 0){
        addEdge(id[rt][wh],id[rt << 1][wh],0);
        addEdge(id[rt][wh],id[rt << 1 | 1][wh],0);
    } else {
        addEdge(id[rt << 1][wh],id[rt][wh],0);
        addEdge(id[rt << 1 | 1][wh],id[rt][wh],0);
    }
}

void get(int l,int r,int L,int R,int rt,int wh){
    if(L <= l && r <= R){
        vs.push_back(id[rt][wh]);
        return;
    }
    int m = l + r >> 1;
    if(m >= L){
        get(l,m,L,R,rt << 1,wh);
    }
    if(m < R){
        get(m + 1,r,L,R,rt << 1 | 1,wh);
    }
}

void dijkstra(int s,int n){
    for(int i = 1;i <= n;i++){
        vis[i] = false;
        d[i] = LLINF;
    }
    Q.push({-0,s});
    d[s] = 0;
    while(!Q.empty()){
        int u = Q.top().second;
        Q.pop();
        if(vis[u]){
            continue;
        }
        vis[u] = true;
        for(int i = 0;i < G[u].size();i++){
            Edge e = G[u][i];
            int v = e.to,w = e.weight;
            if(w + d[u] < d[v]){
                d[v] = w + d[u];
                Q.push({-d[v],v});
            }
        }
    }
}

void init(int n){
    while(!Q.empty()){
        Q.pop();
    }
    for(int i = 1;i <= n;i++){
        G[i].clear();
    }
}

int main(){
    int n,q,s,l,r,t,u,v,c;
    scanf("%d %d %d",&n,&q,&s);
    init(n * 5);
    idx = n;
    build(1,n,1,0);
    build(1,n,1,1);
    while(q--){
        scanf("%d %d",&t,&u);
        if(t == 1){
            scanf("%d %d",&v,&c);
            addEdge(u,v,c);
        } else if(t == 2) {
            vs.clear();
            scanf("%d %d %d",&l,&r,&c);
            get(1,n,l,r,1,0);
            for(int i = 0;i < vs.size();i++){
                addEdge(u,vs[i],c);
            }
        } else {
            vs.clear();
            scanf("%d %d %d",&l,&r,&c);
            get(1,n,l,r,1,1);
            for(int i = 0;i < vs.size();i++){
                addEdge(vs[i],u,c);
            }
        }
    }
    dijkstra(s,5 * n);
    for(int i = 1;i <= n;i++){
        if(d[i] == LLINF){
            d[i] = -1;
        }
        printf("%lld ",d[i]);
    }
}

另:这套DIV还有dalao直播讲题解。。Orz弱校渣终于得到了dalao的直播真传,厉害了。。
http://www.bilibili.com/video/av9365298/

CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值