1. 程式人生 > 實用技巧 >使用Linux Bridge 搭建vxlan 實現 虛擬機器跨物理機通訊

使用Linux Bridge 搭建vxlan 實現 虛擬機器跨物理機通訊

前言

之前暑假集訓的時候學習的演算法,由於學的時候就有點馬馬虎虎之後也根本沒用到過就基本忘乾淨了,沒想到今天比賽居然用到了……補上這一篇。

解說

什麼時候能用?

先明確這一問題。

似乎一般用在有一大堆詢問的時候且每次詢問涉及兩個變數,且可以通過其中一次詢問的結果推導到另一個詢問(概括能力有限,大約是這樣吧……)。

怎麼用?

雙指標

第一部分和雙指標相關,以洛谷 \(P1972\) 為例,大致題意就是給一個序列和一堆詢問,每次詢問這個序列上 \([l,r]\) 間有多少不同數字。

暴力的話肯定是開一個 \(cnt\) 陣列,每次詢問清空一下 \(cnt\) ,之後從 \(l\) 掃到 \(r\)

,遇見 \(cnt\)\(0\) 的數字答案就加一,之後將其 \(cnt\) 加一。

現在考慮用雙指標。假設我們的 \(L\) 指標最開始在 \(1\) 位置, \(R\) 指標最開始在 \(0\) 位置, \(ans\)\(0\) ,那麼我們就可以用指標去匹配每一個詢問區間,在移動指標的過程中順便更新答案。

假設有兩個詢問區間 \([1,9],[6,15]\)

(由於本人太菜了不會畫圖且沒什麼畫圖時間所以接下來的圖片和部分文字轉載自這篇部落格

假設這個序列是這樣子的:(其中 \(Q1\)\(Q2\) 是詢問區間)

我們發現 \(L\) 已經是第一個查詢區間的左端點,無需移動。現在我們將 \(R\)

右移一位,發現新數值 \(1\)

\(R\) 繼續右移,發現新數值 \(2\)

\(R\) 繼續右移,發現新數值 \(4\)

\(R\) 再次右移時,發現此時的新位置中的數值 \(2\) 出現過,數值總數不增:

以此類推,知道我們的 \(R\) 移動到了 \(Q1\) 的右端點上,至此我們就得到了第一個詢問的答案。

現在我們看一下 \(Q2\) 區間的情況:

首先我們發現, \(L\) 指標在 \(Q2\) 區間左端點的左邊,我們需要將它右移,同時刪除原位置的統計資訊。

\(L\) 右移一位到位置 \(2\) ,刪除位置 \(1\) 處的數值 \(1\) 。但由於操作後的區間中仍然有數值 \(1\)

存在,所以總數不減。

依然是以此類推移動左指標直到其和 \(Q2\) 的左端點重合同時維護資訊,之後以此類推移動右端點即可。這樣我們就可以用雙指標維護出答案了。

分塊

看完之後是不是覺得上面的雙指標沒有卵用?因為詢問的區間亂序給出的話我們的兩個指標就會在整個序列上 反 復 橫 跳 ,根本沒有任何優化效果。那麼只有一個解決辦法: 將詢問離線! ,這樣我們就可以將所有詢問排序減小指標移動範圍。但是問題在於如何排序?按左端點排?那麼我們的右指標仍然會在整個序列上 反 復 橫 跳 ,沒有任何優化效果。這時候就需要用到我們的分塊了。感性地理解 由於中國人的性情是喜歡折中的 所以這是一種折中的辦法,它使得左指標跳動的範圍不至於太大,右端點跳動的範圍也不至於太大。

我們將整個序列分成 \(s\) 個塊,之後將詢問按照如下格式進行排序:

bool operator < (const node &A) const{
	if(belong[l]!=belong[A.l]) return belong[l]<belong[A.l];
	return r<A.r;
}

之後就可以愉快的用雙指標處理了

跟分塊問題一樣塊的大小同樣會影響莫隊的時間複雜度, 簡單 計算一下就能發現在這個問題 \(s\) 大小取 \(\sqrt{n}\) 可以達到最優時間複雜度 \(\Theta(n\sqrt{n})\) 。其他時候塊的最優大小還是具體問題具體分析吧(本 \(juruo\) 也不大會所以咕咕咕了)。

注意

  1. 一般指標移動的時候應該遵循先擴後縮原則
  2. 注意 \(L\)\(R\) 的初始狀態帶來的影響

例題


解說

一眼看上去這誰能看出來是莫隊啊

結合部分分的提示簡單推一下式子就會發現:

\[S_{n+1}^m=2S_{n}^m-C_{n}^m \]

\[S_{n}^{m+1}=S_{n}^m+C_{n}^{m+1} \]

如果我們把 \(L\) 看作 \(m\)\(R\) 看作 \(n\) 就會發現它非常符合莫隊的特徵,完美解決!但是寫程式碼的時候要尤其注意上面提到的要注意的兩點,因為 \(n\) 必須大於等於 \(m\)所以不小心的話容易掛……

程式碼



#include<bits/stdc++.h>
#define re register
#define ll long long
using namespace std;
const int lzw=1e5+3,mod=1e9+7;
int t,fac[lzw],ine[lzw],ans,belong[lzw],Max,res[lzw],inv,id;
inline int power(int a,int p){
	int ans=1;
	while(p){
		if(p&1) ans=1LL*ans*a%mod;
		p>>=1;
		a=1LL*a*a%mod;
	}
	return ans;
}
inline int comb(int n,int m){
	return 1LL*fac[n]*ine[m]%mod*ine[n-m]%mod;
}
struct node{
	int l,r,id;
	bool operator < (const node &A) const{//這裡用了一個叫奇偶性優化的東西,大約就是說從一個塊跳下一個塊的右端點時可以快一些
		return (belong[l] ^ belong[A.l]) ? belong[l] < belong[A.l] : ((belong[l] & 1) ? r < A.r : r > A.r);
	}
}all[lzw];
inline void init(){
	int s=sqrt(Max);
	for(re int i=1;i<=Max;i++) belong[i]=(i-1)/s+1;
	sort(all+1,all+1+t);
}
inline int read(){
	int f=1,x=0;char c=getchar();
	while(!isdigit(c)) f=(c=='-'?-1:1),c=getchar();
	while(isdigit(c)) x=x*10+c-'0',c=getchar();
	return f*x;
}
int main(){
	freopen("sum.in","r",stdin);
	freopen("sum.out","w",stdout);
	id=read(),t=read();
	for(re int i=1;i<=t;i++) all[i].r=read(),all[i].l=read(),all[i].id=i,Max=max(Max,all[i].r);
	init();
	fac[0]=1;
	for(re int i=1;i<=lzw-3;i++) fac[i]=1LL*fac[i-1]*i%mod;
	ine[lzw-3]=power(fac[lzw-3],mod-2);
	for(re int i=lzw-3;i;i--) ine[i-1]=1LL*ine[i]*i%mod;
	inv=power(2,mod-2);
	int l=0,r=1,ans=1;
	for(re int i=1;i<=t;i++){
		int ql=all[i].l,qr=all[i].r;
		while(r<qr) ans=(2LL*ans-comb(r,l)+mod)%mod,r++;
		while(l>ql) ans=((ll)ans-comb(r,l)+mod)%mod,l--;
		while(r>qr) ans=((ll)ans+comb(r-1,l))%mod*inv%mod,r--;
		while(l<ql) ans=((ll)ans+comb(r,l+1))%mod,l++;
		res[all[i].id]=ans;
	}
	for(re int i=1;i<=t;i++) printf("%d\n",res[i]);
	return 0;
}

關於之間複雜度問題,郭巨佬認為塊的大小為\(\sqrt{q}\)時最佳,我則認為大小為根號\(n\)的最大值時最佳,結果好像交上去後後者更好……
幸甚至哉,歌以詠志。