您的位置:首页 > 理论基础 > 计算机网络

BZOJ4538:[Hnoi2016]网络 (整体二分+Lca+树状数组/线段树+路径交/树链剖分+Heap)

2017-08-28 16:49 369 查看
题目传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=4538

题目分析:这题网上好多人写树剖啊,都是把一条路径的区间搞出来之后取反更新,删除的话就套个Heap或者写线段树CDQ分治blablabla,时间复杂度O(nlog3(n)),好像因为树剖和Heap的常数特别小所以根本不虚。网上某大神用这种方法8200ms就过了,而我写了个O(nlog2(n))的线段树+路径交稳稳地T掉了,我写的另一个O(nlog2(n))的整体二分+树状数组用了11000msQAQ。可能是出题人为了卡暴力,特地将树构造成接近链的形状,结果正中树剖下怀之类的……

其实我一开始就想的是树剖,这应该是一个比较暴力的想法:用树剖将某条路径变成DFS序上不超过log(n)段区间,然后将这log(n)段区间取反,即取补集,可以证明取反后也只有log(n)段区间。将当前路径的重要程度值加进线段树的这些区间,线段树的每个节点再维护一棵Treap,以支持删除操作(当然,Treap换成Heap常数更小)。还可以写线段树CDQ分治。对于出现时间跨越了整个当前时间段[L,R]的边,直接加进去维护最大值,用完之后打一个懒惰标记清空线段树即可。

当然,也并不是没有时间复杂度更小的方法,我们可以用整体二分。假设二分答案的区间为[L,R],其中间值为mid,那就将所有重要程度值>mid的路径上面的点权值全部+1,并记录当前存在几条路径,假设查询的时候,某个点的权值=当前存在路径数,就说明所有重要程度值>mid的路径都包含该点,就要向左边递归,否则递归右边。路径加法单点查询可以转化为单点加法子树查询,用树状数组即可做到O(nlog2(n))。

CODE(整体二分+Lca+树状数组):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<stdio.h>
using namespace std;

const int maxn=100100;
const int maxl=20;

struct edge
{
int obj;
edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;

struct data
{
int Type,u,v,w,val;
} work[maxn<<2];

int bit[maxn];
int num=0;

int fa[maxn][maxl];
int dep[maxn];

int st[maxn];
int ed[maxn];
int Time=0;

int a[maxn<<2];
bool vis[maxn<<2];
bool Left[maxn<<2];
int b[maxn<<2];

int n,m;

void Add(int x,int y)
{
cur++;
e[cur].obj=y;
e[cur].Next=head[x];
head[x]=e+cur;
}

void Dfs(int node)
{
st[node]=++Time;
for (edge *p=head[node]; p; p=p->Next)
{
int son=p->obj;
if (son!=fa[node][0])
{
fa[son][0]=node;
dep[son]=dep[node]+1;
Dfs(son);
}
}
ed[node]=Time;
}

int Lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
for (int j=maxl-1; j>=0; j--)
if (dep[ fa[x][j] ]>=dep[y]) x=fa[x][j];
if (x==y) return x;
for (int j=maxl-1; j>=0; j--)
if (fa[x][j]!=fa[y][j])
x=fa[x][j],y=fa[y][j];
return fa[x][0];
}

void Update(int x,int v)
{
while (x<=n+1)
{
bit[x]+=v;
x+=(x&(-x));
}
}

int Sum(int x)
{
int sum=0;
while (x)
{
sum+=bit[x];
x-=(x&(-x));
}
return sum;
}

void Binary(int L,int R,int x,int y)
{
if (L==R)
{
for (int i=x; i<=y; i++)
{
int z=a[i],k=work[z].Type;
if (k==0)
{
num++;
Update(st[ work[z].u ]+1,1);
Update(st[ work[z].v ]+1,1);
Update(st[ work[z].w ]+1,-1);
Update(st[ fa[ work[z].w ][0] ]+1,-1);
}
if (k==1)
{
num--;
z=work[z].u;
Update(st[ work[z].u ]+1,-1);
Update(st[ work[z].v ]+1,-1);
Update(st[ work[z].w ]+1,1);
Update(st[ fa[ work[z].w ][0] ]+1,1);
}
if (k==2)
{
int temp=Sum(ed[ work[z].u ]+1)-Sum(st[ work[z].u ]);
if (temp<num) work[z].val=L;
else work[z].val=-1;
}
}
return;
}

int mid=(L+R)>>1;
for (int i=x; i<=y; i++)
{
Left[i]=true;
int z=a[i],k=work[z].Type;
if ( k==0 && work[z].val>mid )
{
Left[i]=false;
num++;
Update(st[ work[z].u ]+1,1);
Update(st[ work[z].v ]+1,1);
Update(st[ work[z].w ]+1,-1);
Update(st[ fa[ work[z].w ][0] ]+1,-1);
}
if ( k==1 && work[ work[z].u ].val>mid )
{
Left[i]=false;
num--;
z=work[z].u;
Update(st[ work[z].u ]+1,-1);
Update(st[ work[z].v ]+1,-1);
Update(st[ work[z].w ]+1,1);
Update(st[ fa[ work[z].w ][0] ]+1,1);
}
if (k==2)
{
int temp=Sum(ed[ work[z].u ]+1)-Sum(st[ work[z].u ]);
if (temp<num) Left[i]=false;
}
}

int cnt1=0;
for (int i=x; i<=y; i++) if (Left[i]) b[++cnt1]=a[i];
int cnt2=cnt1;
for (int i=x; i<=y; i++) if (!Left[i]) b[++cnt2]=a[i];
for (int i=x; i<=y; i++) Left[i]=false;
for (int i=1; i<=cnt2; i++) a[x+i-1]=b[i];

if (cnt1) Binary(L,mid,x,x+cnt1-1);
if (cnt1<cnt2) Binary(mid+1,R,x+cnt1,y);
}

int main()
{
freopen("4538.in","r",stdin);
freopen("4538.out","w",stdout);

scanf("%d%d",&n,&m);
for (int i=1; i<=n; i++) head[i]=NULL;
for (int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
Add(x,y);
Add(y,x);
}

Dfs(1);

for (int j=1; j<maxl; j++)
for (int i=1; i<=n; i++)
fa[i][j]=fa[ fa[i][j-1] ][j-1];

for (int i=1; i<=m; i++)
{
scanf("%d",&work[i].Type);
if (work[i].Type==0)
{
scanf("%d%d%d",&work[i].u,&work[i].v,&work[i].val);
work[i].w=Lca(work[i].u,work[i].v);
vis[i]=true;
}
else
{
scanf("%d",&work[i].u);
if (work[i].Type==1) vis[ work[i].u ]=false;
}
}

for (int i=1; i<=m; i++) if (vis[i])
work[++m].Type=1,work[m].u=i;
for (int i=1; i<=m; i++) a[i]=i;
Binary(1,1e9,1,m);

for (int i=1; i<=m; i++)
if (work[i].Type==2) printf("%d\n",work[i].val);
return 0;
}


还有一种做法,和整体二分的思想差不多,时间复杂度也一样(但常数根本不是一个级别,我估计下面的代码常数是2000左右)。我们将所有路径按重要程度值插入Treap里面,并且在Treap的每一个节点里维护它子树中所有路径的交集。查询一个点的时候就二叉查找,优先考虑右子树,但如果该点在右子树的所有路径的交集上,就要往左走。由于路径交是log(n)的,所以总时间是O(nlog2(n))的。用Treap的话就可以处理强制在线的情况,但由于平衡树是动态的,每一次左旋右旋都要重做一次路径交,代价很高,所以写离线加线段树会更好,然而还是各种TTTTT……

(为了安(qi)慰(pian)自己码code的时间没有白费,在此将TLE的代码也贴一贴,已通过对拍OwO)

CODE(线段树+路径交):

#include<iostream>
#include<string>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<stdio.h>
#include<algorithm>
using namespace std;

const int maxn=100100;
const int maxl=20;

struct Tnode
{
int U,V,cnt;
} tree[maxn<<3];
int bot[maxn<<1];

struct edge
{
int obj;
edge *Next;
} e[maxn<<1];
edge *head[maxn];
int cur=-1;

struct data
{
int Type,u,v,val,id,Time;
} work[maxn<<1];

struct kscla
{
int num;
kscla *Q_Next;
} Q[maxn];
int P[maxn];

int fa[maxn][maxl];
int dep[maxn];

int tp[6];
int sum;
int n,m;

int Get(int x)
{
int y=0;
while ( (1<<y)!=x ) y++;
return y;
}

void Add(int x,int y)
{
cur++;
e[cur].obj=y;
e[cur].Next=head[x];
head[x]=e+cur;
}

void Dfs(int node)
{
for (edge *p=head[node]; p; p=p->Next)
{
int son=p->obj;
if (son!=fa[node][0])
{
fa[son][0]=node;
dep[son]=dep[node]+1;
Dfs(son);
}
}
}

bool Comp1(data x,data y)
{
if ( (!x.Type) && y.Type ) return true;
if ( x.Type && (!y.Type) ) return false;
return (x.val<y.val);
}

bool Comp2(data x,data y)
{
return (x.Time<y.Time);
}

void Swap(int &x,int &y)
{
int z=x;
x=y;
y=z;
}

int Lca(int x,int y)
{
if (dep[x]<dep[y]) Swap(x,y);
for (kscla *p=Q+(dep[x]-dep[y]); p!=Q; p=p->Q_Next)
x=fa[x][p->num];
if (x==y) return x;
for (int j=P[ dep[x] ]; j>=0; j--)
if (fa[x][j]!=fa[y][j])
x=fa[x][j],y=fa[y][j];
return fa[x][0];
}

bool Check(int a,int x,int y,int z)
{
int b=Lca(a,x);
int c=Lca(a,y);
if (b!=z) Swap(b,c);
return ( (b==z) && (c==a) );
}

bool Judge(int x,int y,int a,int b,int lca)
{
if ( !Check(x,a,b,lca) ) return false;
return Check(y,a,b,lca);
}

void Work(int x,int y,int z,int w,int &a,int &b)
{
if ( (!x) || (!z) )
{
a=b=0;
return;
}

tp[0]=Lca(x,y);
tp[1]=Lca(x,z);
tp[2]=Lca(x,w);
tp[3]=Lca(y,z);
tp[4]=Lca(y,w);
tp[5]=Lca(z,w);

a=b=0;
int len=-1;
for (int i=0; i<5; i++)
for (int j=i+1; j<6; j++)
{
int k=Lca(tp[i],tp[j]);
int l=dep[ tp[i] ]+dep[ tp[j] ]-(dep[k]<<1);
if (l<=len) continue;
if ( !Judge(tp[i],tp[j],x,y,tp[0]) ) continue;
if ( !Judge(tp[i],tp[j],z,w,tp[5]) ) continue;
a=tp[i],b=tp[j],len=l;
}
}

void Update(int root,int L,int R,int x,int nu,int nv)
{
if ( x<L || R<x ) return;
if ( x==L && R==x )
{
tree[root].U=nu;
tree[root].V=nv;
if (nu) tree[root].cnt++;
else tree[root].cnt--;
return;
}

int mid=(L+R)>>1;
int Left=root<<1;
int Right=Left|1;

Update(Left,L,mid,x,nu,nv);
Update(Right,mid+1,R,x,nu,nv);

if (!tree[Left].cnt)
{
tree[root]=tree[Right];
return;
}
if (!tree[Right].cnt)
{
tree[root]=tree[Left];
return;
}
tree[root].cnt=tree[Left].cnt+tree[Right].cnt;
Work(tree[Left].U,tree[Left].V,tree[Right].U,tree[Right].V,tree[root].U,tree[root].V);
}

int Query(int root,int L,int R,int x)
{
if (L==R)
{
if (L!=1) return work[ bot[L] ].val;
if (!tree[root].cnt) return -1;
int W=Lca(tree[root].U,tree[root].V);
if ( Check(x,tree[root].U,tree[root].V,W) ) return -1;
return work[ bot[1] ].val;
}

int mid=(L+R)>>1;
int Left=root<<1;
int Right=Left|1;

if (!tree[Right].cnt) return Query(Left,L,mid,x);
if (!tree[Right].U) return Query(Right,mid+1,R,x);
int W=Lca(tree[Right].U,tree[Right].V);
if ( Check(x,tree[Right].U,tree[Right].V,W) ) return Query(Left,L,mid,x);
return Query(Right,mid+1,R,x);
}

int main()
{
freopen("4538.in","r",stdin);
freopen("4538.out","w",stdout);

P[0]=-1;
for (int i=1; i<maxn; i++)
{
P[i]=P[i>>1]+1;
int x=i&(-i);
Q[i].num=Get(x);
Q[i].Q_Next=Q+(i-x);
}

scanf("%d%d",&n,&m);
for (int i=1; i<=n; i++) head[i]=NULL;
for (int i=1; i<n; i++)
{
int x,y;
scanf("%d%d",&x,&y);
Add(x,y);
Add(y,x);
}

Dfs(1);

for (int j=1; j<maxl; j++)
for (int i=1; i<=n; i++)
fa[i][j]=fa[ fa[i][j-1] ][j-1];

for (int i=1; i<=m; i++)
{
scanf("%d",&work[i].Type);
if (!work[i].Type) scanf("%d%d%d",&work[i].u,&work[i].v,&work[i].val);
else scanf("%d",&work[i].u);
work[i].Time=i;
}
sort(work+1,work+m+1,Comp1);
for (sum=1; sum<=m; sum++)
if (!work[sum].Type) work[sum].id=sum,bot[sum]=work[sum].Time;
else break;
sum--;
sort(work+1,work+m+1,Comp2);

for (int i=1; i<=m; i++)
{
if (!work[i].Type) Update(1,1,sum,work[i].id,work[i].u,work[i].v);
if (work[i].Type==1) Update(1,1,sum,work[ work[i].u ].id,0,0);
if (work[i].Type==2) printf("%d\n", Query(1,1,sum,work[i].u) );
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: