1. 程式人生 > >洛谷4322 SHOI2014 三叉神經樹(LCT+思維)

洛谷4322 SHOI2014 三叉神經樹(LCT+思維)

題目連結

好久之前做的題了QWQ
現在來補一發部落格

一道神仙題啊。。qwq

首先,我們可以看出來,我們如果對於每個點維護一個 v a l val ,表示他的直系兒子中有幾個表現為1的。

那麼 v

a l [ x ] > > 1 val[x]>>1
就是他反應的型別

這樣十分便於我們計算一開始的 v a l val

那麼考慮修改。

一定是會修改一條

1 0 > 1 ) 2 ( 1 > 0 ) 連續1(對應著0->1),或者連續2(1->0)

也就是說,如果我們能夠知道一次修改, 1 x 1到x 的路徑下最下面的1或者2的位置,我們就能夠通過鏈修改來實現。

其實一開始我想的是二分

我們發現,可以通過 L C T LCT 維護最深的不是 1 1 的位置和不是 2 2 的位置 n u m 1 n u m 2 num1和num2

那麼我們對於一次修改,假設由 0 1 0修改成1
如果 n u m 1 = = 0 num1==0 ,那麼說明整條鏈都會被修改,直接修改整條路徑

不然,我們就將路徑提出來, s p l a y ( n u m 1 ) splay(num1) 之後,修改他的右兒子,表示他下面的點。
然後把當前點的 v a l val 修改,但是不改變別的量。

QWQ有一些細節,對於修改的時候,由於路徑上的 v a l val 都是1或者2。
所以修改的之後可以直接 x o r   2 xor\ 2

具體細節看程式碼實現吧

裡面有詳細的註釋

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#include<set>
using namespace std;
inline int read()
{
  int x=0,f=1;char ch=getchar();
  while (!isdigit(ch)) {if (ch=='-') f=-1;ch=getchar();}
  while (isdigit(ch)) {x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}
const int maxn = 2e6+1e2;
int ch[maxn][3];
int fa[maxn],val[maxn];
int tag[maxn];
int n,m;
int num1[maxn],num2[maxn]; //深度最深的 兒子數不為1 或者 2 的 節點是的編號 
int st[maxn];
int son(int x)
{
 if (ch[fa[x]][0]==x) return 0;
 else return 1;
}
bool notroot(int x)
{
 return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
}
void update(int x) //由於是深度最深,我們一定是先考慮右子樹,再說當前點,再是右子樹 
{
 num1[x]=num1[ch[x][1]];
 if (!num1[x] && val[x]!=1) num1[x]=x;
 if (!num1[x]) num1[x]=num1[ch[x][0]];
 num2[x]=num2[ch[x][1]];
 if (!num2[x] && val[x]!=2) num2[x]=x;
 if (!num2[x]) num2[x]=num2[ch[x][0]];
}
void solve(int x,int d)
{
 val[x]^=3;
 swap(num1[x],num2[x]); //修改的時候,必定是一段全為1或者2的區間,所以一個一定是0,直接交換是沒錯的 
 tag[x]+=d;
}
void pushdown(int x)
{
 if (tag[x])
 {
  if (ch[x][0]) solve(ch[x][0],tag[x]);
  if (ch[x][1]) solve(ch[x][1],tag[x]);
  tag[x]=0;
 }
}
void rotate(int x)
{
 int y=fa[x],z=fa[y];
 int b=son(x),c=son(y);
 if (notroot(y)) ch[z][c]=x;
 fa[x]=z;
 ch[y][b]=ch[x][!b];
 fa[ch[x][!b]]=y;
 ch[x][!b]=y;
 fa[y]=x;
 update(y);
 update(x);
}
void splay(int x)
{
 int y=x,cnt=0;
 st[++cnt]=y;
 while (notroot(y)) y=fa[y],st[++cnt]=y;
 while (cnt) pushdown(st[cnt--]);
 while (notroot(x))
 {
  int y=fa[x],z=fa[y];
  int b=son(x),c=son(y);
  if (notroot(y))
  {
   if (b==c) rotate(y);
   else rotate(x);
  }
  rotate(x);
 }
    update(x);
}
void access(int x)
{
 for (int y=0;x;y=x,x=fa[x])
 {
  splay(x);
  ch[x][1]=y;
  update(x);
 }
}
int in[maxn];
queue<int> q;
int main()
{
   n=read();
   for (int i=1;i<=n;i++)
   {
      int x=read(),y=read(),w=read();     
      in[i]=3;
      fa[x]=fa[y]=fa[w]=i; 
   }
   for (int i=n+1;i<=3*n+1;i++) val[i]=read()*2,q.push(i); //我們事先val都*2,那麼對於每個點,他表現出來的特徵就是val>>1 
   int m=read();
   while (!q.empty()) //拓撲排序先預處理出來每個點的val 
   {
      int x=q.front();
      q.pop();
      if (x<=n) update(x);
      val[fa[x]]+=val[x]/2;
      in[fa[x]]--;
      if (!in[fa[x]]) q.push(fa[x]);
   }
   int ans=val[1]>>1;
   //在本題中,val表示兒子的表示1的數量,那麼val>>1就相當於每個點表達的資訊
   for (int i=1;i<=m;i++)
   {
      int x=read();
      val[x]^=2; //葉子節點只有可能是0或者1,而乘2之後就是0或者2 
      int k = val[x] - 1;
      x=fa[x]; //不修改底下的葉子節點 
      access(x);
      splay(x); //打通這個點到1的路徑 
      int now;
      if (k==-1) now = num2[x];
      else now = num1[x];
      if (!now)
      {
          solve(x,k);
          update(x);
          ans^=1;
   }
   else
   {
     splay(now);
     solve(ch[now][1],k); 
     update(ch[now][1]);
   val[now]+=k;
     update(now);
   }
   cout<<ans<<"\n";
   }
   return 0;
}