P7880 [Ynoi2006] rldcot
P7880 [Ynoi2006] rldcot
题意
区间虚树数颜色。
题解
十分好的一道题目,绕来绕去又绕回最初的思路了。
首先考虑怎么写出 \(O(nq)\) 的暴力,显然就是扫描树上的每一个点,然后判断有没有点跨子树。
然后考虑到我们求的是虚树颜色数,所以考虑莫队,删除和插入都可以通过找前驱和后继来获得影响的 \(lca\),通过回滚莫队可以做到 \(O(n\sqrt n)\)。
如果直接从嗯求虚树的话不是很能入手,往最原始的跨方向上面去考虑,即存在一个点对跨子树。
原先是求一个区间能否令一个点产生贡献,考虑反过来,求一个点能对哪些区间造成贡献,感觉这是这题一个比较关键的点。
考虑怎么求,显然一个点对应的所有区间就是所有跨子树的点对,但是会发现并不是所有的都有效,会发现同一个 \(l\) 只有 \(r\) 最小的有效,\(r\) 同理。
也就是说我们要对于每一个点求出子树外的前驱与后继,再容易发现,并不是每一个点都需要找,子树内和子树外选一方全部求就好了。
这时候其实就可以用树上启发式合并解决了,将一个点所对应的一些区间变成一个三元组 \((l,r,w)\) 表示包含 \(l,r\) 的区间都有颜色 \(w\),然后扫描线平凡处理就好了。
有效点对只有 \(nlogn\) 对,加上扫描线,时间复杂度 \(O(nlog^2)\),代码不卡常,极其好写 。
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define rg register
#define pc putchar
#define gc getchar
#define pf printf
#define space pc(' ')
#define enter pc('\n')
#define me(x,y) memset(x,y,sizeof(x))
#define pb push_back
#define FOR(i,k,t,p) for(rg int i(k) ; i <= t ; i += p)
#define ROF(i,k,t,p) for(rg int i(k) ; i >= t ; i -= p)
using namespace std ;
bool s_gnd ;
inline void read(){}
template<typename T,typename ...T_>
inline void read(T &x,T_&...p)
{
x = 0 ;rg int f(0) ; rg char c(gc()) ;
while(!isdigit(c)) f |= (c=='-'),c = gc() ;
while(isdigit(c)) x = (x<<1)+(x<<3)+(c^48),c = gc() ;
x = (f?-x:x) ;
read(p...);
}
int stk[30],tp ;
inline void print(){}
template<typename T,typename ...T_>
inline void print(T x,T_...p)
{
if(x < 0) pc('-'),x = -x ;
do stk[++tp] = x%10,x /= 10 ; while(x) ;
while(tp) pc(stk[tp--]^48) ; space ;
print(p...) ;
}
bool S_GND ;
const int N = 5e5+5 ;
int n,m ;
int a[N],vis[N],dep[N],tr[N],ans[N] ;
struct Edge{int v,w ;} ;
struct P{int l,r,col ;} ;
vector<Edge>e[N],ad[N],q[N] ;
vector<P>k ;
set<int>s[N] ;
unordered_map<int,int>mp ;
void Dfs(int x)
{
vis[x] = 1,s[x].insert(x) ;
for(auto [v,w]:e[x]) if(!vis[v])
{
dep[v] = dep[x]+w,Dfs(v) ;
if(s[x].size() < s[v].size()) swap(s[x],s[v]) ;
for(auto p:s[v])
{
auto it = s[x].lower_bound(p) ;
if(it != s[x].end()) k.pb({p,*it,dep[x]}) ;
if(it != s[x].begin()) --it,k.pb({*it,p,dep[x]}) ;
}
for(auto p:s[v]) s[x].insert(p) ;
}
k.pb({x,x,dep[x]}) ;
}
void Add(int x,int p)
{
while(x <= n) tr[x] += p,x += -x&x ;
}
int Ask(int x)
{
int res = 0 ;
while(x) res += tr[x],x -= -x&x ; return res ;
}
signed main()
{
//cerr<<(double)(&s_gnd-&S_GND)/1024.0/1024.0 ;
// freopen(".in","r",stdin) ;
// freopen(".out","w",stdout) ;
read(n,m) ;
FOR(i,2,n,1)
{
int u,v,w ; read(u,v,w) ;
e[u].pb({v,w}),e[v].pb({u,w}) ;
}
FOR(i,1,m,1)
{
int l,r ; read(l,r) ;
q[r].pb({l,i}) ;
}
Dfs(1) ;
for(auto [l,r,w]:k) ad[r].pb({l,w}) ;//,print(l,r,w),enter ;
FOR(i,1,n,1)
{
for(auto [l,col]:ad[i])
{
if(mp.find(col) == mp.end()) Add(l,1),mp[col] = l ;
if(mp.find(col) != mp.end() && mp[col] < l) Add(mp[col],-1),Add(l,1),mp[col] = l ;
}
for(auto [l,id]:q[i]) ans[id] = mp.size()-Ask(l-1) ;//,print(id) ;
}
FOR(i,1,m,1) print(ans[i]),enter ;
return 0 ;
}
感觉这个题的难点是不能卡在虚树上面了,往点对区间的贡献上面去想。
然后就是发现并不需要所有的点对都求出来,一般像这种前驱后继只考虑一边也是对的。

浙公网安备 33010602011771号