<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      NOI2021 輕重邊 題解

      NOI2021 輕重邊


      題目鏈接:#4812. [NOI2021] 輕重邊

      前置知識 : #樹鏈剖分 #線段樹

      題目大意

      給定\(n\)個點組成的樹,\(m\)次操作。

      修改操作會讓路徑\(a \to b\)上的所有點的所有連邊對答案的貢獻變為\(0\),路徑\(a \to b\)的所有邊的貢獻變為\(1\);

      查詢操作則統計路徑\(a \to b\)的答案貢獻。(樹中一開始答案貢獻為\(0\))。

      輸入格式

      \(1\)行,輸入\(T\),表示有\(T\)組數據。

      每組數據

      \(1\)行,輸入\(n,m\)表示有\(n\)個點和\(m\)個操作。
      \(2-n\)行,每行輸入\(u,v\)表示樹的一條邊。
      \(n+1-n+m\)行,每行輸入\(op,a,b\)表示操作信息\((op=1為修改,op=2為查詢)\)

      輸入樣例

      1
      7 7
      1 2
      1 3
      3 4
      3 5
      3 6
      6 7
      1 1 7
      2 1 4
      2 2 7
      1 1 5
      2 2 7
      1 2 1
      2 1 7
      

      輸出樣例

      1
      3
      2
      1
      

      數據范圍

      對于所有測試數據:\(T≤3,1≤n,m≤10^5\)

      測試點編號 \(n,m \le\) 特殊性質
      \(1 \sim 2\) \(10\)
      \(3 \sim 6\) \(5000\)
      \(7 \sim 8\) \(10^5\) \(A,B\)
      \(9 \sim 10\) \(10^5\) \(A\)
      \(11 \sim 14\) \(10^5\) \(B\)
      \(15 \sim 16\) \(2 \times 10^4\)
      \(17 \sim 20\) \(10^5\)

      思考

      感覺每次修改操作對答案的貢獻是單獨的,前面的修改操作不會影響的后面修改操作對答案的貢獻 (就是不會讓后面的操作與前面的操作產生額外貢獻)。

      比如樣例

      經過第一個操作后\(\Huge \downarrow\)

      所以操作具有無后效性,如果后面的操作與前面的操作重合,那么會統計后面的操作貢獻,前面的操作會無效。

      所以我們只需要單獨考慮每一次修改操作,使其擁有單獨的統計答案的方式。

      那么可以給每個修改操作路徑上的點打上時間戳,若邊連接兩點的時間戳相同,則該邊的貢獻為 \(1\) ,否則為 \(0\)

      可以簡單證明一下操作的正確性,每次修改操作會讓路徑 \(a \to b\) 上的所有點的所有連邊對答案的貢獻變為 \(0\),路徑 \(a \to b\) 的所有邊的貢獻變為 \(1\) ,就相當于把這一條鏈單獨拎出來,將其邊權修改為 \(1\) 。

      而將這條鏈打上單獨的時間戳,既可以起到方便統計答案的作用,又可以和其它修改操作進行區分。然后為了實現后面操作對前面操作的覆蓋,我們只記錄節點當前的時間戳。

      然后統計答案的操作

      兩個相同的點權貢獻答案,感覺和「SDOI2011」染色有點像,不同的是這道題每兩個點時間戳相同就貢獻一次,而染色是每兩個點時間戳不同就貢獻一次。

      部分分1

      #樹 #LCA

      對于前\(1\sim 6\)個測試點,\(n,m \le 5000\),完全可以接受 \(\mathcal{O}(n \cdot m)\) 的時間復雜度。

      直接進行暴力修改每個節點的時間戳和遍歷整棵樹統計答案即可。

      時間復雜度 \(\mathcal{O}(T \cdot nm)\) 期望得分\((30 \ points)\) (實際上因為常數小第\(15-16\)個點也可以過) (實際得分 \(40 \ points\))

      關鍵代碼

      //倍增求lca
      void dfs(int x,int fa){
      	f[x][0]=fa;
      	dep[x]=dep[fa]+1;
      	for(int i=1;i<20;i++){
      		f[x][i]=f[f[x][i-1]][i-1];
      	}
      	for(int i=h[x];i;i=e[i].nex){
      		int v=e[i].to;
      		if(v==fa){
      			continue;
      		}
      		dfs(v,x);
      	}
      }
      int lca(int x,int y){
      	if(dep[x]<dep[y]){
      		swap(x,y);
      	}
      	for(int i=19;i>=0;i--){
      		if(dep[f[x][i]]>=dep[y]){
      			x=f[x][i];
      		}
      	}
      	if(x==y){
      		return x;
      	}
      	for(int i=19;i>=0;i--){
      		if(f[x][i]!=f[y][i]){
      			x=f[x][i];
      			y=f[y][i];
      		}
      	}
      	return f[x][0];
      }
      
      //修改操作,枚舉每個點暴力修改
      if(op==1){
      	int LCA=lca(a,b);
      	c++;
      	for(int i=a;i!=LCA;i=f[i][0]){
      		w[i]=c;
      	}
      	for(int i=b;i!=LCA;i=f[i][0]){
      		w[i]=c;
      	}
      	w[LCA]=c;
      }
      
      //查詢操作,枚舉每個點暴力判斷
      if(op==2){
      	int LCA=lca(a,b);
      	int ans=0;
      	for(int i=a;i!=LCA;i=f[i][0]){
      		if(w[i]&&w[i]==w[f[i][0]]){
      			ans++;
      		}
      	}
      	for(int i=b;i!=LCA;i=f[i][0]){
      		if(w[i]&&w[i]==w[f[i][0]]){
      			ans++;
      		}
      	}
      	printf("%d\n",ans);
      }
      

      部分分2

      #線段樹 #DFS序

      對于\(7 \sim 10\)個測試點,樹的形態是一條鏈,那么我們直接用\(DFS序\)的方式將樹轉為區間問題,直接用線段樹維護即可。

      時間復雜度 \(\mathcal{O}(T \cdot m \log_2 n)\)

      我們把操作放在區間上來分析是如何合并的。

      \(w[i]\) 代表第 \(i\) 點的時間戳,\(s[i]\) 表示線段樹的點 ,我們設 \(s[i].l \ 和 \ s[i].r \ 分別為 \ s[i]\) 代表的區間左右端點,而 \(s[i]\) 的信息會從\(s[2*i] \ 和 \ s[2*i+1] \ 轉移過來\) 。

      我們在 [[#思考]] 中已經提到過每兩個點相同就貢獻一次,而兩個子節點內部已經統計過,直接轉移即可。

      會產生變化的只有兩個子節點交界的地方,所以我們需要記錄區間左右端點的時間戳,進行判斷。

      struct tree{
      	int l=0;//區間左端點
      	int r=0;//區間右端點
      	int lx=0;//區間左端點顏色
      	int rx=0;//區間右端點顏色
      	int lazy=0;//懶標記
      	int w=0;//區間內答案貢獻
      	tree operator +(const tree&a)const{//重載運算符,區間合并
      		tree res;
      		res.l=l;
      		res.lx=lx;//左兒子的左端點是區間左端點
      		res.r=a.r;
      		res.rx=a.rx;//右兒子的右端點是區間右端點
      		res.w=w+a.w;
      		if(rx==a.lx&&rx){//warning 當沒有時間戳時不統計答案
      			res.w++;
      		}
      		return res;
      	}//warning 記住結構體中的每個元素都要賦值,就算不變也要在賦值一遍,因為使用時是直接s[k]=s[ls]+s[rs]的賦值操作,如果不每個元素都要賦值可能導致使用的值丟失,讓程序錯誤。
      }s[500050];
      

      關鍵代碼

      //求DFS序
      void dfs(int x,int fa){
      	dfn[x]=++num;
      	rk[num]=x;
      	for(int i=h[x];i;i=e[i].nex){
      		int v=e[i].to;
      		if(v==fa){
      			continue;
      		}
      		dfs(v,x);
      	}
      }
      
      //線段樹維護
      void build(int k,int l,int r){
      	s[k].l=l;
      	s[k].r=r;
      	if(l==r){
      		return;
      	}
      	int mid=(l+r)>>1;
      	build(k<<1,l,mid);
      	build(k<<1|1,mid+1,r);
      	s[k]=s[k<<1]+s[k<<1|1];
      }
      //修改
      void change(int k,int x,int y,int z){
      	if(x<=s[k].l&&s[k].r<=y){
      		s[k].w=s[k].r-s[k].l;
      		s[k].ls=s[k].rs=s[k].lazy=z;
      		return;
      	}
      	pushdown(k);//warning 不要忘了下放懶標記
      	if(x<=s[k<<1].r){
      		change(k<<1,x,y,z);
      	}
      	if(y>=s[k<<1|1].l){
      		change(k<<1|1,x,y,z);
      	}
      	s[k]=s[k<<1]+s[k<<1|1];
      }
      //詢問
      tree query(int k,int x,int y){
      	if(x<=s[k].l&&s[k].r<=y){
      		return s[k];
      	}
      	pushdown(k);//warning 不要忘了下放懶標記
      	if(y<=s[k<<1].r){//如果區間全在左子樹,就只在左子樹上查找
      		return query(k<<1,x,y);
      	}
      	if(s[k<<1|1].l<=x){//同理
      		return query(k<<1|1,x,y);
      	}
      	return query(k<<1,x,y)+query(k<<1|1,x,y);
      }
      

      \(std\)

      #樹鏈剖分 #線段樹

      根據部分分2,線段樹可以維護一條鏈,那么就自然的想到了把樹變成鏈的算法樹鏈剖分,樹剖剖成多條重鏈用線段樹進行維護。

      時間復雜度 \(\mathcal{O}(T \cdot m\log^2_2 n)\)

      \(Code\)

      #include<bits/stdc++.h>
      #define ls k<<1
      #define rs k<<1|1
      using namespace std;
      inline long long read(){
          long long x=0,w=0;char c=0;
          while(c<'0'||c>'9') {w|=c=='-';c=getchar();}
          while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
          return w?-x:x;
      }
      int n,m,x,y,z,t,cnt;
      int son[100050],siz[100050],f[100050],dep[100050],dfn,id[100050],rk[100050],top[100050];
      vector<int> mp[100050];
      char op;
      void dfs1(int x,int fa){
      	dep[x]=dep[fa]+1;
      	siz[x]=1;
      	f[x]=fa;
      	for(int i:mp[x]){
      		if(i==fa){
      			continue;
      		}
      		dfs1(i,x);
      		siz[x]+=siz[i];
      		if(siz[son[x]]<siz[i]){
      			son[x]=i;
      		}
      	}
      }
      void dfs2(int x,int root){
      	id[x]=++dfn;
      	rk[dfn]=x;
      	top[x]=root;
      	if(son[x]){
      		dfs2(son[x],root);
      	}
      	for(int i:mp[x]){
      		if(i==f[x]||i==son[x]){
      			continue;
      		}
      		dfs2(i,i);
      	}
      }
      //樹鏈剖分
      struct tree{
      	int l=0;
      	int r=0;
      	int lx=0;
      	int rx=0;
      	int lazy=0;
      	int w=0;
      	tree operator +(const tree&a)const{
      		tree res;
      		res.l=l;
      		res.r=a.r;
      		res.lx=lx;
      		res.rx=a.rx;
      		res.w=w+a.w;
      		if(rx==a.lx&&rx){
      			res.w++;
      		}
      		return res;
      	}
      }s[500050];
      void pushup(int k){
      	s[k]=s[ls]+s[rs];
      }
      void pushdown(int k){
      	if(!s[k].lazy){
      		return;
      	}
      	s[ls].lazy=s[ls].lx=s[ls].rx=s[rs].lazy=s[rs].lx=s[rs].rx=s[k].lazy;
      	s[ls].w=s[ls].r-s[ls].l;
      	s[rs].w=s[rs].r-s[rs].l;
      	s[k].lazy=0;
      }
      void build(int k,int l,int r){
      	s[k].l=l;
      	s[k].r=r;
      	if(s[k].l==s[k].r){
      		s[k].w=s[k].lazy=s[k].lx=s[k].rx=0;
      		return;
      	}
      	int mid=(l+r)>>1;
      	build(ls,l,mid);
      	build(rs,mid+1,r);
      	pushup(k);
      }
      void change(int k,int x,int y,int c){
      	if(x<=s[k].l&&s[k].r<=y){
      		s[k].lx=s[k].rx=s[k].lazy=c;
      		s[k].w=s[k].r-s[k].l;
      		return;
      	}
      	pushdown(k);
      	if(x<=s[ls].r){
      		change(ls,x,y,c);
      	}
      	if(s[rs].l<=y){
      		change(rs,x,y,c);
      	}
      	pushup(k);
      }
      tree query(int k,int x,int y){
      	if(x<=s[k].l&&s[k].r<=y){
      		return s[k];
      	}
      	pushdown(k);
      	if(y<=s[ls].r){
      		return query(ls,x,y);
      	}
      	if(s[rs].l<=x){
      		return query(rs,x,y);
      	}
      	return query(ls,x,y)+query(rs,x,y);
      }
      //線段樹維護
      void solve1(){
      	int fx=top[x],fy=top[y];
      	while(fx!=fy){
      		if(dep[fx]<dep[fy]){
      			swap(x,y);
      			swap(fx,fy);
      		}
      		change(1,id[fx],id[x],z);
      		x=f[fx];
      		fx=top[x];
      	}
      	if(id[x]>id[y]){
      		swap(x,y);
      	}
      	change(1,id[x],id[y],z);
      }
      //修改
      void solve2(){
      	int fx=top[x],fy=top[y];
      	tree ansx=(tree){0,0,0,0,0,0},ansy=(tree){0,0,0,0,0,0};
      	while(fx!=fy){
      		if(dep[fx]>dep[fy]){
      			ansx=query(1,id[fx],id[x])+ansx;
      			x=f[fx],fx=top[x];
      		}
      		else{
      			ansy=query(1,id[fy],id[y])+ansy;
      			y=f[fy],fy=top[y];
      		}
      	}
      	if(id[x]>id[y]){
      		ansx=query(1,id[y],id[x])+ansx;
      	}
      	else{
      		ansy=query(1,id[x],id[y])+ansy;
      	}
      	swap(ansx.lx,ansx.rx);
      	ansx=ansx+ansy;
      	printf("%d\n",ansx.w);
      }
      //查詢
      int main(){
      	t=read();
      	while(t--){
      		n=read();
      		m=read();
      		memset(son,0,sizeof(son));
      		memset(siz,0,sizeof(siz));
      		memset(f,0,sizeof(f));
      		for(int i=1;i<=n;i++){
      			mp[i].clear();
      		}
      		for(int i=1;i<n;i++){
      			x=read();
      			y=read();
      			mp[x].push_back(y);
      			mp[y].push_back(x);
      		}
      		dfn=0,cnt=0;
      		dfs1(1,0);
      		dfs2(1,1);
      		build(1,1,dfn);
      		for(int i=1;i<=m;i++){
      			cin>>op;
      			x=read();
      			y=read();
      			if(op=='1'){
      				z=++cnt;//時間戳變化
      				solve1();
      			}
      			else{
      				solve2();
      			}
      		}
      	}
      	return 0;
      }
      

      \(Warning\)

      樹鏈剖分的查詢部分有一些難以理解和容易出錯 (因為要注意合并的順序) ,特此圖解。

      聚焦代碼
      void solve2(){
      	int fx=top[x],fy=top[y];
      	tree ansx=(tree){0,0,0,0,0,0},ansy=(tree){0,0,0,0,0,0};
      	while(fx!=fy){
      		if(dep[fx]>dep[fy]){
      			ansx=query(1,id[fx],id[x])+ansx;//聚焦點1
      			x=f[fx],fx=top[x];
      		}
      		else{
      			ansy=query(1,id[fy],id[y])+ansy;//聚焦點1
      			y=f[fy],fy=top[y];
      		}
      	}
      	if(id[x]>id[y]){
      		ansx=query(1,id[y],id[x])+ansx;//聚焦點1
      	}
      	else{
      		ansy=query(1,id[x],id[y])+ansy;//聚焦點1
      	}
      	swap(ansx.lx,ansx.rx);//聚焦點2
      	ansx=ansx+ansy;
      	printf("%d\n",ansx.w);
      }
      

      \(ansx \ 和 \ ansy \ 中只需注意轉移 \ lx,rx,w \ 的值\)

      圖例

      箭頭表示跳躍的路程

      加粗的點為重鏈的 \(top\)

      數字為節點的時間戳

      因為\(dep[fy]>dep[x]\)

      所以

      聚焦點1

      因為\(id[x]>id[y]\)

      所以

      因為\(query()\)表示

      \(x\)\(7\)

      \(ansx\)往上跳

      所以為 \(ansx=query()+ansx\)

      \(ansy同理\)

      然后是最后的合并,\(x,y都在同一個節點上\)

      聚焦點2

      這時候我們發現, \(ansx.lx\) 表示的為 \(3\) 的時間戳,\(ansy.lx\)表示的為 \(4\) 的時間戳,而根據我們的合并。

      tree operator +(const tree&a)const{//重載運算符,區間合并
      	tree res;
      	res.l=l;
      	res.lx=lx;//左兒子的左端點是區間左端點
      	res.r=a.r;
      	res.rx=a.rx;//右兒子的右端點是區間右端點
      	res.w=w+a.w;
      	if(rx==a.lx&&rx){//統計相鄰點的答案 warning 當沒有時間戳時不統計答案 
      		res.w++;
      	}
      	return res;
      }
      

      應該是\(ansx.rx\)\(ansy.lx\)表示相鄰點的時間戳。

      所以我們需要 \(swap(ansx.lx,ansx.rx)\)再進行 \(ansx=ansx+ansy\)

      完結撒花

      posted @ 2024-03-16 09:03  Qing_Nian  閱讀(70)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 津南区| 日本高清视频网站www| 亚洲精品一区二区制服| 日本中文一区二区三区亚洲| 国产不卡一区不卡二区| 免费无码毛片一区二三区| 97色伦97色伦国产| 99热久久这里只有精品| 国内精品无码一区二区三区| 成人免费无遮挡在线播放| 国产精品中文一区二区| 四虎永久播放地址免费| 亚洲国产欧美在线人成| 亚洲色欲在线播放一区| 色综合久久精品中文字幕| 高潮毛片无遮挡高清视频播放| 好爽毛片一区二区三区四| 欧美黑吊大战白妞| 天天做日日做天天添天天欢公交车| 男人用嘴添女人下身免费视频| 97一期涩涩97片久久久久久久 | 国产成人综合色视频精品| 国产成人一区二区三区视频免费| 一区二区三区无码免费看| 丁香婷婷色综合激情五月| 国产自国产自愉自愉免费24区| 18禁极品一区二区三区| 亚洲av成人一区在线| 亚洲三区在线观看内射后入| 亚洲高清有码中文字| 亚洲av永久无码精品天堂久久| 久久国产精品成人影院| 综合久青草视频在线观看| 亚洲第一综合天堂另类专| 亚洲色成人网站www永久四虎| 999精品全免费观看视频| 精品偷自拍另类精品在线| 无码人妻一区二区三区AV| 欧美日本激情| 亚洲中文字幕综合小综合| 熟女少妇精品一区二区|