zoukankan      html  css  js  c++  java
  • 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
    */
    
  • 相关阅读:
    Vue内敛模板
    vue自定义组件添加原生事件监听
    vue 组件开发 props 验证
    Vue中子组件数据跟着父组件改变和父组件数据跟着子组件改变的方法
    jQuery中outerWidth()方法
    CSS3-transition
    行内元素(例如)设置float之后才能用width调整宽度
    leetcode LRU Cache python
    opcache effect
    leetcode Same Tree python
  • 原文地址:https://www.cnblogs.com/cherrypill/p/13130192.html
Copyright © 2011-2022 走看看