题解:P16397 [ECUSTPC 2026 Spring] 右灯左行
lailai0916 · · 题解
题意简述
河流两岸各有
解题思路
每条环线横跨相邻两列。向右穿过第 O 时走北岸,方向为 I 时走南岸。向左穿过它时,所在岸恰好相反。
边权非负,所以一定存在不重复经过站点的最短路。若起点列
先预处理向右行走的两个距离势能。
穿过第
若相邻两条环线方向相同,入点就是下一条环线的出发点。若方向不同,则需要在第
于是,从第
从右向左完全对称。令
相邻方向改变时,同样增加
还需要知道每一列的两个换岸代价。记:
把方向连续相同的环线合并成极大区间
以方向 O 的区间为例。计算
区间边界不存在的项直接忽略。在最右列
计算
方向 I 时,上述两个递推方向互换。
回答询问时分三种情况。
若
若
若
预处理会线性扫描环线与同向区间,每个询问只进行常数次判断。因此,总时间复杂度为
正确性证明
先证明跨列主干唯一。任意不含环的路径从第
每个切口在指定方向上只有一条横向边。因此,经过的岸和边权都被唯一确定。
相邻环线方向变化时,路径必须在公共列换岸。两条相邻竖边都能完成这一步,取较小边权即可。故势能差准确给出两个主干端点之间的最短距离。
再证明换岸递推。一个极大同向区间内,两岸的方向相反。简单路径一旦选定换岸竖边,换岸前后的横向路径均被唯一确定。
递推枚举立即换岸和跨过一个环后再换岸两种可能,因而覆盖区间内每一条简单换岸路径。边界处再加入相邻反向环线的竖边,便得到全图中的最小换岸代价。
最后,不同列询问的简单最短路能唯一拆成起点换岸、跨列主干和终点换岸三段。
两段换岸使用
三段均为各自端点间的最短路径,连接后也是合法路径。因此,询问答案等于全图最短路。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=100005;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,q;
int u[N],d[N],l[N],r[N];
ll dn[N],up[N],ri[N],ro[N],li[N],lo[N];
string s;
void vertical(int x,int y)
{
if(s[x]=='O')
{
dn[y+1]=min(dn[y+1],(ll)r[y]);
if(y<n-1)dn[y+1]=min(dn[y+1],(ll)l[y+1]);
for(int i=y;i>=x;i--)
{
if(i>x)dn[i]=min(dn[i],(ll)r[i-1]);
dn[i]=min(dn[i],dn[i+1]+u[i]+d[i]);
}
up[x]=min(up[x],(ll)l[x]);
if(x>1)up[x]=min(up[x],(ll)r[x-1]);
for(int i=x+1;i<=y+1;i++)
{
if(i<=y)up[i]=min(up[i],(ll)l[i]);
up[i]=min(up[i],up[i-1]+u[i-1]+d[i-1]);
}
}
else if(s[x]=='I')
{
dn[x]=min(dn[x],(ll)l[x]);
if(x>1)dn[x]=min(dn[x],(ll)r[x-1]);
for(int i=x+1;i<=y+1;i++)
{
if(i<=y)dn[i]=min(dn[i],(ll)l[i]);
dn[i]=min(dn[i],dn[i-1]+u[i-1]+d[i-1]);
}
up[y+1]=min(up[y+1],(ll)r[y]);
if(y<n-1)up[y+1]=min(up[y+1],(ll)l[y+1]);
for(int i=y;i>=x;i--)
{
if(i>x)up[i]=min(up[i],(ll)r[i-1]);
up[i]=min(up[i],up[i+1]+u[i]+d[i]);
}
}
}
void solve()
{
cin>>n>>q;
for(int i=1;i<n;i++)
{
cin>>u[i]>>d[i]>>l[i]>>r[i];
}
cin>>s;
s=' '+s;
fill(dn+1,dn+n+1,inf);
fill(up+1,up+n+1,inf);
for(int i=1;i<n;)
{
int j=i;
while(j+1<n&&s[j+1]==s[i])j++;
vertical(i,j);
i=j+1;
}
ro[1]=0;
for(int i=1;i<n;i++)
{
ri[i+1]=ro[i]+(s[i]=='O'?u[i]:d[i]);
if(i+1<n)ro[i+1]=ri[i+1]+(s[i]==s[i+1]?0:min(r[i],l[i+1]));
}
lo[n]=0;
for(int i=n-1;i;i--)
{
li[i]=lo[i+1]+(s[i]=='O'?d[i]:u[i]);
if(i>1)lo[i]=li[i]+(s[i]==s[i-1]?0:min(l[i],r[i-1]));
}
while(q--)
{
char a,b;
int x,y;
cin>>a>>x>>b>>y;
int c=a=='S',e=b=='S';
if(x==y)
{
if(c==e)cout<<0<<'\n';
else cout<<(c?up[x]:dn[x])<<'\n';
}
else if(x<y)
{
int st=s[x]=='I',ed=s[y-1]=='I';
ll ans=ri[y]-ro[x];
if(c!=st)ans+=c?up[x]:dn[x];
if(e!=ed)ans+=e?dn[y]:up[y];
cout<<ans<<'\n';
}
else if(x>y)
{
int st=s[x-1]=='O',ed=s[y]=='O';
ll ans=li[y]-lo[x];
if(c!=st)ans+=c?up[x]:dn[x];
if(e!=ed)ans+=e?dn[y]:up[y];
cout<<ans<<'\n';
}
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while(T--)solve();
return 0;
}