BZOJ 3872 ant colony
一道比较有意思的好题目吧。
这道题其实思路应该是很有意思的。
我们注意到,这棵树被一条关键的边分成了两部分。
当从两边来的数量恰好是要求的数量时,才会计算答案。
那么我们考虑到题目中,传递信息的方式是固定的,
也就是说,我们只要确定了叶节点就能够算出答案。
那么n方暴力就很显然了:枚举每一个叶节点,遍历树计算答案。
考虑优化。
其实上述算法对与 传递信息方式固定 这一性质没有很好的利用上。
这个东西它可以意味着什么呢。
我们考虑一下如果经过关键边时为x计算答案,
那么所有与两个端点相连的边
也只有在当经过它时,数量在一定区间范围内才能贡献答案,这样就直接逆向利用信息传递就可求。
同理,推广到所有边。
这样最后每一个叶节点都会有一个区间,仅仅只有在这个区间内才能贡献答案。
那么二分查找一下就好了。时间复杂度O(nlogn)。写的时候注意点细节(区间的开闭)等。。
#include <bits/stdc++.h> using namespace std; inline int gi () { int x=0, w=0; char ch=0; while (! (ch>='0' && ch<='9') ) { if (ch=='-') w=1; ch=getchar (); } while (ch>='0' && ch<='9') { x= (x<<3) + (x<<1) + (ch^48); ch=getchar (); } return w?-x:x; } const int G=1e6+10; int n,g,tot,KeyEdx,KeyEdy,head[G]; long long k,Ran[G][2],Ant[G],Ind[G],Ans; struct Tree { int next, now; }t[G<<1]; inline void make (int from, int to) { t[++tot].next=head[from]; head[from]=tot; t[tot].now=to; } void DFS (int x, int fax) { for (int i=head[x];i;i=t[i].next) { int Nex=t[i].now; if (Nex==fax) continue; if (Ind[Nex]==1) { Ran[Nex][0]=Ran[x][0], Ran[Nex][1]=Ran[x][1]; } else { Ran[Nex][0]=Ran[x][0]* (Ind[Nex]-1); Ran[Nex][1]=Ran[x][1]* (Ind[Nex]-1)+Ind[Nex]-2; } DFS (Nex, x); } } int main () { n=gi (), g=gi (), k=gi (); for (int i=1;i<=g;++i) Ant[i]=gi (); sort (Ant+1, Ant+g+1); for (int i=1, x, y;i<n;++i) { x=gi (), y=gi (); Ind[x]++, Ind[y]++; make (x, y), make (y, x); if (i==1) { KeyEdx=x; KeyEdy=y; } } if (Ind[KeyEdx]==1) Ran[KeyEdx][0]=Ran[KeyEdx][1]=k; else { Ran[KeyEdx][0]=k* (Ind[KeyEdx]-1); Ran[KeyEdx][1]=Ran[KeyEdx][0]+Ind[KeyEdx]-2; } if (Ind[KeyEdy]==1) Ran[KeyEdy][0]=Ran[KeyEdy][1]=k; else { Ran[KeyEdy][0]=k* (Ind[KeyEdy]-1); Ran[KeyEdy][1]=Ran[KeyEdy][0]+Ind[KeyEdy]-2; } DFS (KeyEdx, KeyEdy); DFS (KeyEdy, KeyEdx); for (int i=1;i<=n;++i) { if (Ind[i]>1) continue; Ans+=upper_bound (Ant+1, Ant+g+1, Ran[i][1])-lower_bound (Ant+1, Ant+g+1, Ran[i][0]); } printf ("%lld\n", Ans*k); return 0; }