题解:P16424 [IATI 2022] Lifts
lailai0916 · · 题解
题意简述
有
解题思路
一部电梯负责的请求编号必然递增。若它在完成请求
把请求分给电梯,相当于把
考虑一张二分图。左部点
匹配中的每条边连接同一部电梯连续完成的两个请求。左右两侧的匹配限制保证每个请求至多有一个前驱和一个后继。边只从较晚请求指向较早请求,因此不会形成环。
大小为
这张三角形二分图存在唯一的大小为
目标匹配比初始匹配少
为每条对角边建立一个容量为
若撤销结点
因此,从结点
一条流路径若经过
发送
直接建立转移仍需二次空间。下面按结点编号分治区间
记:
先处理
右半区间的
再把两类坐标同时取负,重复相同构造。此时覆盖的正是
每层分治的结点和边数均为
得到势能后,所有剩余边的约化费用非负。随后用 Dijkstra 增广
时间复杂度为
正确性证明
首先证明匹配模型正确。每部电梯的相邻请求对应一条匹配边。一个请求至多有一个前驱和一个后继,所以这些边两两不冲突。反向边的请求编号严格减小,匹配不可能形成环。因此,大小为
再证明退流网络正确。初始匹配由所有对角边组成,费用为
每条路径撤销的对角边数比加入的非对角边数多
最后证明分治压图正确。任取转移
原问题、固定大小匹配、退流网络和压缩网络依次保持可行方案与费用。故算法输出的结果就是最小空载距离。
参考代码
#include <bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=10005;
const int V=200005;
const int M=1100005;
const ll inf=0x3f3f3f3f3f3f3f3f;
struct Edge
{
int v,nxt,cap,cost;
}e[M];
int a[N],b[N],x[N],y[N],in[N],out[N];
int head[V],pos[V],pre[V],hp[V];
ll dis[V],h[V];
int s,t,tot,cnt=1,siz;
void add(int u,int v,int cap,int cost)
{
e[++cnt]={v,head[u],cap,cost};
head[u]=cnt;
e[++cnt]={u,head[v],0,-cost};
head[v]=cnt;
}
void link(int l,int mid,int r,int d)
{
vector<int> u,v;
for(int i=mid+1;i<=r;i++)u.push_back(i);
for(int i=l;i<=mid;i++)v.push_back(i);
sort(u.begin(),u.end(),[&](int i,int j){return d*x[i]<d*x[j];});
sort(v.begin(),v.end(),[&](int i,int j){return d*y[i]<d*y[j];});
vector<int> w(v.size());
for(int i=0;i<v.size();i++)
{
w[i]=++tot;
if(i)add(w[i],w[i-1],N,0);
add(w[i],in[v[i]],N,-d*y[v[i]]);
}
int j=0;
for(auto i:u)
{
while(j<v.size()&&(d*y[v[j]]<d*x[i]||(d==1&&y[v[j]]==x[i])))j++;
if(j)add(out[i],w[j-1],N,d*x[i]);
}
}
void build(int l,int r)
{
if(l==r)return;
int mid=(l+r)/2;
link(l,mid,r,1);
link(l,mid,r,-1);
build(l,mid);
build(mid+1,r);
}
void init()
{
for(int i=1;i<=tot;i++)for(int j=head[i];j;j=e[j].nxt)if(e[j].cap)pos[e[j].v]++;
int l=0,r=0;
for(int i=1;i<=tot;i++)if(!pos[i])hp[r++]=i;
fill(dis+1,dis+tot+1,inf);
dis[s]=0;
while(l<r)
{
int u=hp[l++];
for(int i=head[u];i;i=e[i].nxt)
{
if(!e[i].cap)continue;
if(dis[u]!=inf)dis[e[i].v]=min(dis[e[i].v],dis[u]+e[i].cost);
if(!--pos[e[i].v])hp[r++]=e[i].v;
}
}
for(int i=1;i<=tot;i++)h[i]=dis[i];
}
void push(int u)
{
if(!pos[u])pos[u]=++siz;
int i=pos[u];
while(i>1)
{
int j=i/2;
if(dis[hp[j]]<=dis[u])break;
hp[i]=hp[j];
pos[hp[i]]=i;
i=j;
}
hp[i]=u;
pos[u]=i;
}
int pop()
{
int res=hp[1];
pos[res]=-1;
if(--siz)
{
int u=hp[siz+1],i=1;
while(i*2<=siz)
{
int j=i*2;
if(j<siz&&dis[hp[j+1]]<dis[hp[j]])j++;
if(dis[hp[j]]>=dis[u])break;
hp[i]=hp[j];
pos[hp[i]]=i;
i=j;
}
hp[i]=u;
pos[u]=i;
}
return res;
}
ll flow(int k)
{
ll res=0;
for(int j=1;j<k;j++)
{
fill(dis+1,dis+tot+1,inf);
fill(pos+1,pos+tot+1,0);
dis[s]=0;
siz=0;
push(s);
while(siz)
{
int u=pop();
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].v;
if(!e[i].cap||pos[v]==-1)continue;
ll w=(ll)e[i].cost+h[u]-h[v];
if(dis[v]>dis[u]+w)
{
dis[v]=dis[u]+w;
pre[v]=i;
push(v);
}
}
}
for(int i=1;i<=tot;i++)if(dis[i]!=inf)h[i]+=dis[i];
int u=t;
while(u!=s)
{
res+=e[pre[u]].cost;
e[pre[u]].cap--;
e[pre[u]^1].cap++;
u=e[pre[u]^1].v;
}
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n,k;
cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
ll ans=0;
for(int i=1;i<n;i++)ans+=abs(a[i+1]-b[i]);
if(k==1){cout<<ans<<'\n';return 0;}
s=++tot;
t=++tot;
for(int i=1;i<n;i++)
{
x[i]=a[i+1];
y[i]=b[i];
in[i]=++tot;
out[i]=++tot;
add(s,in[i],1,0);
add(in[i],out[i],1,-abs(x[i]-y[i]));
add(out[i],t,1,0);
}
build(1,n-1);
init();
cout<<ans+flow(k)<<'\n';
return 0;
}