牛客网 小睿睿的方案 解题报告
小睿睿的方案
链接:
https://ac.nowcoder.com/acm/contest/371/C
来源:牛客网
题目描述
小睿睿虽然已经是人生赢家了,但当他看见学校里其他人秀恩爱时仍旧会十分不满。他的学校有\(n\)个教室,教室间有\(n-1\)条路径且任意教室互相联通,每天他和小熙熙会选择在两个不同的教室\((i,j)\)间的简单路径上秀恩爱。学校里一共有\(m\)对情侣,每对情侣中的两个人分别在教室\(A,B(A\not=B)\),如果他们秀恩爱时经过的教室里存在任意一对情侣,这种秀恩爱的方案就是不合法的,求合法的无序点对数
无序点对:\((i,j)\)与\((j,i)\)视作同一对
输入描述:
第\(1\)行,\(2\)个整数\(n,m\)
第\(2\)至\(n\)行,每行两个整数\(a,b\),表示\(a,b\)间存在一条边
之后\(m\)行,每行两个整数\(a,b\),表示有一对情侣分别在教室\(a\)和教室\(b\)
输出描述:
一行一个整数,表示答案
说明
对于\(30\%\)的数据,\(n,m\le 100\)
另有\(20\%\)的数据,\(n,m\le 100000\)且图的形态为一条链
对于\(100\%\)的数据,\(n,m\le 100000\)
输入数据较大,建议使用较快的读入方式
看了前两个题,发现很水,就来写这个题,然后最后5分钟的时候写出来了,结果数组还开小了,爆的只有80分...前面两个题还没做qaq
统计不合法的点对
首先搞一个点分。
然后考虑遍历一颗儿子的子树时,可能激活前面的儿子的一个子树,把这个激活放到线段树上就是区间覆盖,然后处理一个区间覆盖和区间查询1的个数,但是从点退回父亲的时候需要撤回,我没找到别的方法,只好硬肛了一个可持久化上去。
然后还有一个细节,就是当前子树点到分治中心的路径上有一对,这个可以激活之前的全部点,相应的,之前的子树也可能包含这样的链,就差不多处理就好了。
复杂度\(O(n\log^2 n)\)
\(O(n\log n)\)正解
这个题感觉好像是个套路,和接水果那题有点像
就是 处理树上路径包含统计数量之类的题目可以用\(dfs\)搞成二维的,然后贡献搞成矩形之类的,扫描线一下。
这个题可以对点对是祖孙关系和两个子树关系的东西讨论,然后弄成矩形覆盖的贡献,直接扫描线就可以了。
Code:(点分)
#include <cstdio>
#include <cctype>
#include <vector>
#define ll long long
const int N=2e5+10;
template <class T>
void read(T &x)
{
x=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
}
struct node
{
int v,id;
node(){}
node(int V,int Id){v=V,id=Id;}
};
std::vector <node> yuu[N];
int n,m,Id[N];ll ans;
int head[N],to[N<<1],Next[N<<1],cnt;
void add(int u,int v)
{
to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int tag[N*30],sum[N*30],ch[N*30][2],tot,T,root[N];
#define ls ch[now][0]
#define rs ch[now][1]
#define ols ch[las][0]
#define ors ch[las][1]
int New()
{
tag[++tot]=0,sum[tot]=0,ch[tot][0]=ch[tot][1]=0;
return tot;
}
void pushdown(int now,int L,int R)
{
if(L==R) return;
if(tag[now])
{
int Mid=L+R>>1;
if(!ls) ls=New();
tag[ls]=tag[now];
sum[ls]=Mid+1-L;
if(!rs) rs=New();
tag[rs]=tag[now];
sum[rs]=R-Mid;
}
}
void change(int las,int &now,int L,int R,int l,int r)
{
if(l>r) return;
pushdown(las,L,R);
now=New();
if(l==L&&r==R)
{
tag[now]=1;
sum[now]=R+1-L;
return;
}
int Mid=L+R>>1;
if(r<=Mid) change(ols,ls,L,Mid,l,r),rs=ors;
else if(l>Mid) ls=ols,change(ors,rs,Mid+1,R,l,r);
else change(ols,ls,L,Mid,l,Mid),change(ors,rs,Mid+1,R,Mid+1,r);
sum[now]=sum[ls]+sum[rs];
}
int query(int now,int L,int R,int l,int r)
{
if(!now) return 0;
if(l==L&&r==R) return sum[now];
pushdown(now,L,R);
int Mid=L+R>>1;
if(r<=Mid) return query(ls,L,Mid,l,r);
else if(l>Mid) return query(rs,Mid,R,l,r);
else return query(ls,L,Mid,l,Mid)+query(rs,Mid+1,R,Mid+1,r);
}
int rt,si,mi,siz[N],del[N],buct[N],is[N],tim,key[N],tl;
int dfn[N],dfsclock;
void dfsrt(int now,int fa)
{
int mx=0;siz[now]=1;
for(int v,i=head[now];i;i=Next[i])
if((v=to[i])!=fa&&!del[v])
{
dfsrt(v,now);
siz[now]+=siz[v];
mx=mx>siz[v]?mx:siz[v];
}
mx=mx>si-siz[now]?mx:si-siz[now];
if(mx<mi) mi=mx,rt=now;
}
void dfs0(int now,int fa)
{
siz[now]=1;
dfn[now]=++dfsclock;
is[now]=tim;
for(int v,i=head[now];i;i=Next[i])
if((v=to[i])!=fa&&!del[v])
{
dfs0(v,now);
siz[now]+=siz[v];
}
}
void dfs(int now,int fa)
{
++T;
root[T]=root[T-1];
for(int i=0;i<yuu[now].size();i++)
{
int v=yuu[now][i].v,id=yuu[now][i].id;
++buct[id];
if(buct[id]==2)
{
key[++key[0]]=now;
change(root[T],root[T],1,dfsclock,1,tl-1);
}
if(is[v]==tim&&dfn[v]<tl&&dfn[v]!=1)
change(root[T],root[T],1,dfsclock,dfn[v],dfn[v]+siz[v]-1);
}
ans+=1ll*sum[root[T]];
for(int v,i=head[now];i;i=Next[i])
if((v=to[i])!=fa&&!del[v])
dfs(v,now);
for(int i=0;i<yuu[now].size();i++) --buct[yuu[now][i].id];
--T;
}
void Divide(int now)
{
del[now]=1;
key[0]=dfsclock=0;
++tim;
dfs0(now,0);
root[T=0]=tot=0;
for(int i=0;i<yuu[now].size();i++) ++buct[yuu[now][i].id];
for(int v,i=head[now];i;i=Next[i])
if(!del[v=to[i]])
{
T=0;//撤回到0但是东西前面树的东西还在
for(int j=1;j<=key[0];j++)
change(root[T],root[T],1,dfsclock,dfn[key[j]],dfn[key[j]]+siz[key[j]]-1);
key[0]=0;
tl=dfn[v];
dfs(v,now);
}
for(int i=0;i<yuu[now].size();i++) --buct[yuu[now][i].id];
for(int v,i=head[now];i;i=Next[i])
if(!del[v=to[i]])
{
mi=si=siz[v];
dfsrt(v,now);
Divide(rt);
}
}
int main()
{
read(n),read(m);
for(int u,v,i=1;i<n;i++)
read(u),read(v),add(u,v),add(v,u);
for(int a,b,i=1;i<=m;i++)
{
read(a),read(b);
yuu[a].push_back(node(b,i));
yuu[b].push_back(node(a,i));
}
mi=si=n,dfsrt(1,0),Divide(rt);
ans=1ll*n*(n-1)/2-ans;
printf("%lld\n",ans);
return 0;
}
扫描线有机会补
2019.2.22