The 2017 ACM-ICPC Asia Beijing Regional Contest C题

Posted cherrypill

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了The 2017 ACM-ICPC Asia Beijing Regional Contest C题相关的知识,希望对你有一定的参考价值。

就是个回滚莫队和带权可删减并查集板子

LCT?雾

这板子还没整理过,就顺手写下吧....

可删除并查集

其实实质和原本并查集差不多就加了一个虚点的概念

为什么要增加虚点呢?

这就是删除操作的本质

(这里用ha[i]=cnt 代表i节点对应的虚点为cnt)

删除,首先把所有与这个点 i(虚点cnt)有关的东西全部删掉,然后这个ha[i]=++cnt,就相当于把i移除了

因为所有与原来cnt有关的全部被删掉了,以后也不会再用到它,虽然‘物理层面‘上是还在原来的集合中,但是‘精神层面‘已经消失了

例题 Almost Union-Find

裸模版...

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+6;
int n,m,a,b,x,fa[N],ha[N],cnt;
long long w[N],sum[N];
int find(int x)
{
	//if(x==fa[x])
	//	return x;
	//return fa[x]=find(fa[x]);
    // 最后return 应该是return find(fa[x]) 不然有些题是过不了的(还是说本来就是错误的)
    return fa[x]==x?fa[x]:find(fa[x]);//等价该过的写法(我居然三年都没发现问题!!!)
}
void merge(int a,int b)
{
	int r1=find(ha[a]),r2=find(fa[ha[b]]);
	if(r1!=r2)
	{
		fa[r1]=r2;
		w[r2]+=w[r1];
		sum[r2]+=sum[r1];
	}
}
void move(int a,int b)
{
	// 把a移动到b集合 
	int r1=find(ha[a]),r2=find(ha[b]);
	if(r1!=r2)
	{
		w[r1]-=a;
		sum[r1]-=1;
		w[r2]+=a;
		sum[r2]+=1;
		ha[a]=++cnt; //先把a移出再定向father
		// 虽然之前的ha[a]还在集合里,但是之后再也不会用到,并且权值该减掉的全部减掉了所以相当于删除操作 
		fa[ha[a]]=r2;
	}
} 
int main()
{
	while(~scanf("%d %d",&n,&m))
	{
		cnt=0;
		memset(w,0,sizeof(w));
		memset(fa,0,sizeof(fa));
		memset(sum,0,sizeof(sum));
		memset(ha,0,sizeof(ha)); 
		for(int i=1;i<=n;i++)
		{
			fa[i]=i;
			w[i]=i;// 初始化每个集合的权值 
			ha[i]=++cnt;// i对应的虚点 
			sum[i]=1;
		}
		for(int i=1;i<=m;i++)
		{
			scanf("%d",&x);
			if(x==1)
			{
				scanf("%d %d",&a,&b);
				merge(a,b);
			}
			if(x==2)
			{
				scanf("%d %d",&a,&b);
				move(a,b);
			}
			if(x==3)
			{
				scanf("%d",&a);
				int r=find(ha[a]);
				printf("%lld %lld
",sum[r],w[r]);
			}
		}
	}
	return 0;
} 

回滚莫队

我个人觉得莫队就是分块的一种思想一种优化?

普通莫队最重要的辨别点在于可以 O(1) 的增加或删除节点,而回滚莫队的关键点在于只能 O(1)的增加或者删除节点,增加或删除只能二者选其一。也就是某一方面比较困难

如果删除操作简单,可以右端点从大到小排序

如果增加操作简单,可以右端点从小到大排序

当换块的时候左端点是递增的,所以左端点的答案ans1可以继承

当belong[q[i].l] 相同时,右端点时递增|递减的,不需要回滚右端点(也就是说右端点答案ans2可以继承),只需要回滚左端点

例题 Maximize Mex

显然每次删除点,可以判断当前点是否比答案小,如果小就可以顺便更新答案

所以这是一个删除简单,增加困难的题,选择右端点从大到小排序

为什么我的莫队每次都被卡一次啊

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=2e5+5;
int n,m,l[N],r[N],a[N],belong[N],ans[N],block,len;
int t[N],ans1,ans2,posl,posr;
struct node
{
	int l,r,mp;
}q[N];
inline int read()
{
	int x=0; bool flag=1; char ch=getchar();
	while(ch<‘0‘||ch>‘9‘) 
	{
		if(ch==‘-‘) 
			flag=0; 
		ch=getchar();
	}
	while(ch>=‘0‘&&ch<=‘9‘) 
	{
		x=(x<<1)+(x<<3)+ch-‘0‘;
		ch=getchar();
	}
	if(flag) 
		return x;
	return -x;
}
bool cmp(node x,node y)
{
	if(belong[x.l]==belong[y.l])
		return x.r>y.r;
	return belong[x.l]<belong[y.l];
}

void built()
{
	int len=sqrt(n);
	block=(n-1)/len+1;
	for(int i=1;i<=n;i++)
		belong[i]=(i-1)/len+1;
	for(int i=1;i<=block;i++)
	{
		r[i]=len*i;
		l[i]=(i-1)*len+1; 
	}
	r[block]=n;
}
void add(int pos)
{
	t[a[pos]]++;
}
int del(int pos,int ans)
{
	t[a[pos]]--;
	if(t[a[pos]]==0)
		ans=min(ans,a[pos]);	
	return ans;
}
int bf(int posl,int posr)
{
	int tempcnt[N],tempans=0;
	memset(tempcnt,0,sizeof(tempcnt));
	for(int i=posl;i<=posr;i++)
	{
		tempcnt[a[i]]++;
	}
	while(tempcnt[tempans])
		tempans++;
	return tempans;
}
int main()
{
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)
		a[i]=read();
	
	built();
	for(int i=1;i<=m;i++)
	{
		q[i].l=read();
		q[i].r=read();
		q[i].mp=i;
	}
	sort(q+1,q+1+m,cmp);
	
	for(int i=l[belong[q[1].l]];i<=n;i++)
		t[a[i]]++;
	ans1=0;
	while(t[ans1])
		ans1++;//初始答案 
	posl=l[belong[q[1].l]];//这里也可以变成别的,不过前面就要改 
	posr=n;
	for(int i=1;i<=m;i++)
	{		
		if(belong[q[i].l]!=belong[q[i-1].l])//左右端点移动到初始位置且要消除影响 
		{
			while(posr<n)
				add(++posr);
			while(posl<l[belong[q[i].l]])
				ans1=del(posl++,ans1);
			ans2=ans1;//左端点的答案			 
		}
		if(belong[q[i].l]==belong[q[i].r])
		{
			ans[q[i].mp]=bf(q[i].l,q[i].r);
			continue;
		}
		while(posr>q[i].r)//一定是r在前面,因为回滚的是左边界,要保存右边界的ans 
			ans2=del(posr--,ans2);//删除因为该点也要删所以时 pos-- 
		int tempans=ans2;
		while(posl<q[i].l)
			ans2=del(posl++,ans2);	
		//回滚 
		while(posl>l[belong[q[i].l]])
			add(--posl);
		ans[q[i].mp]=ans2; //回滚左端点的ans2 
		ans2=tempans;
	}
	for(int i=1;i<=m;i++)
		printf("%d
",ans[i]);
    return 0;
}

例题 回滚莫队&不删除莫队

显然这是一个增加简单,还能顺便更新答案,所以选择第二关键字从小到大排序

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5,inf=0x3f3f3f3f;
int n,m,len,block,posl,posr,a[N],temp[N],belong[N],l[N],r[N],ans[N];
int xx,first[N],last[N],ans1,ans2,tempfirst[N],templast[N];
struct node
{
	int l,r,mp;
}q[N];
vector <int> fr,fl;
void built()
{
	memset(first,inf,sizeof(first));
	len=sqrt(n);
	block=(n-1)/len+1;
	for(int i=1;i<=n;i++)
	{
		belong[i]=(i-1)/len+1;//i属于哪个块 
	}
	for(int i=1;i<=block;i++)
	{
		l[i]=(i-1)*len+1;//第i块的左右边界 
		r[i]=i*len;
	}
	r[block]=n;
	sort(temp+1,temp+n+1);
	xx=unique(temp+1,temp+n+1)-temp-1;//有多少个不重复的数 
	for(int i=1;i<=n;i++)
	{
		a[i]=lower_bound(temp+1,temp+xx+1,a[i])-temp;//a[i]最小是1 
	}
}
bool cmp(node x,node y)
{
	if(belong[x.l]==belong[y.l])
		return x.r<y.r;
	else
		return belong[x.l]<belong[y.l];
} 
int add(int pos,int ans)
{
	first[a[pos]]=min(first[a[pos]],pos);	
	last[a[pos]]=max(last[a[pos]],pos);
	ans=max(ans,last[a[pos]]-first[a[pos]]);
	return ans;
}
int main()
{
//	freopen("P5906_9.in","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		temp[i]=a[i];
	}
	built(); 
	scanf("%d",&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d %d",&q[i].l,&q[i].r);
		q[i].mp=i;
	}
	sort(q+1,q+m+1,cmp);
	memset(first,inf,sizeof(first));
	ans1=0;
	posl=r[belong[q[1].l]]+1;
	posr=posl-1;
	for(int i=1;i<=m;i++)
	{
		if(belong[q[i].l]!=belong[q[i-1].l])//先判定是否在一个块里,不是就要初始化
		{
			for(int j=1;j<=xx;j++)
			{
				first[j]=inf;
				last[j]=0;
			}
			posl=r[belong[q[i].l]];
			posr=posl;
			ans2=ans1=0;
		}
		// 因为右端点是递增的所以贡献可以继承,
		//但是左端点在一个块里是乱序的所以每次都要去掉左端点的贡献 
		while(posr<q[i].r)
			ans2=add(++posr,ans2);
		int tempans=ans2; // 记录加右端点的贡献,保存该状态 
		for(int j=q[i].l;j<=r[belong[q[i].l]];j++)
		{
			tempfirst[a[j]]=first[a[j]];
			templast[a[j]]=last[a[j]];
		} 
		for(posl=min(q[i].r,r[belong[q[i].l]]);posl>=q[i].l;posl--)
			ans2=add(posl,ans2);
			
		ans[q[i].mp]=ans2; 
		ans2=tempans;
		for(int j=q[i].l;j<=r[belong[q[i].l]];j++)// 只要是增加简单(posr=posl=r[..])就可以这样省暴力
		{
			first[a[j]]=tempfirst[a[j]];
			last[a[j]]=templast[a[j]];
		}
		posl=r[belong[q[i].l]];
	}
	for(int i=1;i<=m;i++)
		printf("%d
",ans[i]);
	return 0;
} 

例题 歴史の研究

感觉最后打出来和模板完全不一样...

#include<cstdio>
#include<cstring>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
const int N=1e5+7;
long long n,q,ma,num,blo,x,y,l[N],r[N],be[N],a[N],temp[N],cnt[N],g[1000][1000],f[1000][N];
//f[i][j] 为前i块j出现的次数 g[i][j]为i到j的答案 
void built()
{
	num=sqrt(n);
	blo=(n-1)/num+1;
	for(int i=1;i<=n;i++)
	{
		be[i]=(i-1)/num+1;
	}
	for(int i=1;i<=blo;i++)
	{
		l[i]=(i-1)*num+1;
		r[i]=i*num;
	}
	r[blo]=n;
	sort(temp+1,temp+n+1);
	int xx=unique(temp+1,temp+n+1)-temp-1;
	for(int i=1;i<=n;i++)//离散化
	{
		a[i]=lower_bound(temp+1,temp+xx+1,a[i])-temp;	//a现在只是离散后的位置,temp里才是权值 
	}
	for(int i=1;i<=blo;i++)
	{
		for(int j=l[i];j<=r[i];j++)
			cnt[a[j]]++;
		for(int j=1;j<=n;j++)
			f[i][a[j]]=cnt[a[j]];
	}
	for(int i=1;i<=blo;i++)
	{
		ma=0;memset(cnt,0,sizeof(cnt));
		for(int j=i;j<=blo;j++)
		{
			for(int k=l[j];k<=r[j];k++)
			{
				cnt[a[k]]++;
				ma=max(ma,temp[a[k]]*cnt[a[k]]);
			}
			g[i][j]=max(ma,g[i][j]);
		}
	}
}
long long ask(long long x,long long y)
{
	ma=0;
	if(be[x]==be[y])
	{
		for(int i=x;i<=y;i++)
		{
			cnt[a[i]]++;
			ma=max(ma,cnt[a[i]]*temp[a[i]]);	
		}
		for(int i=x;i<=y;i++)
			cnt[a[i]]--;
	}
	else
	{
		if(be[x]>be[y])
			swap(x,y);
		ma=g[be[x]+1][be[y]-1];
		for(int i=x;i<=r[be[x]];i++)//x块的右端点为x+1块的左端点-1 
		{
			cnt[a[i]]++;
			ma=max(ma,(cnt[a[i]]+(f[be[y]-1][a[i]]-f[be[x]][a[i]]))*temp[a[i]]);//前缀和要注意 
		}
		for(int i=l[be[y]];i<=y;i++)
		{
			cnt[a[i]]++;
			ma=max(ma,(cnt[a[i]]+(f[be[y]-1][a[i]]-f[be[x]][a[i]]))*temp[a[i]]);
		}	
		for(int i=x;i<=r[be[x]];i++)
			cnt[a[i]]--;
		for(int i=l[be[y]];i<=y;i++)
			cnt[a[i]]--;
	}
	return ma;
}
int main()
{
	scanf("%lld %lld",&n,&q);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		temp[i]=a[i];
	}
	built();
	memset(cnt,0,sizeof(cnt));
	for(int i=1;i<=q;i++)
	{
		scanf("%lld %lld",&x,&y);
		printf("%lld
",ask(x,y));
	}
	return 0;
}
/*
5 5
9 8 7 8 9
1 2
3 4
4 4
1 4
2 4
*/

The 2017 ACM-ICPC Asia Beijing Regional Contest C题

既然前置知识都会了,正片开始

C Graph

每次询问给出l,r 代表[l,r] 区间内的点是‘存在’的(并查集维护一下联通性)

显然,根据这询问可以离线所以可以使用莫队来进行维护,其实答案就Ci,2 i是第i次询问的联通点的个数

假设两个连通块cnt 分别为 a,b 则合并后ans+=a*b (非常简单的结论)

因为并查集加点是基操,先加在删比较好维护所以 第二关键字从小到大排序(还能少个bf)

这里删点因为是加点的逆序操作所以可以用stack维护(不是queue)但是更普通的删除并查集还是得用虚点的方法来搞

看网上分块各种秀, 按度分块、按边排序两次分块?普通按点的顺序分块不也能过吗....
不过就是现在才发现find函数原来我一直都写错了(应该算是收获?)导致我成功刷屏了
技术图片

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m,T,Q,x,y,l[N],r[N],a[N],belong[N],ans[N],block,len;
int res,posl,posr;
int fa[N],cnt[N];//可删并查集也可以用stk来实现 不是queue(因为删点先后顺序有讲究) 
struct node
{
	int l,r,mp;
}q[N];
vector <int> f[N];
stack <int> stk;
int find(int x)
{
//  万万没想到find写错了,白wa怎么多次(为什么以前都没问题...) 
	if(x==fa[x]) 
		return x;
	return find(fa[x]);
//	return fa[x]=find(fa[x]);这样居然是错的 
}
bool cmp(node x,node y)
{
	if(belong[x.l]==belong[y.l])
		return x.r<y.r;
	return belong[x.l]<belong[y.l];
}
void built()
{
	int len=sqrt(n);
	block=(n-1)/len+1;
	for(int i=1;i<=n;i++)
		belong[i]=(i-1)/len+1;
	for(int i=1;i<=block;i++)
		r[i]=len*i;
	r[block]=n;
}
void merge(int x,int y,int flag)
{
	int fx = find(x),fy=find(y);
	if(fx==fy)
		return ;
	if(cnt[fx]>cnt[fy]) 
	{
		swap(x,y); 
		swap(fx,fy);
	}
	res+=cnt[fx]*cnt[fy];
	fa[fx]=fy;
	cnt[fy]+=cnt[fx];
	if(!flag) 
		stk.push(fx);
}
int main()
{
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d %d %d",&n,&m,&Q);
		for(int i=1;i<=n;i++)
			f[i].clear();
		while(!stk.empty())
			stk.pop();
		for(int i=1;i<=m;i++)
		{
			scanf("%d %d",&x,&y);
			f[x].push_back(y);
			f[y].push_back(x);
		}
		built();
		for(int i=1;i<=Q;i++)
		{
			scanf("%d %d",&q[i].l,&q[i].r);
			q[i].mp=i;
		}
		sort(q+1,q+1+Q,cmp);
		int posl,posr;
		for(int i=1;i<=Q;i++)
		{		
			if(belong[q[i].l]!=belong[q[i-1].l])
			{
				posr=posl=r[belong[q[i].l]];
				for(int j=1;j<=n;j++)
				{
					fa[j]=j;
					cnt[j]=1;
				}
				res=0;//左端点的答案			 
			}
			while(posr<q[i].r)
			{	
				posr++;
				for(auto j:f[posr])
				{
					if(j>r[belong[q[i].l]]&&j<=q[i].r)
						merge(posr,j,1);
				}
			}
			for(posl=min(q[i].r,r[belong[q[i].l]]);posl>=q[i].l;posl--)
			{
				for(auto j:f[posl])
				{			
					if(j>=q[i].l&&j<=q[i].r)
						merge(posl,j,0);
				}
			}
			ans[q[i].mp]=res;
			while(!stk.empty())
			{
				int u=stk.top();
				stk.pop();
				cnt[fa[u]]-=cnt[u];
				res-=cnt[fa[u]]*cnt[u];
				fa[u]=u;			
			}
			posl=r[belong[q[i].l]];
		}
		for(int i=1;i<=Q;i++)
			printf("%d
",ans[i]);
	}
    return 0;
}
/*
1
6 6 4
1 2
2 3
2 6
1 5
2 4
4 5
1 4
3 6
2 6
3 4
*/


以上是关于The 2017 ACM-ICPC Asia Beijing Regional Contest C题的主要内容,如果未能解决你的问题,请参考以下文章

The 2017 ACM-ICPC Asia East Continent League Final记录

The 2017 ACM-ICPC Asia Beijing Regional Contest C题

The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online

The 2018 ACM-ICPC Asia Beijing Regional Contest

The 2018 ACM-ICPC Asia Qingdao Regional Contest

The 2018 ACM-ICPC Asia Qingdao Regional Contest, Online J Press the Button