「SNOI 2019」通信

传送门


problem

n n n 个排成一列的哨站要进行通信。第 i i i 个哨站的频段为 a i a_i ai

每个哨站 i i i 需要选择以下二者之一:

  • 直接连接到控制中心,代价为W;
  • 连接到前面的某个哨站 j ( j < i ) j(jj(j<i),代价为 ∣ a i − a j ∣ |a_i−a_j| aiaj

每个哨站只能被后面的至多一个哨站连接。

请你求出最小可能的代价和。

数据范围: 1 ≤ n ≤ 1000 1\le n \le 1000 1n1000 0 ≤ W , a i ≤ 1 0 9 0\le W,a_i\le 10^9 0W,ai109


solution

应该比较容易看出是费用流吧。

但是,对于点 i i i,如果暴力对每个点 j j j j < i jj<i)都建边,会有 O ( n 2 ) O(n^2) O(n2) 的边,会 T 掉。

仔细想一想,我们从 i i i [ 1 , j − 1 ] [1,j-1] [1,j1] 所有点连边,有一点线段树优化建图的意味。

可是,那个绝对值用普通线段树比较难搞,我们考虑权值线段树 / / /主席树

由于权值较大,要离散化。设原来的权值为 b i b_i bi,离散过后的权值为 a i a_i ai,令 k = max ⁡ i = 1 n { a i } k=\max\limits_{i=1}^n\{a_i\} k=i=1maxn{ai}(即上界)。

我们建两颗主席树,分别表示 b j < b i b_jbj<bi b j > b i b_j>b_i bj>bi j j j,那么第一颗初始应该连 − b j -b_j bj 的边,第二颗则是 b j b_j bj 的边。

那么每次对 i i i 进行连边操作时,我们就向第一颗 [ 1 , a i ] [1,a_i] [1,ai] b i b_i bi 的边,向第二颗 [ a i , k ] [a_i,k] [ai,k] − b i -b_i bi 的边。

不难发现这和原来的连边的等价的。

这样,边的数量就减到了 O ( n log ⁡ n ) O(n\log n) O(nlogn),便能通过此题。


code

#include
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int N=2e5+5,M=3e6+5;
int n,W,k,t=1,S,T,rt1,rt2,tot;
int a[N],b[N],f[N],vis[N],walk[N];
int first[N],v[M],w[M],nxt[M],cost[M];
ll d[N],mincost;
int Abs(int x)  {return x>0?x:-x;}
void add(int x,int y,int f,int c){
	nxt[++t]=first[x],first[x]=t,v[t]=y,w[t]=f,cost[t]=c;
	nxt[++t]=first[y],first[y]=t,v[t]=x,w[t]=0,cost[t]=-c;
}
queue<int>Q;
bool spfa(){
	for(int i=S;i<=tot;++i){
		f[i]=first[i],d[i]=1e18,vis[i]=walk[i]=0;
	}
	d[S]=0,Q.push(S);
	while(!Q.empty()){
		int x=Q.front();Q.pop();
		vis[x]=0;
		for(int i=first[x];i;i=nxt[i]){
			int to=v[i];
			if(w[i]&&d[to]>d[x]+cost[i]){
				d[to]=d[x]+cost[i];
				if(!vis[to])  Q.push(to),vis[to]=1;
			}
		}
	}
	return d[T]!=1e18;
}
int dinic(int now,int flow){
	if(now==T){
		mincost+=d[T]*flow;return flow;
	}
	int delta,ans=0;walk[now]=1;
	for(int &i=f[now];i;i=nxt[i]){
		int x=v[i];
		if(!walk[x]&&w[i]&&d[x]==d[now]+cost[i]){
			delta=dinic(x,min(flow,w[i]));
			w[i]-=delta,w[i^1]+=delta,flow-=delta,ans+=delta;
			if(!flow)  return ans;
		}
	}
	return ans;
}
void discrete(){
	sort(b+1,b+n+1),k=unique(b+1,b+n+1)-(b+1);
	for(int i=1;i<=n;++i)  a[i]=lower_bound(b+1,b+k+1,a[i])-b;
}
int lc[N],rc[N];
#define mid ((l+r)>>1)
void edge(int rt,int l,int r,int x,int y,int p,int val){
	if(!rt)  return;
	if(l>=x&&r<=y)  {add(p,rt,1,val);return;}
	if(x<=mid)  edge(lc[rt],l,mid,x,y,p,val);
	if(y >mid)  edge(rc[rt],mid+1,r,x,y,p,val);
}
void Insert(int rt,int &u,int l,int r,int pos,int p,int val){
	u=++tot;
	lc[u]=lc[rt],rc[u]=rc[rt];
	if(l==r)  {add(u,p,1,val);return;}
	if(pos<=mid)  Insert(lc[rt],lc[u],l,mid,pos,p,val);
	else  Insert(rc[rt],rc[u],mid+1,r,pos,p,val);
	if(lc[u])  add(u,lc[u],1e9,0);
	if(rc[u])  add(u,rc[u],1e9,0);
}
#undef mid
int main(){
	scanf("%d%d",&n,&W);
	S=0,T=2*n+2;
	for(int i=1;i<=n;++i){
		scanf("%d",&a[i]),b[i]=a[i];
		add(S,i,1,0),add(n+i,T,1,0),add(i,2*n+1,1,W);
	}
	add(2*n+1,T,1e9,0),discrete(),tot=T;
	for(int i=1;i<=n;++i){
		edge(rt1,1,k,1,a[i],i,b[a[i]]);
		edge(rt2,1,k,a[i],k,i,-b[a[i]]);
		Insert(rt1,rt1,1,k,a[i],n+i,-b[a[i]]);
		Insert(rt2,rt2,1,k,a[i],n+i,b[a[i]]);
	}
	while(spfa())  dinic(S,inf);
	printf("%lld\n",mincost);
	return 0;
}

你可能感兴趣的:(#,线段树优化建图,#,网络流)