【洛谷5331】[SNOI2019] 通信(CDQ分治+费用流)
- 有\(n\)个哨站,第\(i\)个哨站段频为\(a_i\)。
- 第\(i\)个哨站可以选择花费\(m\)的代价直接与控制中心相连,也可以选择花费\(|a_i-a_j|\)的代价与第\(j\)个哨站(\(j<i\))相连。每个哨站最多与一个后面的哨站相连。
- 求最小代价。
- \(n\le10^3\)
暴力费用流
把每个点拆成两个,分别表示向前连和向后连。
具体建图如下:
- 从超级源向\(i\)连容量为\(1\)、费用为\(0\)的边。(表示向前连的点)
- 从\(n+i\)向超级汇连容量为\(1\)、费用为\(0\)的边。(表示向后连的点)
- 从\(i\)向超级汇连容量为\(1\)、费用为\(m\)的边。(直接与控制中心相连)
- 从\(i\)向\(n+j\)(\(j<i\))连容量为\(1\)、费用为\(|a_i-a_j|\)的边。(让\(i\)与\(j\)配对)
当然直接这么做边数\(O(n^2)\),肯定过不去。
\(CDQ\)分治优化建图
核心是要优化\(i\)向\(n+j\)连的费用为\(|a_i-a_j|\)的边。
假设当前处理到区间\([l,r]\),则我们需要从右区间\([mid+1,r]\)向左区间\([l,mid]\)连边。
分别将两个区间内的元素排序,对左区间建一排虚点辅助连边,相邻辅助点间所连边的代价为对应元素的差值。
然后我们枚举右区间的每个点,双指针维护出它的前驱和后继,向这两个辅助点连边,那么只要沿着辅助点之间的边走就能通过差值的累积表示出所有需要连的边了。
代码:\(O(\texttt{Dinic})\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 1000
#define LL long long
#define inf (int)1e9
#define INF (LL)1e18
using namespace std;
int n,m,ct,a[N+5];
namespace D//最小费用最大流
{
#define s (2*n+1)
#define t (2*n+2)
#define PS (N*15)
#define ES (PS*5)
#define add(x,y,f,c) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f,e[ee].C=c)
int ee=1,lnk[PS+5];struct edge {int to,nxt,F,C;}e[2*ES+5];
I void Add(CI x,CI y,CI f,CI c) {add(x,y,f,c),add(y,x,0,-c);}
int p[PS+5],IQ[PS+5];LL C[PS+5];queue<int> q;I bool SPFA()
{
RI i;for(i=1;i<=ct;++i) C[i]=INF;C[s]=0,q.push(s),IQ[s]=1;
RI k;W(!q.empty()) for(IQ[k=q.front()]=0,q.pop(),i=lnk[k];i;i=e[i].nxt) e[i].F&&
C[k]+e[i].C<C[e[i].to]&&(C[e[i].to]=C[k]+e[p[e[i].to]=i].C,!IQ[e[i].to]&&(q.push(e[i].to),IQ[e[i].to]=1));
return C[t]^INF;
}
I void MCMF() {RI x;LL g=0;W(SPFA()) {g+=C[x=t];W(x^s) --e[p[x]].F,++e[p[x]^1].F,x=e[p[x]^1].to;}printf("%lld\n",g);}
}
I bool cmp(CI x,CI y) {return a[x]<a[y];}
int id[N+5];I void CDQ(CI l,CI r)//CDQ分治优化建图
{
#define P(i) (ct+(i)-l+1)
if(l==r) return;RI i,j,mid=l+r>>1;for(i=l;i<=r;++i) id[i]=i;sort(id+l,id+mid+1,cmp),sort(id+mid+1,id+r+1,cmp);//分别排序两个子区间
for(i=l;i<=mid;++i) D::Add(P(i),n+id[i],1,0),i^l&&//对左区间建一排辅助点
(D::Add(P(i-1),P(i),inf,a[id[i]]-a[id[i-1]]),D::Add(P(i),P(i-1),inf,a[id[i]]-a[id[i-1]]),0);//相邻辅助点间所连边的代价为对应元素的差值
for(i=mid+1,j=l;i<=r;++i) {W(j<=mid&&a[id[i]]>a[id[j]]) ++j;//双指针维护右区间每个点的前驱后继
j^l&&(D::Add(id[i],P(j-1),1,a[id[i]]-a[id[j-1]]),0),j<=mid&&(D::Add(id[i],P(j),1,a[id[j]]-a[id[i]]),0);}//向这两个辅助点连边
ct+=mid-l+1,CDQ(l,mid),CDQ(mid+1,r);//分治
}
int main()
{
RI i;for(scanf("%d%d",&n,&m),i=1;i<=n;++i)
scanf("%d",a+i),D::Add(2*n+1,i,1,0),D::Add(i,2*n+2,1,m),D::Add(n+i,2*n+2,1,0);//拆成的两个点与超级源汇之间的连边
return ct=2*n+2,CDQ(1,n),D::MCMF(),0;
}
待到再迷茫时回头望,所有脚印会发出光芒