上交的寝室没网实在是...据说acm时调试只有5分钟,基本上不用gdb,这点真是硬伤了,一般来说有难度的题目我要么不调试,要么调比较久,看来有必要锻炼静态调试。
题意:一开始有n个点,每次可以选择两个点连边,保证每次连边后是一个森林,对于每棵树来说,要选择一个点,树中其他点到它的距离和为此树的权值,要求每次操作后,整个森林中的树的权值和最小。
显然每棵树中要求的点是重心,因为考虑不是重心的时候,向重心移动,必定造成ans-n+2*size,如果不是重心,移动后ans会变小。
如果只是维护重心,其实简单一点,两颗树i,j相连(均以重心x,y为根),新重心必定在两重心的连线上,假设以y为根,对于x-y上的点z,如果它是重心,那么z子树以外的节点个数必定是<n/2的,也就是说z子树是>=n/2(边界有点麻烦,取不取等于自己考虑吧),当然只有这个条件不够,但是如果我们能找到最后一个子树大于n/2的,答案必定在附近的节点(汗),如此看来,我只要能维护子树大小,就可以直接找出重心了。
对于子树大小的维护,因为涉及到旋转换根这种操作,所以不能直接统计,但是我们对于每个节点统计虚边子树大小的话,这个权值是不会随着splay的形态改变而改变的,因此与splay子树大小一起,还是可以求出子树大小的。这样一来在splay上走一下应该可以在o(nlogn)的时间解决重心的维护问题。
可是题目还要求每个点到重心的距离和,这种东西与子树大小一样,是不能直接在splay上统计的,同样要用到虚边信息,麻烦的是,这个东西比子树大小的限制更多,如果不是在特殊的节点还不能随便求出,因此,考虑到两棵树相连顶多移动其中一棵树大小次,这里用启发式来做一下,每次再用动态树维护,当然,为了应付换根,还要处理一下,op记到头的答案,ed记到尾的答案,算法复杂度是o(nlognlogn)的,虽然题解中是o(nlogn)的,但是代码长至8kb,最后4个点没过,已经过了的点又比我跑得慢。
顺便提一下,用启发式就没有了二分时候的等于号取不取的麻烦问题...
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
const int oo=1073741819,maxn=50000;
using namespace std;
int l[maxn],r[maxn],rt[maxn],c[maxn][2],size[maxn],b[maxn],g[maxn],f[maxn],op[maxn],ed[maxn],sum[maxn],tot[maxn],sw[maxn],t[maxn];
int e,n,m,ans;
int find(int x) {if (b[x]!=x) b[x]=find(b[x]);return b[x];}
void swap(int x)
{
e=l[x],l[x]=r[x],r[x]=e;
e=op[x],op[x]=ed[x],ed[x]=e;
e=c[x][0],c[x][0]=c[x][1],c[x][1]=e;
}
void updata(int x)
{
sum[x]=sum[l[x]]+sum[r[x]]+f[x];
size[x]=size[l[x]]+size[r[x]]+1;
ed[x]=ed[l[x]]+(size[l[x]]+sum[l[x]])*(1+size[r[x]])+size[r[x]]*(1+f[x])+tot[x]+ed[r[x]];
op[x]=op[l[x]]+size[l[x]]*(1+f[x])+tot[x]+(1+size[l[x]])*(size[r[x]]+sum[r[x]])+op[r[x]];
c[x][0]=(c[l[x]][0]) ? c[l[x]][0] : x;
c[x][1]=(c[r[x]][1]) ? c[r[x]][1] : x;
}
void pushdown(int x)
{
if (sw[x]) {
swap(l[x]),swap(r[x]);
sw[l[x]]^=1,sw[r[x]]^=1,sw[x]=0;
}
updata(x);
}
void left(int x)
{
int y=rt[x],z=rt[y];
r[y]=l[x],rt[l[x]]=y;updata(y);
l[x]=y,rt[y]=x;
if (l[z]==y) l[z]=x;else if (r[z]==y) r[z]=x;rt[x]=z;
}
void right(int x)
{
int y=rt[x],z=rt[y];
l[y]=r[x],rt[r[x]]=y,updata(y);
r[x]=y,rt[y]=x;
if (l[z]==y) l[z]=x;else if (r[z]==y) r[z]=x;rt[x]=z;
}
void splay(int x)
{
pushdown(x);
for (int y,z;(l[rt[x]]==x) || (r[rt[x]]==x);) {
y=rt[x],z=rt[y];
if ((l[z]==y) || (r[z]==y)) pushdown(z);pushdown(y);pushdown(x);
if (l[y]==x) {
if (l[z]==y) right(y);right(x);
}
else {
if (r[z]==y) left(y);left(x);
}
}
updata(x);
}
void access(int x)
{
splay(x);
f[x]+=sum[l[x]]+size[l[x]],tot[x]+=ed[l[x]]+sum[l[x]]+size[l[x]],l[x]=0;
updata(x);
for (int y;rt[x];) {
y=rt[x];
splay(y);
f[y]-=sum[x]+size[x]-sum[l[y]]-size[l[y]],tot[y]-=ed[x]+sum[x]+size[x]-ed[l[y]]-sum[l[y]]-size[l[y]],l[y]=x;
updata(y);
splay(x);
}
}
int ask(int x,int &root,int n,int oroot)
{
int ans=oo;root=0;
for (;n;n--) {
access(x),swap(x),sw[x]^=1;
int tmp=ed[x];
if (tmp<ans) ans=tmp,root=x;
access(oroot),splay(x);
if (!c[l[x]][1]) break;
x=c[l[x]][1];
}
return ans;
}
int main()
{
freopen("village.in","r",stdin);
freopen("village.out","w",stdout);
char ch;
int x,y;
scanf("%d%d\n",&n,&m);
ans=0;
for (int i=1;i<=n;i++) b[i]=i,g[i]=1,t[i]=0,size[i]=1,c[i][0]=c[i][1]=i;
for (int i=1;i<=m;i++) {
scanf("%c",&ch);
if (i==7)
int k=1;
if ('Q'==ch) {
scanf("\n");
printf("%d\n",ans);
}
else {
scanf("%d%d\n",&x,&y);
int ll=find(x),rr=find(y);
if (g[ll]>g[rr]) e=ll,ll=rr,rr=e,e=x,x=y,y=e;
ans-=t[ll]+t[rr];
b[ll]=rr,g[rr]+=g[ll];
int tmp=g[ll];
access(x);
for (ll=x;r[ll];ll=r[ll]) pushdown(ll);
swap(x),sw[x]^=1;
access(y);
rt[x]=y,f[y]+=size[x]+sum[x],tot[y]+=ed[x]+sum[x]+size[x],updata(y);
int root,tr;
for (tr=y;r[tr];tr=r[tr]) pushdown(tr);
t[rr]=ask(tr,root,tmp+1,ll);
access(root),swap(root),sw[root]^=1;
ans+=t[rr];
// printf("%d\n",ans);
}
}
return 0;
}