IOI2017 Day1 Wiring 题解

传送门

题解

第一次写博客,感觉好激动啊~

其实这题的Subtask 2很有启发意义

Subtask 2中,由于红点在蓝点左边,所以答案就是 i = 0 n 1 ( r n 1 r i ) + i = 0 m 1 ( b i b 0 ) + max ( n , m ) × ( b 0 r n 1 ) \sum_{i=0}^{n-1}(r_{n-1}-r_i)+\sum_{i=0}^{m-1}(b_i-b_0)+\max(n,m)\times(b_0-r_{n-1})

回到原题,考虑把连接点的序列分成几段的红色,蓝色,…相间

1.png

这时应该把每一段切开,左边的连向前一段,右边的连向后一段

2.png

这时花费的代价就是相邻两段(蓝红或者红蓝)连起来所需的代价(参照Subtask 2的公式)

这个可以用dp解决,设 d p i dp_i 是当前第 i i 个点所在段在位置 i i 切开时,前 i i 个点的最小代价

根据第 x x 段和第 x 1 x-1 段的大小关系分类讨论,并维护相应的值,可以做到 O ( 1 ) O(1) 转移

最终答案就是 d p n dp_n (最后一个点必须切开),总时间复杂度为 O ( n ) O(n)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll N=100005,inf=1ll<<60;
ll n1,n2,n,dp[N],f[N],g[N],sum[N],sz,sz1,st;
struct point{ll x,col;bool operator<(point a){return x<a.x;}}p1[N],p[N];
long long min_total_length(vector<int> red, vector<int> blue) {
	n1=red.size(),n2=blue.size();
	for(ll i=0;i<n1;i++)p1[n++]=(point){red[i],1};
	for(ll i=0;i<n2;i++)p1[n++]=(point){blue[i],2};
	merge(p1,p1+n1,p1+n1,p1+n,p+1);
	for(ll i=1;i<=n;i++){
		if(p[i].col!=p[i-1].col){
			for(int j=i;p[j].col==p[i].col;j++)sum[i]+=p[j].x;
			for(int j=i-1;j>=st;j--){
				f[j]=p[i-1].x*(i-j)-sum[j]+min(dp[j],dp[j-1]);
				if(i!=j+1)f[j]=min(f[j],f[j+1]);
			}
			for(int j=st;j<i;j++){
				g[j]=p[i].x*(i-j)-sum[j]+min(dp[j],dp[j-1]);
				if(j!=st)g[j]=min(g[j],g[j-1]);
			}
			st=i,sz1=sz,sz=1;
		}else ++sz,sum[i]=sum[i-1]-p[i-1].x;
		if(st==1){dp[i]=inf;continue;}
		ll cursum=sum[st]-sum[i]+p[i].x;
		if(sz1<=sz)dp[i]=f[st-sz1]-sz*p[st-1].x+cursum;
		else dp[i]=min(g[st-sz-1]-sz*p[st].x+cursum,f[st-sz]-sz*p[st-1].x+cursum);
	}
	return dp[n];
}

猜你喜欢

转载自blog.csdn.net/qq_27327327/article/details/80710590