USACO 2018 January Contest Platinum A: Lifeguards 题解

该博客详细介绍了USACO 2018年一月竞赛铂金级别问题A的解决方案。博主首先按区间左端点对所有区间进行排序,接着处理完全包含的区间,如果这类区间数量超过限制k,则可以直接计算结果。否则,使用动态规划(dp)方法求解。博主指出原始dp转移方程会导致O(n*n*k)的时间复杂度,不满足要求。通过优化,博主提出用单调队列来维护状态,将复杂度降低到O(n*k)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

将所有的区间按左端点从小到大排序

我们处理那些被完全包含的区间,这些区间即使删除也不会使答案变坏

这样先删一波,如果发现这种小区间的个数多于k,就可以直接算答案了

否则我们要dp

设dp[i][j]为考虑到第i个区间,已经删除了j个区间,且第i个区间没有被删除的情况下最大的覆盖长度

显然有状态转移方程dp[i][j]=max(dp[k][j-i-k-1]+第i个区间贡献的覆盖)

这个方程相当于枚举上一个没有被删除的区间k,然后将k+1~i-1全部删除

但我们看到这个转移是O(n)的,所以总复杂度为O(n*n*k),不能接受

考虑优化dp转移

对于第i个区间,设其左端点为l

我们先看一看方程,会发现对dp[i][j]产生贡献的i'-j'=i-1-j

1. 对于i之前的那些右端点<=l的区间,它们与i没有重叠部分,所以只要在它们当中取max,再加上第i个区间的长度即可

2. 对于那些与i有重叠部分的区间,在当前区间右移的时候,这些dp的贡献会变,但相对大小不会变,所以可以维护一个单调队列,dp[i][j]对应的单调队列的编号为i-1-j,每次先把队头的那些已经跑到左边的区间弹出去(算成1的贡献),然后取队头就是当前的有重叠的状态中的最大答案

然后当前dp值算出来以后要插进对应的单调队列中(编号为i-j的单调队列),如果队尾状态加上与当前状态的右端点差还没有当前状态的dp值大的话,就把它从队尾弹出

这样总复杂度O(n*k)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <utility>
#include <cctype>
#include <algorithm>
#include <bitset>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <deque>
#include <stack>
#include <cmath>
#define LL long long
#define LB long double
#define x first
#define y second
#define Pair pair<int,int>
#define pb push_back
#define pf push_front
#define mp make_pair
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const LL LINF=2e16;
const int INF=2e9;
const int magic=348;
const double eps=1e-10;

inline int getint()
{
	char ch;int res;bool f;
	while (!isdigit(ch=getchar()) && ch!='-') {}
	if (ch=='-') f=false,res=0; else f=true,res=ch-'0';
	while (isdigit(ch=getchar())) res=res*10+ch-'0';
	return f?res:-res;
}

int n,k;
int maxn[100048];
deque<Pair> q[100048];
int dp[100048][101];
Pair a[100048];bool exist[100048];
Pair x[100048];int x_top=0;

struct node
{
	int val;
	int nv;
	int from;
	bool type;
}b[200048];int tot=0;
Pair Pos[200048];
vector<int> fake;

int c[200048];
inline bool update(int x,int delta)
{
	while (x<=n*2)
	{
		c[x]+=delta;
		x+=LOWBIT(x);
	}
}
inline int query(int x)
{
	int res=0;
	while (x)
	{
		res+=c[x];
		x-=LOWBIT(x);
	}
	return res;
}

bool cmp_y(Pair x,Pair y)
{
	return x.y<y.y;
}

bool cmp(node x,node y)
{
	return x.val<y.val;
}

int main ()
{
	freopen ("lifeguards.in","r",stdin);
	freopen ("lifeguards.out","w",stdout);
	int i,j;
	n=getint();k=getint();
	for (i=1;i<=n;i++) a[i].x=getint(),a[i].y=getint();
	if (k>=n)
	{
		printf("0\n");
		return 0;
	}
	sort(a+1,a+n+1);
	for (i=1;i<=n;i++)
	{
		b[++tot].val=a[i].x;b[tot].from=i;b[tot].type=false;
		b[++tot].val=a[i].y;b[tot].from=i;b[tot].type=true;
	}
	sort(b+1,b+tot+1,cmp);
	for (i=1;i<=tot;i++)
		if (!b[i].type) Pos[b[i].from].x=i; else Pos[b[i].from].y=i;
	for (i=1;i<=tot;i++)
		if (!b[i].type)
			update(i,1);
		else
		{
			update(Pos[b[i].from].x,-1);
			if (query(Pos[b[i].from].x)) fake.pb(b[i].from);
		}
	if (int(fake.size())>=k)
	{
		int ans=0,max_right=0;
		for (i=1;i<=n;i++)
		{
			if (max_right<=a[i].x)
			{
				ans+=a[i].y-a[i].x;
			}
			else if (max_right>=a[i].y) 
			{
				continue;
			}
			else
			{
				ans+=a[i].y-max_right;
			}
			max_right=max(max_right,a[i].y);
		}
		printf("%d\n",ans);
		return 0;
	}
	k-=int(fake.size());
	memset(exist,true,sizeof(exist));
	for (i=0;i<int(fake.size());i++) exist[fake[i]]=false;
	for (i=1;i<=n;i++)
		if (exist[i]) x[++x_top]=a[i];
	for (i=1;i<=x_top;i++)
		for (j=0;j<=k;j++)
			dp[i][j]=0;
	dp[1][0]=x[1].y-x[1].x;
	q[1].pb(mp(dp[1][0],x[1].y));
	memset(maxn,0,sizeof(maxn));
	for (i=2;i<=x_top;i++)
		for (j=0;j<=min(k,i-1);j++)
		{
			int ind=i-1-j;
			while (!q[ind].empty() && q[ind].front().y<=x[i].x)
			{
				maxn[ind]=max(maxn[ind],q[ind].front().x);
				q[ind].pop_front();
			}
			dp[i][j]=max(dp[i][j],maxn[ind]+x[i].y-x[i].x);
			if (!q[ind].empty()) dp[i][j]=max(dp[i][j],q[ind].front().x+x[i].y-q[ind].front().y);
			int ins_ind=i-j;
			while (!q[ins_ind].empty() && q[ins_ind].back().x+x[i].y-q[ins_ind].back().y<=dp[i][j]) q[ins_ind].pop_back();
			q[ins_ind].pb(mp(dp[i][j],x[i].y));
		}
	int ans=0;
	for (i=1;i<=x_top;i++)
		for (j=0;j<=min(k,i-1);j++)
			if (j+x_top-i==k) ans=max(ans,dp[i][j]);
	printf("%d\n",ans);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值