【题解】Code+ Apr_18 最短路

Problem

良心洛谷

想要数据的也可以去Code+上下载

题意:
给定 n n 个点,求从s t t 的最短路径,其中有两种走法(可以混搭):一种是走给定的m有向边 (ui,vi,wi) ( u i , v i , w i ) ;另一种可以由任意点 x x 到任意点y,其费用是 c(x c ∗ ( x xor x o r y) y )

数据范围: n105,m5×105 n ≤ 10 5 , m ≤ 5 × 10 5

Solution

明显不能直接 n2 n 2 建边,为了多骗点分可能会想到可以dijkstra在过程中在线加边,处理到终点为止

但这样的最坏复杂度依旧是跑不过的(code+上这题的测试点有99个,骗分基本骗不满)

本着在任意一个正解中绝对不可能处理所有的 n2+m n 2 + m 条边,发现有一些边可以被其他边替代,比如两个数异或值二进制表示中有两个及以上的1,那么可以用值分别为这两个1的边合并而成

比如: 3 3 xor 6 6 =5 112 11 2 xor x o r 1102=1012 110 2 = 101 2
5 二进制下有之间有两个 1
那么这个值可以由 1002 100 2 12 1 2 这两个值合并而得
即边 (3,6)=(3,7)+(7,6) ( 3 , 6 ) = ( 3 , 7 ) + ( 7 , 6 )
其权值对应为
1012 101 2 1002 100 2 12 1 2

则对于任意点 x x ,只用考虑给定边和到{v|v=x xor x o r 2k,kN,v<=n} 2 k , k ∈ N , v <= n } 的边即可,其他边的效用定可以被这些边的组合包含,则对于点 x x ,第二类边只有log2x

则问题简化到只有 nlogn+m n log ⁡ n + m 条边了, SPFA S P F A 目测会挂(有99个测试点),用 dijkstra d i j k s t r a (博主用的是 dijkstra d i j k s t r a 的线段树优化)

注意 0 0 号结点也需要考虑(有可能两个节点编号按位与为0),并把异或值控制在 n n 以内(出了n范围的点一定可以用 0 0 <script type="math/tex" id="MathJax-Element-170">0</script>号节点解决)

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rg register
#define cl(x) memset(x,0,sizeof(x))
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)<(y)?(x):(y))
#define abs(x) ((x)>0?(x):(-(x)))

template <typename _Tp> inline _Tp read(_Tp&x){
    rg char c11=getchar(),ob=0;x=0;
    while(c11^'-'&&!isdigit(c11))c11=getchar();if(c11=='-')c11=getchar(),ob=1;
    while(isdigit(c11))x=x*10+c11-'0',c11=getchar();if(ob)x=-x;return x;
}

const int N=201000,M=1001000,inf=2147483647;
struct Edge{int v,w,nxt;}a[M];
int head[N],dis[N],seg[N<<2];
char bo[N];
int n,m,c,_,ds,s,t;

inline void add(int u,int v,int w){a[++_].v=v,a[_].w=w,a[_].nxt=head[u],head[u]=_;}

#define mid (((l)+(r))>>1)

inline void build(int l,int r,int x){
    if(l==r){seg[x]=inf;return ;}
    build(l,mid,x<<1);
    build(mid+1,r,x<<1|1);
    seg[x]=inf;
    if(~seg[x<<1])seg[x]=min(seg[x],seg[x<<1]);
    if(~seg[x<<1|1])seg[x]=min(seg[x],seg[x<<1|1]);
    return ;
}

inline void update(int l,int r,int x,int pos,int t){
    if(l==r){seg[x]=min(seg[x],t);return ;}
    if(pos<=mid)update(l,mid,x<<1,pos,t);
    else update(mid+1,r,x<<1|1,pos,t);
    seg[x]=inf;
    if(~seg[x<<1])seg[x]=min(seg[x],seg[x<<1]);
    if(~seg[x<<1|1])seg[x]=min(seg[x],seg[x<<1|1]);
    return ;
}

inline int query(int l,int r,int x){
    if(l==r){ds=seg[x];return l;}
    if(seg[x]==seg[x<<1])return query(l,mid,x<<1);
    else if(seg[x]==seg[x<<1|1])return query(mid+1,r,x<<1|1);
    else return -1;
}

#undef mid

void spath(){
    build(0,n,1);
    update(0,n,1,s,0);
    while(!bo[t]){
        rg int x=query(0,n,1);
        bo[x]=1;dis[x]=ds;
        update(0,n,1,x,-1);
        for(rg int i=head[x];i;i=a[i].nxt)
            if(!bo[a[i].v])update(0,n,1,a[i].v,dis[x]+a[i].w);
        for(rg int i=1;i<=n;i<<=1)
            if(!bo[x^i]&&(x^i)<=n)update(0,n,1,x^i,dis[x]+c*i);
    }
    return ;
}

int main(){
    read(n);read(m);read(c);
    for(rg int i=0,x,y,z;i<m;++i)read(x),read(y),read(z),add(x,y,z);
    read(s);read(t);
    spath();
    printf("%d\n",dis[t]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值