Codevs 线段覆盖1&2&3&4&5

2 篇文章 0 订阅
对于线段覆盖1、3,贪心,每次选取末端点靠前的,O(n)扫一遍即可(话说这个题似乎排序最占时间)。(当然DP也可以)
#include<iostream>
#include<algorithm>
#define maxn 1000000+5
using namespace std;
struct line{
	int b,e;
	friend bool operator < (line x,line y){
		if(x.e!=y.e)return x.e<y.e;
		return x.b>y.b;
	}
}l[maxn];
int main(){
	ios::sync_with_stdio(false);
	int n,i,j,k,x,y;cin>>n;
	for(i=1;i<=n;i++){
		cin>>x>>y;
		if(x>y)swap(x,y);
		l[i].b=x;l[i].e=y;
	}
	sort(l+1,l+n+1);
	int pos=-1,cnt=0;i=1;
	while(i<=n){
		if(l[i].b>=pos){
			cnt++;
			pos=l[i].e;
		}
		i++;
	}
	cout<<cnt;
	return 0;
}

 但是对于线段有权值的情况,可以证明贪心是行不通的,这时候就必须DP

我的做法是:设dp[I]表示到第i条线段可获得的最大价值,容易得到这样的状态转移方程:

dp[I]=max(dp[I-1],dp[j]+line[i].c),j表示距离i最近的与i没有交集的线段。

对于查找线段j,朴素的做法就是从i开始向前枚举,然而这样只能通过线段覆盖2,对于4会TLE两个点,5会TLE7个点。

仔细分析线段性质,可以发现由于我们对线段进行了排序,所以线段的末端点对于线段的编号是具有单调性的,所以可以使用二分查找第一个满足此性质的线段。

#include<iostream>
#include<algorithm>
#define int long long
#define maxn 1000000+5
using namespace std;
struct line{
	int b,e,c;//begin,end,cost
	friend bool operator < (line x,line y){
		if(x.e!=y.e)return x.e<y.e;
		else if(x.b!=y.b)return x.b<y.b;
		else return x.c<y.c;
	}
}l[maxn];
int dp[maxn],x,y,z;
int bsearch(int i,bool d){
	if(d==0){//二分查找
		int lf=0,ri=i-1,mid;
		while(lf<ri){
			mid=(lf+ri)>>1;
			if(l[mid].e>l[i].b){
				ri=mid-1;
			}
			else if(l[mid].e<=l[i].b){
				lf=mid+1;
			}
		}
		if(l[lf].e<=l[i].b)return lf;
		return lf-1;
	}
	else{//朴素查找
		int p=i;
		while(l[p].e>l[i].b)p--;
		return p;
	}
}
#undef int
int main(){
	ios::sync_with_stdio(false);
	int n,i,j,k;cin>>n;
	for(i=1;i<=n;i++){
		cin>>x>>y>>z;
		l[i].b=x;l[i].e=y;l[i].c=z;
	}
	sort(l+1,l+n+1);
	l[0].b=l[0].e=l[0].c=-1;
	for(int i=1;i<=n;i++)
		dp[i]=max(dp[i-1],dp[bsearch(i,0)]+l[i].c);
	cout<<dp[n];
	return 0;
}

然而还有另一种DP思路,(%ZY),设dp[I]表示在坐标I的最大价值,可行的一种状态转移方程为:

f[line[I].e]=max(f[line[I].e],f[line[I].b]+line[I].c);

转移时枚举line[I].e而不是每个坐标

在每次循环时,拿f[line[I].e]来更新从f[Iine[I].e+1]到f[Iine[I+1].e]中的所有点,这样可以实现在转移时选或不选这条线段的操作。

对于线段覆盖4、5,数据范围过大,需要离散化。

#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
struct segment
{
	long long b,e,c;
}a[1000005];
struct mes
{
	long long pos,rank;
}t;
long long n,i,j,pre,p,f[1000005];
struct cmp2
{
	bool operator()(mes x,mes y)
	{
		return x.pos>=y.pos;
	}
};
priority_queue<mes,vector<mes>,cmp2> q;
bool cmp(segment x,segment y)
{
	return x.e<y.e;
}
int main()
{
	ios::sync_with_stdio(false);
	cin>>n;
	for(i=1;i<=n;i++)
	{
		cin>>a[i].b>>a[i].e>>a[i].c;
		q.push((mes){a[i].b,(i<<1)-1});//奇数节点为begin
		q.push((mes){a[i].e,i<<1});//偶数节点为end
	}
	pre=q.top().pos;
	for(i=1;i<=n+n;i++)
	{//离散化
		t=q.top();
		q.pop();
		if(t.pos!=pre)//如果两个点的坐标不相同
		{
			++p;
			pre=t.pos;
		}
		if((t.rank&1)==1) a[(t.rank+1)>>1].b=p;
		else a[(t.rank)>>1].e=p;
	}
	sort(a+1,a+n+1,cmp);
	for(i=1;i<n;i++)
	{
		f[a[i].e]=max(f[a[i].e],f[a[i].b]+a[i].c);
		for(j=a[i].e+1;j<=a[i+1].e;j++) f[j]=f[a[i].e];
	}
	f[a[n].e]=max(f[a[n].e],f[a[n].b]+a[n].c);
	cout<<f[a[n].e];
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值