HDU-6602 杭电多校 线段树

14 篇文章 0 订阅
7 篇文章 0 订阅

题目链接:hdu 6602 博客 

思路源自博客:https://blog.nowcoder.net/n/bdacd5d54bdc491ea71ece169c860f39

感觉多校的题目还是比较重思维 而饿我的思维很菜 

这题我一开始想了个假算法,因为要个数>=k或者==0,一开始我想着用主席树,先二分一个长度len,再去枚举左端点,假设枚举到了左端点为L 那么右端点R=L+len-1  我们用主席树T[L-1]和T[R]的插值判断是不是这个区间所有的数出现次数为0或者>=k 但是这样复杂度是错的 >=k这个条件看似可以剪掉很多不必要的递归 其实不然 当k==1时 复杂度可能达到O(n)所以爆炸了 大家感兴趣可以看看错误代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 1e5+10;
int c[N*30],lson[N*30],rson[N*30],tot,T[N],t[N],a[N];
int n,g,k,R[N];
vector<int>v[N];
void update(int &rt,int las,int l,int r,int pos){
	rt=++tot,c[rt]=c[las]+1;
	int mid = l+r>>1;
	if(l==r) return;
	if(pos<=mid) 
	rson[rt]=rson[las],update(lson[rt],lson[las],l,mid,pos);
	else 
	lson[rt]=lson[las],update(rson[rt],rson[las],mid+1,r,pos);
}
bool query(int ql,int qr,int l,int r){
	if(l==r) return (c[qr]-c[ql])>=k;
	int mid = l+r>>1;
	int lct = c[lson[qr]]-c[lson[ql]],rct = c[rson[qr]]-c[rson[ql]];
	if((lct&&lct<k)||(rct&&rct<k)) return false;
	bool ret = true;
	if(lct>=k)  ret&=query(lson[ql],lson[qr],l,mid);
	if(rct>=k)  ret&=query(rson[ql],rson[qr],mid+1,r);
	return ret;
}
bool check(int len){
	for(int l = 1; l <= n-len+1; l++){
		int r = l+len-1;
		if(r<R[l]) continue;
		if(query(T[l-1],T[r],1,g)) return true;
	}
	return false;
}
int main(){
	while(~scanf("%d%d%d",&n,&g,&k)){
		tot=0;
		for(int i = 1; i <= n; i++){
			int x;
			scanf("%d",&x);
			a[i]=x;
			R[i]=n+1;
		}
		for(int i = 1; i <= g; i++) v[i].clear();
		if(k==1){
			printf("%d\n",n);
			continue;
		}
		for(int i = 1; i <= n; i++)
		update(T[i],T[i-1],1,g,a[i]);
		for(int i = n; i >= 1; i--){
			v[a[i]].push_back(i);
			int f = v[a[i]].size();
			if(f>=k){
				R[i]=*(v[a[i]].begin()+f-k);
			}
		}
		//for(int i = 1; i <= n; i++) printf("R[%d]=%d\n",i,R[i]);
		int l=k,r=n,ans;
		while(l<=r){
			int mid = l+r>>1;
			if(check(mid)) l=mid+1,ans=mid;
			else r=mid-1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

大家对错误解题方法肯定没什么兴趣  接下来是正确方法 :我们枚举每个位置,并把它作为右端点,用线段树动态的维护最左的满足条件的左端点

  1. 我们定义一个贡献  显然一个值出现0次或者大于等于k次才有贡献  先初始化  对于1到c的每个值x 我们把他们第一次出现的左边的贡献都加1 因为这些位置对于x这个值来说 都是出现0次x  对于x没出现过的情况 整个区间都要更新 为了方便 在存放位置的容器里放两个虚点 分别为0和n+1 中间是x出现的位置 这样可以保证这个初始化是正确的
  2. 我们从1到n开始遍历  遍历到 i 位置时,因为 i 是线段的右端点  对于 线段 j~i (1<=j<=i) a[ i ] 出现的次数肯定不为0  所以我们又得把 第上述第1点的贡献去掉 (具体更新时 不一定是从1开始 而是从上一次更新的位置)
  3. 如果 a[ i ] 在1到 i 中出现的次数大于等于k了  假设是now 那么我们需要找到一个位置 这个位置的值为a[i]  并且是 a[i]第now-k次出现 设这个位置为pos1 并且找到a[i] 第now-k+1次出现的位置 设为pos2   显然区间pos1~pos2的贡献+1 因为以这个区间内的位置为左端点和以 i 位置为右端点的线段上 a[ i ] 恰好出现了k次
  4. 由第1点,我们知道对于1到c的每个值x 我们都只初始化了x出现的最左端的那个位置的左边  然而对于两个相邻的x之间的数 他们也是有1点贡献的 为了不影响当前位置的询问 我们在询问完以后进行更新  例如 数列 1 2 3 5 6 2 9 对于第一个2和第二个2中间的3、5、6  显然对于3、5、6来说3作为左端点时 2出现的次数为0 所以当我们经过第一个2 我们要把区间3~5的贡献加1
  5.   询问的时候找最左的值为c的位置  我们维护区间的最大值可以轻松完成询问   关键是要理解贡献是啥
  6. 线段树的用法还是很活的 如何去建立这个模型 还需要多多做题
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
#define lson id<<1,l,mid
#define rson id<<1|1,mid+1,r
int lazy[N<<2],mx[N<<2],n,c,k,a[N],now[N];
vector<int>pos[N];
void pushup(int id){
	mx[id]=max(mx[id<<1],mx[id<<1|1]);
}
void pushdown(int id){
	if(lazy[id]){
		mx[id<<1]+=lazy[id];
		mx[id<<1|1]+=lazy[id];
		lazy[id<<1]+=lazy[id];
		lazy[id<<1|1]+=lazy[id];
		lazy[id]=0;
	}
}
void build(int id,int l,int r){
	lazy[id]=mx[id]=0;
	if(l==r) return;
	int mid = l+r>>1;
	build(lson);build(rson);
	pushup(id); 
}
void update(int id,int l,int r,int L,int R,int val){
	if(L>R) return;//注意这里必须要 因为部分区间的LR大小关系不满足 
	if(L<=l&&R>=r){
		lazy[id]+=val;
		mx[id]+=val;
		return;
	}
	int mid = l+r>>1;
	pushdown(id);
	if(L<=mid) update(lson,L,R,val);
	if(R>mid) update(rson,L,R,val);
	pushup(id);
}
int query(int id,int l,int r){
	if(l==r) return l;
	int ret = -1,mid = l+r>>1;
	pushdown(id);
	if(mx[id<<1]==c) ret=query(lson);
	else if(mx[id<<1|1]==c) ret=query(rson);
	return ret; 
}
int main(){
	while(~scanf("%d%d%d",&n,&c,&k)){
		for(int i = 1; i <= c; i++){
			pos[i].clear();
			pos[i].push_back(0);
		}
		for(int i = 1; i <= n; i++){
			scanf("%d",&a[i]);
			pos[a[i]].push_back(i);
		}
		build(1,1,n);
		for(int i = 1; i <= c; i++){
			pos[i].push_back(n+1);
			update(1,1,n,pos[i][0]+1,pos[i][1]-1,1);
			now[i]=0;
		}
		int ans = 0;
		for(int i = 1; i <= n; i++){
			int t = a[i];
			update(1,1,n,pos[t][now[t]]+1,pos[t][now[t]+1]-1,-1);
			now[t]++;
			if(now[t]>=k) update(1,1,n,pos[t][now[t]-k]+1,pos[t][now[t]-k+1],1);
			int ql = query(1,1,n);
			if(ql!=-1) ans=max(ans,i-ql+1);
			update(1,1,n,pos[t][now[t]]+1,pos[t][now[t]+1]-1,1);
		}
		printf("%d\n",ans);
	}
}

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值