P2564 [SCOI2009]生日礼物

P2564 [SCOI2009]生日礼物

题意:

n个彩珠,k个种类,分布在一个彩带上,现在要选取彩带的一部分,要求该部分包含所有种类的彩珠,且长度尽可能短,你能计算这个最短的长度吗?
1≤N≤1000000,1≤K≤60,0≤珠子位置<2{31}

题解:

比赛时第一反应是尺取,但是一看这个距离放弃了,后来想可以先离散?或者map距离,因为种种原因都没做出,现在赛后补题
实质用的是区间伸缩
因为珠子的位置范围很大,但是珠子的数量有限,所以我们完全可以枚举珠子的数量
我们给每种彩珠编号以及存下每种彩珠的坐标
然后根据坐标排序
然后就是区间伸缩(我一直认为是尺取),两个指针l和r,分别指向左右两端的两种珠子,在一半题中我们都会让指针指向距离的左右两端,但是因为本题距离过长,所以我们使其指向左右两端彩珠(即第i个彩珠和第j个彩珠)
左端l更新情况是:如果第l个彩珠和第l-1个彩珠在一个位置,那我们就l++,以l+1个彩珠为新的起点
相当于我们以不同的彩珠作为左端点,然后枚举右端点,当num == m时,说明目前这个区间上有了所有的彩珠
(u1s1我也将不大很明白,就是把尺取距离变成尺取每种彩珠)
详细看看代码把

代码:

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();//s=(s<<3)+(s<<1)+(ch^48);
   return s*w;
}
const int maxn=1000008;
struct node{
	int pos;//位置 
	int id;//种类 
}a[maxn];
int b[maxn];//用来统计每种彩珠的出现次数 
bool cmp(node a,node b)
{
	return a.pos<b.pos;
}
int main()
{
	int n,k;
	cin>>n>>k;
	int tot=0;
	for(int i=1;i<=k;i++){
		int t;
		cin>>t;
		for(int j=1;j<=t;j++){
			cin>>a[++tot].pos;
			a[tot].id=i;
		}
	}
	sort(a+1,a+1+tot,cmp);
	int l=1,r=1;
	int num=0;//记录当前彩带上有多少种彩珠
	b[a[l].id]++;//先将第一个彩珠记录 
	num++;
	int sum=1e9;//最后记录答案 
	while(l<=n&&r<=n)
	{
		if(num==k)
		{
			sum=min(sum,a[r].pos-a[l].pos);//更新答案 
			b[a[l].id]--;
			if(b[a[l].id]==0)num--;//离开l点
			l++;
			if(l>n)break;
			while(a[l].pos==a[l-1].pos)//如果移动后还是原来那个位置 
			{
				b[a[l].id]--;
				if(b[a[l].id]==0)num--;
				l++;
				if(l>n)break;
			}
		}
		else 
		{
			r++;
			if(r>n)break;
			b[a[r].id]++;
			if(b[a[r].id]==1)num++; 
			while(a[r].pos==a[r+1].pos)
			{
				r++;
				if(r>n)break;
				b[a[r].id]++;
				if(b[a[r].id]==1)num++;
			}
		}
	} 
	cout<<sum;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值