bzoj1178: [Apio2009]CONVENTION会议中心

5 篇文章 0 订阅

这题有点强,一句话题意:求字典序最小的线段覆盖(这里的字典序指选择的线段的编号排序后的字典序)。

如果不要求字典序只需要对左(右)端点排序,然后贪心取即可。

一开始我想到的是DP,但是字典序存在了不可避免的后效性,看了题解之后,才有了思路。

先离散,要使字典序最小优先选序号小的,然后判断选取某一个之后最优解是否改变。接下来就是怎么O(logn)求区间内线段覆盖。(具体做法搜索ppt。。。

随随便便求出每个点出发走过一条线段之后右端点的最小值,倍增出每个点出发走2^k条线段后的右端点的最小值。每次询问就在倍增的东西上走就好了。

//为了这题学了树状数组区间加,区间查和在树状数组上二分,然而似乎比线段树还慢。。。

注意去掉行末空格

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 400005

using namespace std;
//%%% liang ye
int n,m,cnt,b[N],c[N],p[N],f[N][19],M;
struct T
{
	int l,r;
}a[N];
int read()
{
	int x=0;char c=getchar();
	while(c<'0'||c>'9') c=getchar();
	while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
	return x;
}
void mdy(int x,int k)
{
	int t=k*(m-x+1);
	for (int i=x;i<=m;i+=i&-i)
		b[i]+=t,c[i]+=k;
}
int qry(int x)
{
	int t1=0,t2=0;
	for (int i=x;i;i-=i&-i)
		t1+=b[i],t2+=c[i];
	return t1-t2*(m-x);
}
int kth(int x)
{
	int t=0,rk=0,tmp,bt=0,ct=0,tt;
	for (int i=M;i>=0;i--)
	{
		tmp=t+(1<<i);
		if (tmp>m) continue;
		tt=rk+b[tmp]-c[tmp]*(m-tmp)+ct*(tmp-t);
		if (tt<x)
			rk=tt,t=tmp,bt+=b[tmp],ct+=c[tmp];
	}
	return t+1;
}
int find(int x)
{
	int l=1,r=m,mid;
	while(l<=r)
	{
		mid=l+r>>1;
		if (p[mid]==x) return mid;
		if (p[mid]<x) l=mid+1;
		else r=mid-1;
	}
}
void Pre()
{
	sort(p+1,p+cnt+1);
	for (int i=1;i<=cnt;i++)
		if (i==1||p[i]!=p[m]) p[++m]=p[i];
	for (;1<<M+1<m;M++);
	//M=20;
	for (int i=1;i<=n;i++)
	{
		a[i].l=find(a[i].l);
		a[i].r=find(a[i].r);
	}
	memset(f,0x3f,sizeof f);
	for (int i=1;i<=cnt;i++)
		f[a[i].l][0]=min(f[a[i].l][0],a[i].r);
	for (int i=m-1;i;i--)
		f[i][0]=min(f[i][0],f[i+1][0]);
	for (int i=1;(1<<i)<m;i++)
		for (int j=1;j<=m;j++)
			if (f[j][i-1]<m)
				f[j][i]=f[f[j][i-1]+1][i-1];
}
int Get(int x,int y)
{
	int t=0;
	for (int i=M;i>=0;i--)
		if (f[x][i]<=y) t+=1<<i,x=f[x][i]+1;
	return t;
}
int main()
{
//	freopen("1.in","r",stdin);
//	freopen("1.out","w",stdout);
	n=read();
	for (int i=1;i<=n;i++)
	{
		a[i].l=read();
		a[i].r=read();
		p[++cnt]=a[i].l;
		p[++cnt]=a[i].r;
	}
	Pre();
	int lych=Get(1,m),hz=0;
	printf("%d\n",lych);
	for (int i=1;i<=n;i++)
	{
		int L=qry(a[i].l-1),R=qry(a[i].r);
		if (L!=R) continue;
		L=L?kth(L)+1:1;
		R=R!=qry(m)?kth(R+1)-1:m;
		if (Get(L,a[i].l-1)+Get(a[i].r+1,R)+1==Get(L,R))
		{
			hz++;
			printf("%d",i);
			if (hz!=lych) putchar(' ');
			mdy(a[i].l,1);
			mdy(a[i].r+1,-1);
		}
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值