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\n",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\n",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\n",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\n",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\n",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
*/