1. 程式人生 > 實用技巧 >外設驅動庫開發筆記21:BME680環境感測器驅動

外設驅動庫開發筆記21:BME680環境感測器驅動

大家好,在頹了一週思維題後,我們來扯一扯圓方樹。
首先我們先看道例題。

Codeforces 487E Tourists

題意:

有一張 \(n\) 個點 \(m\) 條邊的無向圖,點上有點權,\(q\) 次操作,每次操作有以下兩種型別:

  • "C \(x\ y\)",將 \(x\) 點的點權改為 \(y\)
  • "A \(x\ y\)",求所以 \(x\to y\) 的簡單路徑上點權最小值的最小值。

\(1\leq n,m,q\leq 10^5\)

首先把握住關鍵資訊。本題的題眼顯然在這個“簡單路徑”上。簡單路徑意味著不能經過同一個點。
很自然地可以想到點雙連通分量。顯然,根據點雙連通分量的定義,在同一點雙連通分量中,我們可以走到其中點權最小的點並走到相鄰的點雙連通分量中,並且不會經過重複的點。
考慮縮點。不過直接縮點有一個問題,之前我們遇到的連通分量都是“強連通分量”或“邊雙連通分量”,對於這一類連通分量都有一個特點,那就是每個點恰好屬於一個強連通分量或邊雙連通分量。而有可能出現一個點屬於多個點雙連通分量的情況,故不能直接縮點。
那麼怎麼辦呢?就要先從點雙連通分量的性質開始說起了。
點雙連通分量,指不含割點的極大連通子圖。特別地,兩個點之間有一條邊的子圖也是點雙連通分量。
點雙連通分量有以下性質:

  1. 點雙連通分量以割點連線
  2. 每條邊必須恰好屬於一個點雙連通分量。
  3. 任意兩個點雙連通分量至多有一個公共點
  4. 同一點雙連通分量中任意兩點 \(u,v\) 之間簡單路徑的並集恰好等於整個點雙。

性質 1,2,3 都比較顯然,性質 4 粉兔神仙給出了嚴格證明,然鵝我看了半天一個字也沒看懂,有興趣自己去翻他的 blog。
那麼什麼是圓方樹呢?如果我們將原圖中的每一個點看作一個“圓點”,對每個點雙連通分量新建一個“方點”。對於每一個點雙連通分量,在其對應的方點與點雙當中每個”圓點“之間連邊,那麼得到的就是圓方樹。
比如下圖:

回到此題來。先 tarjan 求出點雙連通分量。圓點上的點權為對應點的 \(w_i\)

,方點上的點權為與其相連的圓點的點權的最小值。
那麼答案即為 \(x,y\) 之間點權值的最小值。
為什麼?設 \(P\)\(x\to y\) 的所有路徑經過的點的集合的並集,那麼答案顯然為 \(\min\limits_{u\in P}w_u\)
那麼 \(P\) 究竟是個什麼東西呢?隨便找一條 \(x,y\) 之間的路徑 \(T\),假設其經過的邊為 \(e_1,e_2,\dots,e_k\)
根據點雙連通分量的性質 2,這些邊可以被劃分到一個個點雙連通分量中。假設這 \(k\) 條邊總共屬於 \(m\) 個點雙連通分量,其中邊 \(e_{i_{j,1}},e_{i_{j,2}},\dots,e_{i_{j,c_j}}\)
屬於點雙連通分量 \(j\)
根據點雙連通分量的性質 4,這 \(c_j\) 條邊可以包含整個點雙連通分量 \(j\),也就是說,這個點雙連通分量的所有點都應當屬於 \(P\)
也就是說這 \(m\) 個點雙連通分量點集的並 \(\subseteq P\)
而不在這 \(m\) 個點雙連通分量中的點顯然不可能被訪問到,不然就違反了點雙連通分量的定義了。
故我們得到了一個很重要的性質:這 \(m\) 個點雙連通分量點集的並 \(=P\)

回到圓方樹上來,\(x,y\) 路徑上的方點顯然就對應這 \(m\) 個點雙連通分量,而每個方點上的權值是這個點雙中所有點權值的 \(\min\),故這 \(m\) 個方點權值的 \(\min\) 就是 \(\min\limits_{u\in P}w_u\)
用個樹剖維護一下就行了。
但這樣還是會被叉掉——考慮一張菊花圖,修改菊花圖上度數為 \(n-1\) 的點就要修改 \(n-1\) 個方點的權值,複雜度最壞為 \(n^2\log n\)
那麼有什麼辦法呢?
我們可以在每個方點開一個 multiset,儲存它所有兒子的權值。則方點的權值即為 multiset 中的最小值。這樣,修改一個圓點時,就只需要改動它父親的multiset即可。
然後在查詢的時候,若兩點之間的 LCA 是個方點,則將答案與 LCA 的父親(必定是個圓點)的權值取 \(\min\) 即可。

時間複雜度線性對數方。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fz(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
#define ffe(it,v) for(__typeof(v.begin()) it=v.begin();it!=v.end();it++)
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
template<typename T> void read(T &x){
	x=0;char c=getchar();T neg=1;
	while(!isdigit(c)){if(c=='-') neg=-1;c=getchar();}
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	x*=neg;
}
const int MAXN=1e5;
int n,m,qu,w[MAXN*2+5],cnt;
namespace segtree{
	struct node{int l,r,val;} s[MAXN*8+5];
	void build(int k,int l,int r){
		s[k].l=l;s[k].r=r;s[k].val=0x3f3f3f3f;if(l==r) return;
		int mid=(l+r)>>1;build(k<<1,l,mid);build(k<<1|1,mid+1,r);
	}
	void modify(int k,int x,int v){
		if(s[k].l==s[k].r){s[k].val=v;return;}
		int mid=(s[k].l+s[k].r)>>1;
		if(x<=mid) modify(k<<1,x,v);
		else modify(k<<1|1,x,v);
		s[k].val=min(s[k<<1].val,s[k<<1|1].val);
	}
	int query(int k,int l,int r){
		if(l<=s[k].l&&s[k].r<=r) return s[k].val;
		int mid=(s[k].l+s[k].r)>>1;
		if(r<=mid) return query(k<<1,l,r);
		else if(l>mid) return query(k<<1|1,l,r);
		else return min(query(k<<1,l,mid),query(k<<1|1,mid+1,r));
	}
}
namespace tree{
	int hd[MAXN*4+5],nxt[MAXN*4+5],to[MAXN*4+5],ec=0;
	void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
	int siz[MAXN*2+5],fa[MAXN*2+5],wson[MAXN*2+5],dep[MAXN*2+5];
	int top[MAXN*2+5],dfn[MAXN*2+5],tim=0;
	void dfs1(int x,int f){
		fa[x]=f;siz[x]=1;
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e];if(y==f) continue;
			dep[y]=dep[x]+1;dfs1(y,x);siz[x]+=siz[y];
			if(siz[y]>siz[wson[x]]) wson[x]=y;
		}
	}
	void dfs2(int x,int tp){
		dfn[x]=++tim;top[x]=tp;
		if(wson[x]) dfs2(wson[x],tp);
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e];if(y==fa[x]||y==wson[x]) continue;
			dfs2(y,y);
		}
	}
	multiset<int> st[MAXN+5];
	void prework(){
		dfs1(1,0);dfs2(1,1);segtree::build(1,1,cnt);
		for(int i=2;i<=n;i++) w[fa[i]]=min(w[fa[i]],w[i]),st[fa[i]-n].insert(w[i]);
		for(int i=1;i<=n-cnt;i++) st[i].insert(0x3f3f3f3f);
//		for(int i=1;i<=cnt;i++) printf("%d %d %d %d %d %d\n",fa[i],siz[i],dep[i],wson[i],top[i],dfn[i]);
		for(int i=1;i<=cnt;i++) segtree::modify(1,dfn[i],w[i]);
	}
	void change(int x,int v){
		if(x!=1){
			st[fa[x]-n].erase(st[fa[x]-n].find(w[x]));st[fa[x]-n].insert(v);
			w[fa[x]]=*st[fa[x]-n].begin();segtree::modify(1,dfn[fa[x]],w[fa[x]]);
		} w[x]=v;segtree::modify(1,dfn[x],w[x]);
	}
	int query(int x,int y){
		if(dep[x]<dep[y]) swap(x,y);
		int ret=0x3f3f3f3f;
		while(top[x]!=top[y]){
			if(dep[top[x]]<dep[top[y]]) swap(x,y);
			chkmin(ret,segtree::query(1,dfn[top[x]],dfn[x]));
			x=fa[top[x]];
		}
		if(dep[x]<dep[y]) swap(x,y);
		chkmin(ret,segtree::query(1,dfn[y],dfn[x]));
//		printf("%d\n",y);
		if(y>n) chkmin(ret,w[fa[y]]);
		return ret;
	}
}
namespace graph{
	int hd[MAXN*2+5],nxt[MAXN*2+5],to[MAXN*2+5],ec=0;
	void adde(int u,int v){to[++ec]=v;nxt[ec]=hd[u];hd[u]=ec;}
	int dfn[MAXN+5],low[MAXN+5],stk[MAXN+5],top=0,tim=0;
	void tarjan(int x){
		dfn[x]=low[x]=++tim;stk[++top]=x;
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e];if(!dfn[y]){
				tarjan(y);low[x]=min(low[x],low[y]);
				if(low[y]>=dfn[x]){
					cnt++;w[cnt]=0x3f3f3f3f;int z;
					do {
						//printf("%d ",stk[top]);
						z=stk[top];tree::adde(cnt,z);tree::adde(z,cnt);top--;
					} while(z!=y);
					tree::adde(cnt,x);tree::adde(x,cnt);//printf("%d\n",x);
				}
			} else low[x]=min(low[x],dfn[y]);
		}
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&qu);cnt=n;
	for(int i=1;i<=n;i++) scanf("%d",&w[i]);
	for(int i=1;i<=m;i++){
		int u,v;scanf("%d%d",&u,&v);
		graph::adde(u,v);graph::adde(v,u);
	} graph::tarjan(1);tree::prework();
	while(qu--){
		char opt[3];int x,y;scanf("%s%d%d",opt+1,&x,&y);
		if(opt[1]=='C') tree::change(x,y);
		else printf("%d\n",tree::query(x,y));
	}
	return 0;
}
/*
9 9 1
2
4
8
7
7
6
7
8
10
2 1
1 7
3 2
4 3
5 4
5 6
6 7
4 8
2 9
A 4 3
*/

其它習題:

洛谷 P4630 [APIO2018] Duathlon 鐵人兩項

容易想到固定住 \(s,f\),計算有多少個符合要求的 \(c\)
\(c\) 的個數就是 \(s\)\(f\) 簡單路徑經過的點的並集的大小 \(-2\)\(s\neq c,f\neq c\))。
那麼這個並集大小怎麼計算呢?
根據之前的推論,這個並集就是 \(s\)\(f\) 之間所有 \(m\) 個點雙點集的並集,故其大小為這 \(m\) 個點集的大小之和 \(sum\)
欸等等……好像有什麼問題。有的點雙之間有公共點,而這個公共點在兩個點雙中都會被算一次。故還需減掉公共點的個數。而只有相鄰經過的點雙之間才會有公共點,故公共點的個數為 \(m-1\)。所以並集的大小就是 \(sum-(m-1)\)
如果我們把方點的權值賦為這個點雙中點的個數,圓點的權值賦為 \(-1\)。考慮 \(s\)\(f\) 之間路徑上所有點的權值和。方點貢獻的權值之和為 \(sum\),圓點共 \(m+1\) 個,每圓點貢獻 \(-1\) 的權值,故總權值為 \(sum-(m+1)\),剛好就等於 \(s\)\(f\) 簡單路徑經過的點的並集的大小 \(-2\)
故題目轉化為:求圓點之間兩兩路徑上的權值之和。這個隨便亂搞搞就行了。