[ABC232G] Modulo Shortest Path (最佳化建圖)

Captainfly19發表於2024-04-19

連結:https://www.luogu.com.cn/problem/AT_abc232_g

暴力的做法肯定不行,這道題要用到一個比較經典的拆點操作:把一個點拆成內點和外點。在接下來的分析中會慢慢介紹。

由於題目每次連的邊都是單向邊,那要考慮的問題是:比如說現在要從1走到3,怎麼走才能與暴力建邊等價。

先不考慮取模這個條件。假如說我一開始就是暴力建邊,那麼比如從1走到3的路徑長度就是(a[1]+b[3])。那麼我們可以把一個點拆成一個內點和外點,其中外點向內點連結一條長度為0的邊,內點向外點連結一條 b[1]+a[i] 的邊,然後外點向下一個外點連結一條 b[i+1]-b[i] 的邊。

為什麼要這麼連呢?我們考慮題目中要求的是求1到n的最短路。那麼從1號點出發時,先是從內點走向外點,隨後一直走外點直到目的地,對應的邊權該抵消的抵消該相加的相加。到最後達到目的地的時候,累加起來的結果就是我們想要的邊權。比如說從1走到3,那麼路徑上的所有邊權 就是 b[1]+a[1]+b[2]-b[1]+b[3]-b[2]+0=a[1]+b[3]

現在再來考慮取模這個條件。我們觀察資料範圍可以發現 a[i]+b[i] 不可能超過 2*m ,那麼也就是說可以直接把取模這個條件轉化成 (a[i]+b[i]-m). 我們可以將b陣列先進行排序,從小到大與進行連邊。我們二分查詢出第一個需要取模操作的點 k,連一條從i的內點指向k的外點的一條長度為 (b[k]-a[i]-m) 的邊即可。
因為這個k相當於一個分界點,在這個分界點只需要出現一次 -m,在後面的累加過程中這個 -m 是不會被消掉的,其他的累加抵消操作和上面一致。

連好了邊,跑一次Dijkstra即可。理解不了,可以畫一張圖,然後看一看是怎麼抵消的就行。然後要理解Dij跑最短路的原理就會知道到這個點到答案怎麼更新,然後就可以分析每條邊有一種各司其職的感覺,從內點到外點的邊有些在更新答案的時候是沒有用的,不會影響到最短路統計。

#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef long long ll;
typedef pair<int,int> PII;
#define mp make_pair
#define ull unsigned long long
#define all(x) x.begin(), x.end()
//__builtin_count()
typedef array<int,2> pr;
const unsigned int mask =  (chrono::steady_clock::now().time_since_epoch().count());
inline int gcd(int a, int b){return b == 0 ? a : gcd(b, a % b);}//gcd(a,b)=gcd(a,b-a)(b>a);
inline int lcm(int a, int b){return a / gcd(a, b) * b;}//lcm(n,m)=n*m/gcd(n,m)
const ll mod = 998244353;
const int inv2 = (mod+1)/2;
#define lson rt<<1
#define rson rt<<1|1
int addmod(int &x, int y) {x = (x + y >= mod ? x + y - mod : x + y);return 1;}
ll qpow(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
ll inv(int x){return qpow(x,mod-2);}
inline void read(__int128 &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
int o[200010],on;
inline void output(__int128 x)
{
    on=0;
    while(x) o[++on]=x%10,x/=10;
    for(int i=on;i>=1;i--) cout<<o[i];
}

const int maxn = 5e5+10;
struct node{
    int a,b,id;
    bool operator < (const node &k) const{
        return b<k.b;
    }
}a[maxn];
struct dian{
    int v,w;
    bool operator < (const dian &k) const{
        return w>k.w;
    }
}g[maxn];
int n,m;
int b[maxn],dis[maxn],vis[maxn];
vector<pr> vec[maxn];
void dij(int st)
{
    priority_queue<dian> que;
    memset(dis,0x3f,sizeof(dis));
    que.push({st,dis[st]});
    dis[st]=0;
    while(!que.empty())
    {
        auto x=que.top();
        que.pop();
        if(vis[x.v]) continue;
        vis[x.v]=1;
        for(auto to:vec[x.v])
        {
            if(dis[to[0]]>dis[x.v]+to[1])
            {
                dis[to[0]]=dis[x.v]+to[1];
                que.push({to[0],dis[to[0]]});
            }
        }
    }
}   
signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].a;
        a[i].id=i;
    }
    for(int i=1;i<=n;i++)
    {
        cin>>a[i].b;
    }
    sort(a+1,a+1+n);
    for(int i=1;i<=n;i++)
    {
        b[i]=a[i].b;
    }
    int st,ed;
    for(int i=1;i<=n;i++)
    {
        if(a[i].id==1)
        {
            st=i;
        }
        else if(a[i].id==n) ed=i;
        vec[i+n].push_back({i,0});
        vec[i].push_back({n+1,b[1]+a[i].a});
        int k=lower_bound(b+1,b+1+n,m-a[i].a)-b;
        if(k<=n)
        {
            vec[i].push_back({k+n,b[k]+a[i].a-m});
        }
    }
    for(int i=1;i<n;i++)
    {
        vec[i+n].push_back({i+n+1,b[i+1]-b[i]});
    }
    dij(st);
    cout<<dis[ed]<<'\n';
    return 0;
}

相關文章