因為偷懶就只寫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;
}