NOIP 模擬 6 模板
阿新 • • 發佈:2021-06-11
題解
這道題是一道啟發式合併的題目,每次合併完重構一下線段樹就可以,不用線段樹合併。
以操做時間為下標,建立一顆線段樹,維護小球的個數與小球的顏色數,最後線段樹上二分查詢。
我們先不用考慮每個節點放小球數的限制,最後二分查詢時,找小球數 \(\leq\) 限制數的所對應的節點下標的顏色數。
在本題中,我們要用一個 \(vector\) 來儲存,通過 \(vector\) 的動態記憶體釋放來防止 \(MLE\) 。
而且也要注意,在本題中顏色編號可能為負數,所以我們要先將其對映到正整數範圍內,這個可以用 \(map\) 。
還有一個挺玄學的地方就是啟發式合併,通俗得說就是由一個小集合向一個大集合合併,這樣可以減少合併次數,從而減小複雜度。
這體現到本題上就是對於一個節點,如果其非葉節點,找到一個運算元最多的子樹,將其它子樹向這個重兒子合併。
細節挺多,且卡常的地方也很多,具體看程式碼
\(AC\kern 0.5emCODE:\)
Code
#include<bits/stdc++.h> #define ri register int #define p(i) ++i using namespace std; namespace IO{ char buf[1<<21],*p1=buf,*p2=buf; #define gc() p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++ inline int read() { ri x=0,f=1;char ch=gc(); while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();} while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+(ch^48);ch=gc();} return x*f; } } using IO::read;//快讀 namespace nanfeng{ #define cmax(x,y) ((x)>(y)?(x):(y)) #define cmin(x,y) ((x)>(y)?(y):(x)) #define FI FILE *IN #define FO FILE *OUT #define pb(x) push_back(x) #define node(c,id) (node){c,id} const int N=1e5+7; struct edge{int v,nxt;}e[N<<1]; struct node{int c,id;}; int first[N],buc[N],tms[N],son[N],ans[N],ocu[N],t=1,tot,n,m,Q; map<int,int> col;//可能會有負數 vector<node> ch[N];//二位陣列會mle,node 可以用pair替換 inline void add(int u,int v) { e[t].v=v; e[t].nxt=first[u]; first[u]=t++; } struct Seg{//封裝 #define ls(x) (x<<1) #define rs(x) (x<<1|1) struct Segmenttree{int sz,szc,lz;}T[N<<2]; inline void init(int x) { T[1].sz=T[1].sz=0; T[1].lz=1;//懶標記實在初始化時用,類似與區間更新,沒用到的區間就不初始化 for (ri i(0);i<ch[x].size();p(i)) ocu[ch[x][i].c]=0; } inline void down(int x) { if (!T[x].lz) return; int l=ls(x),r=rs(x); T[x].lz=T[l].sz=T[l].szc=T[r].sz=T[r].szc=0; T[l].lz=T[r].lz=1; } inline void up(int x) { int l=ls(x),r=rs(x); T[x].sz=T[l].sz+T[r].sz; T[x].szc=T[l].szc+T[r].szc; } void update(int x,int l,int r,int k,int c,int nm) { if (l==r) {T[x].sz+=nm;T[x].szc+=c;return;} down(x); int mid=(l+r)>>1; if (k<=mid) update(ls(x),l,mid,k,c,nm); else update(rs(x),mid+1,r,k,c,nm); up(x); } inline void merge(int x) { for (ri i(0);i<ch[x].size();p(i)) { node tmp=ch[x][i]; if (!ocu[tmp.c]) { update(1,1,m,tmp.id,1,1); ocu[tmp.c]=tmp.id; } else if (ocu[tmp.c]>tmp.id) { update(1,1,m,ocu[tmp.c],-1,0); update(1,1,m,tmp.id,1,1); ocu[tmp.c]=tmp.id; } else update(1,1,m,tmp.id,0,1); } } int query(int x,int l,int r,int k) { if (!k) return 0; if (l==r) return T[x].szc; int mid=(l+r)>>1; down(x); int lt=ls(x),rt=rs(x); if (T[lt].sz<=k) return T[lt].szc+query(rt,mid+1,r,k-T[lt].sz);//以小球數二分 else return query(lt,l,mid,k); } }T; void dfs_init(int x,int fa) { tms[x]=ch[x].size(); for (ri i(first[x]),v;i;i=e[i].nxt) { if ((v=e[i].v)==fa) continue; dfs_init(v,x); tms[x]+=tms[v]; if (tms[v]>tms[son[x]]) son[x]=v;//這裡有一個優化,因為是啟發式合併,所以要先處理出運算元多的重兒子,由小向大合併,減少合併數 } } void dfs(int x,int fa) { for (ri i(first[x]),v;i;i=e[i].nxt) { if ((v=e[i].v)==fa||v==son[x]) continue; dfs(v,x);T.init(v);//記得回溯 } if (son[x]) dfs(son[x],x);//重兒子單獨搜,不用回溯 T.merge(x); for (ri i(first[x]),v;i;i=e[i].nxt) { if ((v=e[i].v)==fa||v==son[x]) continue; T.merge(v); } ans[x]=T.query(1,1,m,buc[x]);//二分查詢 if (son[x]) { int sx=son[x]; for (ri i(0);i<ch[x].size();p(i)) ch[sx].pb(ch[x][i]); swap(ch[x],ch[sx]);//啟發式合併 for (ri i(first[x]),v;i;i=e[i].nxt) { if ((v=e[i].v)==fa||v==son[x]) continue; for (ri i(0);i<ch[v].size();p(i)) ch[x].pb(ch[v][i]); } } } inline int main() { // FI=freopen("nanfeng.in","r",stdin); // FO=freopen("nanfeng.out","w",stdout); n=read(); for (ri i(1);i<n;p(i)) { int u=read(),v=read(); add(u,v);add(v,u); } for (ri i(1);i<=n;p(i)) buc[i]=read(); m=read(); for (ri i(1);i<=m;p(i)) { int x=read(),c=read(); if (!col[c]) col[c]=p(tot),c=tot;//對映成整數範圍 else c=col[c]; ch[x].pb(node(c,i)); } dfs_init(1,0);dfs(1,0); Q=read(); for (ri i(1),x;i<=Q;p(i)) printf("%d\n",ans[x=read()]); return 0; } } int main() {return nanfeng::main();}
時間複雜度為 \(\mathcal O(nlog^2n)\)?我不太會分析,哪位大佬教我一下?