Problem
NiroBC 姐姐是个活泼的少女,她十分喜欢爬树,而她家门口正好有一棵果树,正好满足了她爬树的需求。
这颗果树有N个节点,节点标号 1…N。每个节点长着一个果子,第i个节点上的果子颜色为 Ci 。
NiroBC姐姐每天都要爬树,每天都要选择一条有趣的路径 (u,v) 来爬。
一条路径被称作有趣的,当且仅当这条路径上的果子的颜色互不相同。
(u,v) 和 (v,u) 被视作同一条路径。特殊地,(i,i) 也被视作一条路径,这条路径只含 i 一个果子,显然是有趣的。
NiroBC姐姐想知道这颗树上有多少条有趣的路径。
Hint
Solution
这题我刚看以为是启发式合并,再想想又感觉可能可以点分治,不过想了个把小时都没想到,最终只得无奈地打了67points的做法。
12points:暴力
两重循环,枚举一条路径的两个端点,然后再用普通lca暴力判断路径中的颜色是否互不相同。
时间复杂度:
O(N3)
O
(
N
3
)
。
Code
实在太水了,故没打。
37points:dfs
考虑枚举一条路径的一个端点x。
我们可以设x为root,然后从x开始dfs。
显然,我们在dfs到x这个节点(不一定是root)的时候,可以维护从root到x中出现过哪些颜色,然后只走向那些没有出现过的颜色的子节点。
时间复杂度:
O(N2)
O
(
N
2
)
。
Code
反正下面的67points做法也有,故不单独贴了。
67points:dfs+队列
Subtask3的特殊限制是整棵树形成一条链。那么我们也可以把那棵树代换成一个序列,则原问题转化为:1.定义一个有趣的子串为序列中的一个连续的子串,且该子串中每个位置的颜色不重复;2.求序列中有趣的子串数。
考虑枚举子串的右边界i。
若设当右边界为i时,最大的有趣子串的左边界为j,则可以发现,j随着i的递增严格单调不减。于是我们就可以像一个队列一样,每次插入c[i]这个颜色,而如果原先的最大的有趣子串中已包含这个颜色,则让j++,直到不包含为止。
结合前面37points的做法,可以拿到67points。
时间复杂度:
O(N2)
O
(
N
2
)
or
O(N)
O
(
N
)
。
Code
#include <cstdio>
using namespace std;
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
const int N=1e5+1,M=N<<1;
int i,j,n,c[N],u,v,tot,tov[M],next[M],last[N];
ll ans;
bool color[N];
inline void insert(int x,int y)
{
tov[++tot]=y;
next[tot]=last[x];
last[x]=tot;
}
void scan()
{
scanf("%d",&n);
fo(i,1,n)scanf("%d",&c[i]);
fo(i,1,n-1)
{
scanf("%d%d",&u,&v);
insert(u,v);
insert(v,u);
}
}
void dfs(int x,int fat)
{
color[c[x]]=1;
int i,y;
for(i=last[x];i;i=next[i])
if((y=tov[i])!=fat&&!color[c[y]])
ans++,dfs(y,x);
color[c[x]]=0;
}
void brute()
{
fo(i,1,n)dfs(i,0);
ans=ans/2+(ll)n;
}
void limp()
{
j=1;
fo(i,1,n)
{
while(j<i&&color[c[i]])color[c[j++]]=0;
color[c[i]]=1;
ans+=(ll)(i-j+1);
}
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scan();
if(n<=3e3)
brute();
else limp();
printf("%lld",ans);
}
100points:扫描线+线段树
一开始我也觉得很扯:这道题怎么会和扫描线+线段树有关系?!
但是其实这就是正解。
这道题和子树看上去有点关系,考虑先将整棵树dfs一遍,求出每个点的dfs序(记为dfn,先序遍历),以及以每个节点为根的子树的编号区间。设p[x]为点x的子树的左编号(显然就等于dfn[x]),q[x]为右编号。
然而这并没有什么卵用。
我们可以考虑一个问(when)题:
给定一棵N个点的树,给出M组形如“x与y不可出现在同一路径上”的限制,问有多少条合法路径。
如果一条路径(u,v)不合法,就说明(u,v)上同时出现了某组x,y。
考虑把路径(u,v)映射成二维直角坐标系上的一个点,横坐标是dfn[u],纵坐标是dfn[v]。设dfn[u]≥dfn[v]。
我们可以分类讨论一下:
当x和y有祖孙关系时。设x是y的祖先,z为x到y的路径上与x距离为1的点,则u和v两个点,一个 不在 z的子树内,另一个 在 y的子树内。也就是说,x和y这组限制使得坐标系中[p[y]..q[y],1..p[z]-1]这个矩阵(显然z亦为y的祖先,所以dfn[z]<dfn[y],于是q[y]≥p[y]>p[z]-1)不合法,并使[q[z]+1..n,p[y]..q[y]](显然y在z的子树中,所以q[z]≥q[y],于是q[z]+1>q[y]≥p[y])这个矩阵也不合法。
当x和y无祖孙关系时。u和v两个点,一个 在 x的子树内,另一个 在 y的子树内。设dfn[x]≥dfn[y],则x和y这组限制使得[p[x]..q[x],p[y]..q[y]]这个矩阵不合法。
这时,问题就转化成了一个N*N的二维平面,上面有至多2M个矩形,求有多少个格子不被任何一个矩形覆盖。
这种题可以用二维线段树/树套树扫描线+线段树轻松解决。
不过,要注意一点,此问题求的并不是往常的“所有点中被覆盖的最多次数”之类的,而是没被覆盖的点数,所以我们在实现线段树时不能傻傻地直接区间修改,而是要修改它的lazy_tag,且这个lazy_tag反常地不能下传,然后以lazy_tag来维护线段树t。
回到这道题目,要求路径上的点的颜色互不相同,所以同色的点不能出现在同一路径上。对于每一个点,最多只有19个点与其颜色相同,所以M至多=19N。
时间复杂度: O(19Nlog2N) O ( 19 N l o g 2 N ) 。
Code
#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
const int N=1e5+9,M=N*20;
int i,j,c[N],u,v,tot,tov[N<<1],next[N<<1],last[N],dfn[N],p[N],q[N],time,deep[N],anc[N][17],b[N],x,y;
ll n,ans;
inline void link(int x,int y)
{
tov[++tot]=y;
next[tot]=last[x];
last[x]=tot;
}
void scan()
{
scanf("%d",&n);
fo(i,1,n)scanf("%d",&c[i]);
fo(i,1,n-1)
{
scanf("%d%d",&u,&v);
link(u,v);
link(v,u);
}
}
void renum(int x)
{
dfn[x]=p[x]=++time;
int i,y=anc[x][0];
fo(i,1,16)
{
if(!y)break;
y=anc[x][i]=anc[y][i-1];
}
for(i=last[x];i;i=next[i])
if(!dfn[y=tov[i]])
{
deep[y]=deep[x]+1;
anc[y][0]=x;
renum(y);
}
q[x]=time;
}
inline bool cmp(int x,int y){return c[x]<c[y];}
void init()
{
renum(1);
fo(i,1,n)b[i]=i;
sort(b+1,b+n+1,cmp);
}
int getz(int x,int y)
{
int i,f;
fd(i,16,0)if(deep[f=anc[y][i]]>deep[x])y=f;
return y;
}
bool pdf(int x,int y)
{
return anc[getz(x,y)][0]==x;
}
struct Limit
{
int P[M<<1],Q[M<<1],W[M<<1],next[M<<1],last[N],tot;
inline void link(int x,int p,int q,int w)
{
P[++tot]=p;Q[tot]=q;W[tot]=w;
next[tot]=last[x];
last[x]=tot;
}
inline void limit(int x,int y)
{
if(pdf(x,y))
{
int z=getz(x,y);
link(p[y] ,1 ,p[z]-1,1 );
link(q[y]+1,1 ,p[z]-1,-1);
link(q[z]+1,p[y],q[y] ,1 );
}
else
{
if(dfn[x]<dfn[y])swap(x,y);
link(p[x] ,p[y],q[y],1 );
link(q[x]+1,p[y],q[y],-1);
}
}
}L;
void cover()
{
fo(i,1,n)
fd(j,i-1,1)
{
if(c[x=b[i]]>c[y=b[j]])break;
if(deep[x]>deep[y])swap(x,y);
L.limit(x,y);
}
}
#define A v<<1
#define B A|1
int val,t[N<<2],lz[N<<2];
void add(int v,int l,int r)
{
if(y<l||x>r)return;
if(x<=l&&r<=y){lz[v]+=val;return;}
int mid=l+r>>1;
add(A,l,mid);add(B,mid+1,r);
t[v]=(lz[A]?mid-l+1:t[A])+(lz[B]?r-mid:t[B]);
}
void work()
{
fo(i,1,n)
{
for(j=L.last[i];j;j=L.next[j])
{
x=L.P[j];y=L.Q[j];val=L.W[j];
add(1,1,n);
}
ans+=lz[1]?i:t[1];
}
}
inline void print()
{
ans=n*n-2*ans;
printf("%lld",(ans-n)/2+n);
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
scan();
init();
cover();
work();
print();
}