codeforces 600E - Lomsat gelral(留坑)

nlog^2的平衡树启发式合并

我写挂了QAQ

T了

留坑待补。。

#include<cstdio>
#include<cstring>
#include<cstdlib>
const int N=1e5+7;
int a[N];
struct edgt
{
	int color,l,r,w,rnd;
}tr[N];
int cnt;
int stack[N+100],top;
inline void rturn(int &k)
{
	int t=tr[k].l;tr[k].l=tr[t].r;tr[t].r=k;
	k=t;
}
inline int read()
{
	int ans=0;char t=getchar();
	while(t<'0'||t>'9')	t=getchar();
	while(t>='0'&&t<='9')	ans=ans*10+t-'0',t=getchar();
	return ans;
}
inline void lturn(int &k)
{
	int t=tr[k].r;tr[k].r=tr[t].l;tr[t].l=k;
	k=t;
}
#define lson tr[k].l
#define rson tr[k].r
void insert(int &k,int num,int tot)
{
	if(!k){
		k=stack[top--];tr[k].color=num;tr[k].rnd=rand();tr[k].w=tot;
		return;
	}
	if(tr[k].color==num)	tr[k].w+=tot;
	else if(num<tr[k].color){
		insert(tr[k].l,num,tot);if(tr[lson].rnd<tr[k].rnd)	rturn(k);
	}	
	else{
		insert(tr[k].r,num,tot);if(tr[rson].rnd<tr[k].rnd)	lturn(k);
	}
}
void dfstree(int &ro,int k)
{
	if(!k)	return;
	insert(ro,tr[k].color,tr[k].w);	
	dfstree(ro,lson);
	dfstree(ro,rson);
	stack[++top]=k;
	tr[k].l=0,tr[k].r=0;
}	
int maxans;
long long sum; 
void dfs2(int k)
{
	if(!k) return ;
	if(tr[k].w>maxans)	maxans=tr[k].w,sum=tr[k].color;
	else if(tr[k].w==maxans)	sum+=tr[k].color;
	dfs2(lson);dfs2(rson);
}
struct node
{
	int to,next;
}e[N*2];
int first[N];
inline void insert_edgt(int u,int v)
{
	e[++cnt]=(node){v,first[u]};first[u]=cnt;
	e[++cnt]=(node){u,first[v]};first[v]=cnt;
}
int size[N];
long long ans[N];

int rt[N];
//void test(int k)
//{
//	if(!k)	return;
//	printf("%d %d\n",tr[k].color,tr[k].w);
//	test(lson);
//	test(rson);
//}
void dfs(int x,int fa)
{
	size[x]=1;
	int max=0,pos=-1;
	for(int k=first[x];k;k=e[k].next)
	if(e[k].to!=fa)
	{
		dfs(e[k].to,x);
		size[x]+=size[ e[k].to ];  
		if(size[e[k].to]>max)	max=size[e[k].to],pos=e[k].to;
	}
	if(pos!=-1)	rt[x]=rt[pos];
	 insert(rt[x],a[x],1);
//	 	 if(x==1)printf("std::\n"),test(rt[1]);;
	for(int k=first[x];k;k=e[k].next)
	if(e[k].to!=fa&&e[k].to!=pos)
	{
//		if(e[k].to==6&&x==1)	printf("aaaaaa %d %d\n",stack[top],top),test(rt[e[k].to]);
		dfstree(rt[x],rt[e[k].to]);
		rt[e[k].to]=0;
//		if(x==1)printf("std::%d\n",e[k].to),test(rt[x]);;
	}
	maxans=0,sum=0;
	dfs2(rt[x]);
	ans[x]=sum;
}
int main()
{
	srand(232);
	int n=read();
	for(int i=1;i<=n;i++)	a[i]=read();
	for(int i=1;i<=n+7;i++)	stack[i]=i;
	top=n+7;
	for(int i=1;i<n;i++){	
		int u,v;u=read(),v=read(),insert_edgt(u,v);
	}
	dfs(1,-1);	
	for(int i=1;i<=n;i++)	printf("%I64d ",ans[i]);
	return 0;
}	

dsu on tree:

参考资料:传送门 ||传送门

其实就是个大暴力!

但由于轻重链剖分启发式合并的运用,所以复杂度是nlogn的

我们先预处理出每个点的重儿子

然后开始dfs

先递归下去处理轻儿子(在轻儿子的dfs过程中会删除该子树的贡献)

再处理重儿子(在重儿子的dfs过程中不会删除该子树的贡献)

最后再重新dfs(另一个函数update)一遍把轻儿子的贡献加起来(注意此时不要加重儿子的,因为已经保留了)

记录该节点的ans

然后如果该点是它的父亲的轻儿子,那么我们再dfs一遍把贡献删去(update)

实测78 ms

#include<cstdio>
#include<cstring>
inline int read()
{
	int ans=0;char t=getchar();
	while(t<'0'||t>'9')	t=getchar();
	while(t>='0'&&t<='9')	ans=ans*10+t-'0',t=getchar();
	return ans;
}
const int N=1e5+7;
int a[N];
struct node
{
	int to,next;
}e[N*2];
int cnt,first[N];
inline void insert(int u,int v)
{
	e[++cnt]=(node){v,first[u]};first[u]=cnt;
	e[++cnt]=(node){u,first[v]};first[v]=cnt;
}
int size[N],heavy[N],tar[N];
void count(int x,int fa)
{
	size[x]=1;int max=0,pos=-1;
	for(int k=first[x];k;k=e[k].next)
	if(e[k].to!=fa)	
	{
		count(e[k].to,x),size[x]+=size[e[k].to];
		if(size[e[k].to]>max)	max=size[e[k].to],pos=e[k].to;
	}
	heavy[x]=pos;
	if(pos!=-1)tar[pos]=1;
}
long long ans[N];
int color[N],max;
long long sum;
void update(int x,int fa,int num)
{
	color[a[x]]+=num;
	if(num>0)
	{
		if(color[a[x]]>max)	max=color[a[x]],sum=a[x];
		else if(color[a[x]]==max)	sum+=a[x];
	}
	for(int k=first[x];k;k=e[k].next)
	if(e[k].to!=fa&&e[k].to!=heavy[x])	update(e[k].to,x,num);
}
void dfs(int x,int fa)
{
	for(int k=first[x];k;k=e[k].next)
	if(e[k].to!=fa&&e[k].to!=heavy[x])
	dfs(e[k].to,x);
	
	if(heavy[x]!=-1)		dfs(heavy[x],x);
	update(x,fa,1);
	heavy[x]=0;
	ans[x]=sum;
	
	if(!tar[x])	update(x,fa,-1),max=0;	
}
int main()
{
	int n=read(),u,v;
	for(int i=1;i<=n;i++)	a[i]=read();
	for(int i=1;i<n;i++)	u=read(),v=read(),insert(u,v);
	count(1,-1);
//	for(int i=1;i<=n;i++)	printf("aa%d %d\n",heavy[i],tar[i]);
	dfs(1,-1);
	for(int i=1;i<=n;i++)	printf("%lld ",ans[i]);
	return 0;
}




由于子树的dfs序是连续的

因此我们可以在dfs序上用莫队处理区间询问

如果要支持删除操作的话就得加数据结构,并且复杂度带一个log

所以我们考虑不带删除的莫队

对于左右端点在一个块的,我们直接for求出答案,单个询问复杂度sqrt(n)

对于左端点在块内,右端点在另一块的

因为右端点是递增的,我们直接和原来一样递增扫过去

同时左端点处从块的最右边暴力扫过去

然后再消除影响

左端点由于移动范围最多sqrt(n),n个询问

而右端点每个块最多O(n),sqrt(n)个块

所以总的复杂度还是nsqrt(n)的

实测124 ms

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
inline int read()
{
	int ans=0;char t=getchar();
	while(t<'0'||t>'9')	t=getchar();
	while(t>='0'&&t<='9')	ans=ans*10+t-'0',t=getchar();
	return ans;
}
const int N=1e5+7;
int a[N];
struct node
{
	int to,next;
}e[N*2];
struct edgt
{
	int l,r,order;
}f[N];
int pos[N];
bool cmp(edgt a,edgt b)
{
	if(pos[a.l]==pos[b.l])		return a.r<b.r;
	return pos[a.l]<pos[b.l];
}
int cnt,first[N];
inline void insert(int u,int v)	
{
	e[++cnt]=(node){v,first[u]};first[u]=cnt;
	e[++cnt]=(node){u,first[v]};first[v]=cnt;
}
int qu[N];
void dfs(int x,int fa)
{
	qu[++cnt]=x;f[x].l=cnt;f[x].order=x;
	for(int k=first[x];k;k=e[k].next)
	if(e[k].to!=fa)	dfs(e[k].to,x);
	f[x].r=cnt;
}
int n;
long long ans[N];
int sum[N],color[N],block_right[N];
void work()
{
	int max=0,premax=0;
	long long sum=0,presum=0;
	int r=block_right[1];
	for(int i=1,now=1;i<=n;i++)
	{
		if(f[i].l>block_right[now])	
		{
			memset(color,0,sizeof(color));
			max=0;sum=0;now++;
			while(pos[f[i].l]>block_right[now])	now++;
			r=block_right[now];
		}
//		printf("kkk::%d %d\n",i,now);
		if(f[i].r<=block_right[now])
		{
//			max=0,sum=0;
//			printf("aaaa%d\n",i);
			for(int kk=f[i].l;kk<=f[i].r;kk++)
			{
				color[ a [ qu[kk] ] ]++;
				if(color[ a [ qu[kk] ] ]>max)	max=color[ a[ qu[kk] ] ],sum=a[ qu[kk] ];
				else if(color[ a [ qu[kk] ] ]==max)	sum+=a[ qu[kk] ];
			}
			for(int kk=f[i].l;kk<=f[i].r;kk++)	color[ a [ qu[kk] ] ]--;
			ans[f[i].order]=sum;
			max=0;sum=0;
		}
		else
		{
			while(r<f[i].r)
			{
				r++;color[ a [ qu[r] ] ]++;
				if(color[ a [ qu[r] ] ]>max)	max=color[ a[ qu[r] ] ],sum=a[ qu[r] ];
				else if(color[ a [ qu[r] ] ]==max)	sum+=a[ qu[r] ];
			}	
			premax=max,presum=sum;
			int l=block_right[now]+1;
			while(l>f[i].l)
			{
				l--;color[ a[ qu[l] ] ]++;
				if(color[ a[ qu[l] ] ]>max)	max=color[ a[ qu[l] ] ],sum=a[ qu[l] ];
				else if(color[ a[ qu[l] ] ]==max)	sum+=a[ qu[l] ];
			}
			ans[f[i].order]=sum;
			max=premax,sum=presum;
			while(l<block_right[now]+1)		color[ a [ qu[l] ] ]--,l++;
		}
	}
}
int main()
{
	int u,v;
	n=read();
	for(int i=1;i<=n;i++)	a[i]=read();
	for(int i=1;i<n;i++)	u=read(),v=read(),insert(u,v);
	cnt=0;
	dfs(1,-1);
	int block=sqrt(n); 	
	for(int i=1;i<=n;i++)	pos[i]=(i-1)/block+1;
	for(int i=1;i<=n;i++)
	{
		block_right[i]=i*block;
		if(block_right[i]>=n)	{
			block_right[i]=n;break;
		}
	}
//	for(int i=1;i<=n;i++)	printf("%d ",qu[i]);
//	for(int i=1;i<=n;i++)	printf("std::%d %d %d\n",f[i].l,f[i].r,f[i].order);
//	printf("\n\n");
	std::sort(f+1,f+1+n,cmp);
//	for(int i=1;i<=n;i++)	printf("std::%d %d\n",f[i].l,f[i].r);
	work();
	for(int i=1;i<=n;i++)	printf("%lld ",ans[i]);
	return 0;
}


CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值