【[JLOI2014]松鼠的新家】
阿新 • • 發佈:2019-01-02
//第一次A掉紫題就來寫題解,我是不是瘋了 //說實話這道題還是比較裸的樹上差分 //對於樹上的一條路徑(s,t),我們只需要把ch[s]++,ch[t]++,ch[LCA(S,T)]--,再把lca的爸爸減一 //再dfs跑一遍就可以了 //但這題還是有些不一樣的,這道題裡的路徑終點會算作下一條路徑的起點 //解法與樓下的大佬們有些類似,但我可能寫的比較好理解一些吧(其實只因為我太弱了) //還是上程式碼吧 #include<iostream> #include<cstring> #include<fstream> #include<cstdio> using namespace std; const int maxn=300001; struct node { int u,v; int nxt; }edge[maxn*2];// 無向邊,領接表開兩倍 int deep[maxn],ch[maxn],head[maxn],a[maxn]; int f[maxn][30];//倍增陣列,f[i][j]表示i向上跳2^j到達的點 int n,num=1; inline void read(int &x)//讀入優化 { char c=getchar(); x=0; while(c<'0'||c>'9') c=getchar(); while(c>='0'&&c<='9') { x=x*10+c-48; c=getchar(); } } void add_edge(int x,int y)//鄰接表加邊 { edge[num].u=x; edge[num].v=y; edge[num].nxt=head[x]; head[x]=num; num++; } void build(int r)//建樹 { for(int i=head[r];i!=-1;i=edge[i].nxt) { int xx=edge[i].v; if(deep[xx]==0)//如果點xx沒有被訪問過的話 { deep[xx]=deep[r]+1; f[xx][0]=r; build(xx);//繼續以xx為根建樹 } } } void fill() { for(int i=1;i<=29;i++) for(int j=1;j<=n;j++) f[j][i]=f[f[j][i-1]][i-1];//第j個節點,向上跳i能到達的節點先跳到2^(i-1)處再向上跳2^(i-1)能到達的節點 } int lca(int x,int y) { if(deep[x]<deep[y]) swap(x,y);//如果x在y上面,交換讓x往上跳 for(int i=29;i>=0;i--) if(deep[f[x][i]]>=deep[y]) x=f[x][i];//使x跳到與y同一深度 if(x==y) return x; for(int i=29;i>=0;i--) if(f[x][i]!=f[y][i]) { x=f[x][i]; y=f[y][i]; } return f[x][0];//再跳一步,找到lca } void dfs(int r) { for(int i=head[r];i!=-1;i=edge[i].nxt) { int xx=edge[i].v; if(xx==f[r][0]) continue; dfs(xx); ch[r]+=ch[xx];//求子樹和 } } inline void write(int x)//閒的沒事幹加的輸出優化 { if(x>9) write(x/10); putchar(x%10+'0'); } int main() { read(n); for(int i=1;i<=n;i++) { read(a[i]); head[i]=-1; } int xx,yy; for(int i=1;i<=n-1;i++) { read(xx); read(yy); add_edge(xx,yy); add_edge(yy,xx); } deep[1]=1; build(1);//以1為根建樹 fill(); for(int i=1;i<=n-1;i++) { xx=a[i]; yy=a[i+1]; ch[xx]++; ch[yy]++; ch[lca(xx,yy)]--; ch[f[lca(xx,yy)][0]]--; } dfs(1); for(int i=2;i<=n;i++) ch[a[i]]--;//因為我們把既作為起點又做為終點的點算了兩次,現在把這些點的權值減一即可 for(int i=1;i<=n;i++) { write(ch[i]); putchar(char(10));//char(10)即換行 } return 0; }