1. 程式人生 > 其它 >M-SOLUTIONS Programming Contest 2021(AtCoder Beginner Contest 232) 題解

M-SOLUTIONS Programming Contest 2021(AtCoder Beginner Contest 232) 題解

M-SOLUTIONS Programming Contest 2021(AtCoder Beginner Contest 232) 題解(只包含G和H) 目錄

因為偷懶就只寫G和H的題解了。

G - Modulo Shortest Path

首先可以觀察到對於一條從點\(i\)到點\(j\)的邊,權值只有兩種:\(A_i+B_j\)\(A_i+B_j-M\)

那麼我們假如把點按照\(B\)升序排成一列,那麼當中的一個點肯定只會向前半部分連權值為\(A_i+B_j\)的邊,後半部分連權值\(A_i+B_j-M\)的邊。

我們可以把一個點拆成入點和出點(此時仍舊按照\(B\)升序排成兩列),由出點向入點連權值為\(A_i+B_j\)\(A_i+B_j-M\)的這兩種邊,入點向對應出點連線權值為\(0\)

的邊。

雖然此時邊數仍舊是\(O(N^2)\)的,但是我們可以在每一個入點向下一個入點連一條權值為它們的\(B_i\)的差值的邊,可以看成是一種反悔操作,走到入點了可以不走向出點,而是往下一個入點繼續走,再走到對應的出點。這樣發現沒有必要給每一個點的出點連那麼多條邊出去了,只需要兩條,一條連向序列開頭的點,一條連向第一個使得權值和大於等於\(M\)的點。那麼每一條原來的出點向入點連線的邊都可以看成是一條現在出點向入點連線的邊和一條入點構成的鏈的組合。

接下來只需要從起點到終點跑最短路就行了。

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;

int n,m,S,T;
pair<pair<int,int>,int> a[200005];
vector<pair<int,int>> g[400005];
ll d[400005];

int 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].first.first;
    for(int i=1;i<=n;i++)cin>>a[i].first.second,a[i].second=i;
    sort(a+1,a+1+n,[](const pair<pair<int,int>,int> &a,const pair<pair<int,int>,int> &b){
        if(a.first.second!=b.first.second)return a.first.second<b.first.second;
        return a.second<b.second;
    });
    S=1;
    while(a[S].second!=1)S++;
    T=1;
    while(a[T].second!=n)T++;
    for(int i=1;i<n;i++){
        g[n+i].emplace_back(n+i+1,a[i+1].first.second-a[i].first.second);
    }
    for(int i=1;i<=n;i++){
        g[n+i].emplace_back(i,0);
        g[i].emplace_back(n+1,a[i].first.first+a[1].first.second);
        int l=1,r=n,mid,res=-1;
        while(l<=r){
            mid=l+r>>1;
            if(a[i].first.first+a[mid].first.second>=m){
                res=mid;
                r=mid-1;
            }else{
                l=mid+1;
            }
        }
        if(res!=-1)g[i].emplace_back(n+res,a[i].first.first+a[res].first.second-m);
    }
    priority_queue<pair<ll,int>,vector<pair<ll,int>>,greater<pair<ll,int>>> q;
    q.emplace(0,S);
    memset(d,0x3f,sizeof(d));
    d[S]=0;
    while(!q.empty()){
        ll cd;
        int x;
        tie(cd,x)=q.top();
        q.pop();
        if(cd>d[x])continue;
        for(auto &[y,z]:g[x])if(d[y]>cd+z){
            q.emplace(d[y]=cd+z,y);
        }
    }
    cout<<d[T]<<'\n';

    return 0;
}

H - King's Tour

比賽時沒有想到遞迴處理的我真是鑄幣嗚嗚嗚

首先可以考慮只有兩行或者只有兩列的棋盤怎麼處理,那麼由於八向移動的特性可以這麼處理(起點在左上角,紅點為終點):

然後就考慮行數和列數都至少為\(3\)的情況(同樣預設起點左上角),嘗試走過最上方的一行,或者最左邊的一列,由於終點一定不會在左上角,且行數和列數都大於\(2\),那麼一定兩種操作可以選做一種,並且做完以後剩下來沒訪問過的棋盤仍舊是滿足起點在一個角上且終點不和起點相同位置。

然後遞迴處理即可。

#include<bits/stdc++.h>
using namespace std;

vector<pair<int,int>> sol(int n,int m,int a,int b){
    vector<pair<int,int>> r;
    if(m==2){
        for(int i=1;i<a;i++){
            r.emplace_back(i,1);
            r.emplace_back(i,2);
        }
        for(int i=a;i<=n;i++)r.emplace_back(i,b^3);
        for(int i=n;i>=a;i--)r.emplace_back(i,b);
    }else if(n>2&&(a>2||a==2&&b!=m)){
        for(int i=1;i<=m;i++)r.emplace_back(1,i);
        for(auto &[x,y]:sol(n-1,m,a-1,m+1-b))r.emplace_back(x+1,m+1-y);
    }else{
        r=sol(m,n,b,a);
        for(auto &[x,y]:r)swap(x,y);
    }
    return r;
}

int main(){

    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int n,m,a,b;
    cin>>n>>m>>a>>b;
    for(auto &[x,y]:sol(n,m,a,b))cout<<x<<' '<<y<<'\n';

    return 0;
}