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

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

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

      狀壓 dp

      狀壓dp

      簡介

      狀壓 \(dp\) 就是把每個元素是否存在于一個集合中的情況用一個 二進制數表示,來作為 \(dp\) 的一個階段,通常我們會利用計算機超快的位運算對這個集合進行操作。

      狀壓 \(dp\) 主要分為以下兩類:

      1.集合類

      2.棋盤類(輪廓線 \(dp\)

      其中,集合類中有一類叫做子集枚舉,會在后文講到

      集合類

      luogu P10447 最短 Hamilton 路徑

      此題為經典的 \(NP\) 問題——旅行商問題,\(NP\) 問題沒有多項式的復雜度解法

      思路:

      不難想到純暴力解法:

      求出 \(n\) 個點的全排列,每個全排列就是一條路徑,一共 \(n!\) 條路徑,每條路徑計算距離總和需要加 \(n\) 次,復雜度為 \(O(n \times n!)\)

      題目中 \(n \le 20\),最多 \(20*20!=48,658,040,163,532,800,000 \approx 4.9 \times 10^{19}\),不難發現直接升天了,所以我們需要更好的解法。

      定義 \(f_{i,s}\) 表示最后一個走到的點是 \(i\)(下標從 \(0\) 開始),已經走過的點所構成的集合二進制為 \(s\) 時,所走的最小距離。

      轉移(刷表法):后文的公式都是用以數學的表示法寫的,具體的代碼表示請看代碼

      \[f_{i,s \cup \{i\}}=\min_{i\notin s \operatorname{and} j \in s} (f_{j,s}+dis_{j,i}) \]

      初始化:\(f_{0,1}=0\)

      答案:\(f_{n-1,2^n-1}\)

      復雜度:一共會遍歷 \(2^n\) 個集合狀態,每個狀態要枚舉 存在以及不存在 \(s\) 中的點,為 \(n^2\),總復雜度:\(O(n^2 \times 2^n)\),當 \(n=20\) 時,\(20^2 \times 2^{20}=419,430,400 \approx 4 \times 10^8\),能夠接受,然后就過了。

      代碼:

      #include<bits/stdc++.h>
      using namespace std;
      const int N=20;
      int n,ans=0x3f3f3f3f,dis[N][N],f[N][1<<N];
      int main(){
      	cin>>n;
      	for(int i=0;i<n;i++){
      		for(int j=0;j<n;j++){
      			cin>>dis[i][j];
      		}
      	}
      	memset(f,0x3f,sizeof(f));
      	f[0][1]=0;
      	for(int s=1;s<1<<n;s++){
      		for(int i=0;i<n;i++){
      			if(s&(1<<i)) continue;//找一個不在 s 中的點 i
      			for(int j=0;j<n;j++){
      				if(!(s&(1<<j))) continue;//找一個在 s 中的點 j
      				f[i][s|(1<<i)]=min(f[i][s|(1<<i)],f[j][s]+dis[j][i]);
      			}
      		}
      	}
      	cout<<f[n-1][(1<<n)-1];
      	return 0;
      }
      

      luogu P1433 吃奶酪

      幾乎一樣,只是初始化和答案略微不同

      代碼:

      #include<bits/stdc++.h>
      using namespace std;
      const int N=15;
      int n;
      double ans,x[N],y[N],d[N][N],f[N][1<<N];
      int main(){
      	cin>>n;
      	for(int i=0;i<n;i++) cin>>x[i]>>y[i];
      	for(int i=0;i<n;i++){
      		for(int j=0;j<(1<<n);j++){
      			f[i][j]=10000000.0;
      		}
      	}
      	ans=10000000.0;
      	for(int i=0;i<n;i++){
      		for(int j=0;j<n;j++){
      			d[i][j]=sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));//預處理任意兩點間的距離
      		}
      	}
      	for(int i=0;i<n;i++){
      		f[i][1<<i]=sqrt(x[i]*x[i]+y[i]*y[i]);//初始化:從(0,0)走到(x_i,y_i)
      	}
      	for(int s=0;s<1<<n;s++){
      		for(int i=0;i<n;i++){
      			if((s&(1<<i))==0) continue;
      			for(int j=0;j<n;j++){
      				if(s&(1<<j)) continue;
      				f[j][s|(1<<j)]=min(f[j][s|(1<<j)],f[i][s]+d[i][j]);
      			}
      		}
      	}
      	for(int i=0;i<n;i++) ans=min(ans,f[i][(1<<n)-1]);//以任意點為結尾都可以
      	printf("%.2lf",ans);
      	return 0;
      }
      

      接下來是兩道 \(CF\) 的題(給出洛谷的鏈接,提交需至\(CF\)

      AT_dp_o Matching

      本題只需將任意一個性別作為集合即可,這里以所有女性為集合:

      為了方便,記 \(cnt_s\) 表示 \(s\) 在二進制表示下的 \(1\) 的個數,題目中的好感度用 \(a_{i,j}\) 表示第 \(i\) 個男性與第 \(j\) 個女性的好感度:

      定義 \(f_{i,s}\) 表示到第 \(i\) 個男性,且女性集合為 \(s\) 時的完美配對的總方案數

      首先要明確一點,男性的順序是沒關系的,所以,我們可以假設到第 \(i\) 個男性就已經配對了 \(i\) 對男女,所以要求 \(cnt_s=i\) 時,才可以轉移

      轉移:

      \[f_{i+1,s \cup \{j\}}+=f_{i,s} (a_{i,j}=1 \operatorname{and} j \notin s \operatorname{and} cnt_s=i) \]

      事實上,我們可以用滾動數組滾掉 \(i\) 這一維,所以,轉移變為:

      \[f_{s \cup \{j\}}+=f_{s} (a_{cnt_s,j}=1 \operatorname{and} j \notin s) \]

      這里用 \(a_{cnt_s,j}=1\) 是因為當 \(s\) 確定時,\(cnt_s\) 就唯一確定了,不用再去遍歷 \(i\)

      初始化:一個女性都沒有的方案數為 \(1\)\(f_0=1\)

      答案:所有 \(n\) 對男女都已配對:\(f_{2^n-1}\)

      這里再多說一句:C++ 的 STL 中有 __builtin_popcount(s) (注意,最前面是兩個 "_")表示 \(s\) 在二進制表示下的 \(1\) 的個數,即上文的 \(cnt_s\),復雜度為常數很大的 \(O(1)\),但其實沒太大必要,手寫一個求 \(cnt_s\) 的函數也沒多難,復雜度為 \(O(logn)\)

      記得取模!!!

      代碼:

      #include<bits/stdc++.h>
      using namespace std;
      const int N=21,mod=1e9+7;
      int n,a[N][N],f[1<<N];
      int main(){
      	cin>>n;
      	for(int i=0;i<n;i++){
      		for(int j=0;j<n;j++){
      			cin>>a[i][j];
      		}
      	}
      	f[0]=1;
      	for(int s=0;s<(1<<n);s++){
      		int cnt=0,z=s;
      		while(z) cnt+=z&1,z>>=1;
      		for(int i=0;i<n;i++){
      			if(!(s&(1<<i))&&a[cnt][i]){
      				f[s|(1<<i)]=(f[s|(1<<i)]+f[s])%mod;
      			}
      		}
      	}
      	cout<<f[(1<<n)-1];
      	return 0;
      }
      

      AT_dp_u Grouping

      這是一道 子集枚舉 的板題,題目十分簡單,但是需要介紹一下子集枚舉,先說本題思路,再介紹子集枚舉。

      思路:

      定義 \(f_S\) 表示選的兔子分成若干個組,這些兔子構成的二進制集合為 \(S\) 的最大得分

      轉移:

      \[f_{S}=\max_{T \in S}(f_{T}+f_{ \complement _{\mathbb S}T} ) \]

      初始化:

      \[\forall S \in[0,2^n-1],f_{S}=\sum_{i \in S} \sum_{j \in S \operatorname{and} j<i} a_{i,j} \]

      答案:\(f_{2^n-1}\)

      以上為本題思路,接著介紹子集枚舉:

      子集枚舉

      代碼思路

      從剛才的轉移方程來看,我們要枚舉 \(S\) 的所有子集 \(T\),才能進行狀態轉移,代碼怎么實現?

      有一個很巧妙的方法:for(int T = S ; T >= 0 ; T = (T-1) & S)

      分析一下,首先,代表 \(T\) 的二進制數肯定比代表 \(S\) 的二進制數小,由子集的性質顯然可知,所以初始 T=S,接著 \(T\) 肯定要不小于零,所以 T>=0

      最后是最重要的一步,我們已經知道了 \(T\) 肯定比 \(S\) 小,所以可以先讓 \(T--\),但不難發現,要是 \(S=(110)_2\),令T=ST--\(T\) 就會變成 \((101)_2\),顯然不是 \(S\) 的子集,怎么辦呢?

      只要 \(T\&=S\) 就好了,因為 \(\&\) 的性質,所以 \(T\) 就會變成只含 \(S\) 中的樣子,剛才的例子就會變成:\(T=(100)_2\),所以就有了 \(T=(T-1)\&S\)

      復雜度:\(O(3^n)\)

      證明很有趣:

      \(n\) 個數,先枚舉其所有的集合,可以分為元素個數分別為 \(0,1,2,3...n\) 的集合,對于每種大小為 \(k\) 的集合,我們記為 \(k\) 類集合(作者自己編的名字),相當于從 \(n\) 個數中選了 \(k\) 個數,所以有 \(C_{n}^{k}\) 種選擇,即第 \(k\) 類集合有這么多個

      接著,對于每個第 \(k\) 類集合,它的子集個數一共有 \(2^k\) 個,所以,第 \(k\) 類集合一共要枚舉 \(C_{n}^{k} \times 2^k\) 個子集,那么對于 \(k \in [0,n]\),一共要枚舉的子集數就為:

      \[cnt =\sum_{k=0}^n C_{n}^{k} \times 2^{k} = \sum_{k=0}^{n} C_{n}^{k} \times 2^k \times 1^{n-k} \]

      不難發現,我們給原式湊了一個 \(\times 1^{n-k}\) 以后,原式的值不變,但是最后面的式子變成了我們所熟悉的 二項式定理,所以:

      \[cnt=(2+1)^n=3^n \]

      故復雜度為 \(O(3^n)\)

      最后給出本題代碼:

      代碼:

      #include<bits/stdc++.h>
      using namespace std;
      typedef long long ll;
      const int N=16;
      int n;
      ll a[N][N],f[1<<N];
      int main(){
      	cin>>n;
      	for(int i=0;i<n;i++){
      		for(int j=0;j<n;j++){
      			cin>>a[i][j];
      		}
      	}
      	for(int s=0;s<(1<<n);s++){
      		for(int i=0;i<n;i++){
      			for(int j=0;j<i;j++){
      				if((s&(1<<i))&&(s&(1<<j))) f[s]+=a[i][j];//初始化
      			}
      		}
      	}
      	for(int s=1;s<(1<<n);s++){
      		for(int t=s;t;t=(t-1)&s){//子集枚舉
      			f[s]=max(f[s],f[t]+f[s^t]);
      		}
      	}
      	cout<<f[(1<<n)-1];
      	return 0;
      }
      

      棋盤類(輪廓線dp)

      首先,要知道,什么是輪廓線?

      這里直接給出板題,方便解釋:

      luogu P1896 [SCOI2005] 互不侵犯

      眾所周知,經典的八皇后問題用到了 \(dfs\) 求解(luogu P1219 [USACO1.5] 八皇后 Checker Challenge),本題的限制要求和八皇后很像,但還是有很大區別,八皇后問題是求皇后的擺放方案,而本題是求合法方案數并且由于皇后和國王的走法不同,導致前者需用 \(dfs\) 求解,而后者可用狀壓 \(dp\) 求解。

      給出下圖:


      上圖中,紅色圓圈表示放了國王的位置,藍色方塊表示國王的攻擊范圍(即不能放國王的位置),紅色的折現就是下一個國王能放的位置的邊界(要在紅線的下面或右邊),因此,紅線就是輪廓線。輪廓線 \(dp\) 的階段是行的編號,\(狀態就是輪廓線\)

      不難發現,我們可以用一個 二進制數表示第 \(i\) 的輪廓線(第 \(j\) 表示的就是從右往左數第 \(j\) 個格子是否能放新的國王,這里的 \(j\) 是從 \(0\) 開始的)

      所以,我們有了動規的的狀態:

      定義 \(f_{i,s,j}\) 表示第 \(i\) 行,這一行的輪廓線為 \(s\) ,且已經放了 \(j\) 個國王的方案數

      轉移:

      \[f_{i,s,j}= \sum_{t\cap s=\varnothing} \sum_{c=0}^{k}f_{i-1,t,c} \]

      上面的方程中,\(t\) 表示上一行的輪廓線,而 t&s==0 表示第 \(i\) 行的輪廓線 \(s\) 與上一行的輪廓線 \(t\) 沒有沖突,即國王沒有互相沖突,\(c\) 表示到了上一行已經放了 \(c\) 個國王,這里有點類似背包

      初始化:一個國王都沒放:\(f_{0,0,0}=1\)

      答案:

      \[\sum_{s=0}^{2^n-1} f_{n,s,k} \]

      因為我們不知道最后一行會擺成什么樣子,所以要遍歷最后一行的所有輪廓線,然后求和。

      但是其實,上述的說法有問題。嚴格來說,我們枚舉的 \(s\)\(t\) 并不是輪廓線,而是每一行的國王擺放的情況,只不過,t&s==0 的這個條件,讓相當于求出了輪廓線,這里需要注意,但是上文就這樣吧,下文會改變說法的

      然后本題就做完了,但別著急,對于這種輪廓線,我們有優化:

      不難發現,對于同一行的狀態 \(s\) ,有些肯定不合法,例如 \((1101101)_2\),(\(1\) 表示放了國王)很明顯,按照題意,是不允許同一行的國王相鄰的(事實上是八連通范圍內都不能相鄰),所以,我們可以在開始時,先處理出來所有可能的狀態 \(s\),即把所以有相鄰 \(1\)\(s\) 全部去掉,接著每行的狀態只需枚舉預處理出的合法的 \(s\) 即可。

      這樣,我們就優化了時間,此時,\(f_{i,s,j}\) 中的 \(s\) 表示為第 \(s\) 個可行的狀態 \(s\),事實上,空間的話,可以輸出一下當 \(n=9\) 時,可行的 \(s\) 數,然后 \(f\) 數組的第二維開那么大就好了,這里直接給出,\(n=9\) 時,合法的狀態數為 \(89\),和 \(2^9=512\) 差了一個數量級,當然,本題就算開了 \(2^9\) 也不會 \(MLE\),所以代碼中直接開了 \(2^9\) 大小的數組。

      下面給出的是刷表法,跟上面的填表法幾乎一樣(狀壓還是刷表更好)

      還有一點要說,預處理時怎么判斷 \(s\) 是否合法?即 \(s\) 的二進制表示是否有相鄰的 \(1\)?只需判斷 \(s \&(s>>1)\)\(s \& (s<<1)\) 是否都為 \(0\) 即可!

      代碼:

      #include<bits/stdc++.h>
      using namespace std;
      typedef long long ll;
      const int N=9;
      int n,k,cnt,ok[1<<N],cnts[1<<N];//用ok數組記錄可行的s,cnts數組記錄該可行狀態s二進制下1的個數,即這種狀態放了cnts個國王
      ll f[N+1][1<<N][N*N+1],ans;
      void init(){
      	for(int s=0;s<1<<n;s++){
      		if((s&(s<<1))||(s&(s>>1))) continue;//不合法
      		ok[cnt]=s;
      		int z=s;
      		while(z) cnts[cnt]+=1&z,z>>=1;
      		cnt++;
      	}
      }
      bool is(int a,int b){//判斷兩行的狀態是否沖突
      	return (a&b)||((a<<1)&b)||((a>>1)&b);
      }
      int main(){
      	cin>>n>>k;
      	init();//預處理所有的合法狀態
      	f[0][0][0]=1;
      	for(int i=0;i<n;i++){
      		for(int j=0;j<cnt;j++){
      			for(int r=0;r<cnt;r++){
      				if(is(ok[j],ok[r])) continue;
      				for(int c=0;c<=k;c++){
      					if(cnts[r]+c>k) continue;
      					f[i+1][r][cnts[r]+c]+=f[i][j][c];
      				}
      			}
      		}
      	}
      	for(int i=0;i<cnt;i++) ans+=f[n][i][k];
      	cout<<ans;
      	return 0;
      }
      

      luogu P1879 [USACO06NOV] Corn Fields G

      來一道練習題,但有點不一樣,本題給出一張地圖,有些點不能放奶牛,怎么辦?有個巧妙的方法,對于第 \(i\) 行給出的輸入,記 \(t_i\) 在二進制下表示該行的空地和草地的情況,空地就讓二進制的那一位為 \(1\),草地就為 \(0\)。枚舉第 \(i\) 行的奶牛的擺放狀態時若 (t[i]&ok[j])!=0,則說明,有奶牛在空地上,不合法,就跳過

      代碼:

      #include<bits/stdc++.h>
      using namespace std;
      const int N=12,mod=1e8;
      int n,m,cnt,ans,t[N+1],f[N+1][1<<N],ok[1<<N];//本題不用 cnts 數組,放幾頭奶牛都可以
      void init(){
      	for(int s=0;s<1<<m;s++){
      		if((s&(s<<1))||((s>>1)&s)) continue;
      		ok[++cnt]=s;
      	}
      }
      int main(){
      	cin>>n>>m;
      	init();//依舊預處理所有可行的狀態s
      	for(int i=1;i<=n;i++){
      		for(int j=0;j<m;j++){
      			int k;
      			cin>>k;
      			t[i]=t[i]<<1|(1-k);
      		}
      	}
      	for(int i=1;i<=cnt;i++) if(!(ok[i]&t[1])) f[1][i]=1;
      	for(int i=2;i<=n;i++){
      		for(int j=1;j<=cnt;j++){
      			if(ok[j]&t[i-1]) continue;
      			for(int k=1;k<=cnt;k++){
      				if((ok[j]&ok[k])||(ok[k]&t[i])) continue;
      				f[i][k]=(f[i][k]+f[i-1][j])%mod;
      			}
      		}
      	}
      	for(int j=1;j<=cnt;j++) ans=(ans+f[n][j])%mod;
      	cout<<ans;
      	return 0;
      }
      

      luogu P2704 [NOI2001] 炮兵陣地

      本題是前幾題的綜合,很有價值,既要預處理所有合法的狀態 \(s\),還要預處理每個 \(s\) 的二進制下 \(1\) 的個數,以及空間和時間優化。

      本題要看前兩行的狀態,所以定義 \(f_{i,s1,s2}\) 表示到了第 \(i\) 行,這一行狀態為 \(s1\),上一行狀態為 \(s2\) 的最多能放的炮兵數。

      \(is1(i,s)\) 表示狀態 \(s\) 是否能滿足第 \(i\) 行的地形,不滿足返回\(true\)\(is2(s1,s2,s3)\) 表示 \(s1\)\(s2\)\(s3\) 是否有至少有兩個是沖突的,沖突則返回 \(true\)\(cnt_{s}\) 表示 \(s\) 在二進制表示下 \(1\) 的個數

      轉移:

      \[\forall i \le n-2 \operatorname{and}is1(i+2,s3) \operatorname{and} is2(s1,s2,s3),f_{i+2,s3,s2,}=\max_{is1(i,s1) \operatorname{and}(i+1,s2)} (f_{i+1,s2,s1}+cnt_{s3}) \]

      答案:

      \[ans=\max_{is1(n,s1) \operatorname{and} is1(n-1,s2) } f_{n,s1,s2} \]

      初始化,需要初始化第一行和第二行,具體實現的看代碼吧,這里就不寫出來了

      注意,本題有 \(hack\) 數據:\(n=1\) ,需要特判。還有,直接開\(f[100][1<<10][1<<10]\) 就算是 \(int\) 也會 \(MLE\),所以這就要用到上文提到的只開合法的狀態的數量大小,需要自己在本地輸出一下最大的數量,然后開那么大,開 \(65\) 是夠用的

      代碼:

      #include<bits/stdc++.h>
      using namespace std;
      typedef long long ll;
      const int N=10;
      int n,m,cnt,ok[1<<N],t[105],cnts[1<<N],ans,f[105][65][65];
      void init(){
      	for(int s=0;s<1<<m;s++){
      		if((s&(s>>2)||(s&(s>>1)))) continue;
      		ok[++cnt]=s;
      		int z=s;
      		while(z) cnts[cnt]+=1&z,z>>=1;
       	}
      }
      int main(){
      	cin>>n>>m;
      	init();//依舊可以預處理所有合法狀態
      	for(int i=1;i<=n;i++){
      		for(int j=0;j<m;j++){
      			char c;
      			cin>>c;
      			t[i]=t[i]<<1|(c=='P'?0:1);
      		}
      	}
      	for(int i=1;i<=cnt;i++){
      		if(ok[i]&t[1]) continue;
      		f[1][i][0]=cnts[i];//預處理第一行
      		ans=max(f[1][i][0],ans);//處理n=1時的情況
      		if(ok[i]&t[1]) continue;
      		for(int j=1;j<=cnt;j++){
      			if(ok[j]&t[2]) continue;
      			if(ok[i]&ok[j]) continue;
      			f[2][j][i]=cnts[i]+cnts[j];
      		}
      	}
      	for(int i=1;i<=n;i++){
      		for(int s1=1;s1<=cnt;s1++){
      			if(ok[s1]&t[i]) continue;
      			for(int s2=1;s2<=cnt;s2++){
      				if(ok[s2]&t[i+1]) continue;
      				if(ok[s1]&ok[s2]) continue;
      				for(int s3=1;s3<=cnt;s3++){
      					if(ok[s3]&t[i+2]) continue;
      					if(ok[s1]&ok[s3]) continue;
      					if(ok[s2]&ok[s3]) continue;
      					f[i+2][s3][s2]=max(f[i+2][s3][s2],f[i+1][s2][s1]+cnts[s3]);
      				}
      			}
      		}
      	}
      	for(int i=1;i<=cnt;i++){
      		for(int j=1;j<=cnt;j++){
      			if(ok[i]&t[n]) continue;
      			if(ok[j]&t[n-1]) continue;
      			if(ok[i]&ok[j]) continue;
      			ans=max(ans,f[n][i][j]);
      		}
      	}
      	cout<<ans;
      	return 0;
      }
      

      最后,給出幾道練習題:

      luogu P3112 [USACO14DEC] Guard Mark G

      luogu P2915 [USACO08NOV] Mixed Up Cows G

      今天先更到這里,之后會繼續做狀壓 \(dp\),遇到好題會繼續寫的,qwq。

      posted @ 2025-11-05 10:13  czh(抽紙盒)  閱讀(2)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲欧美中文字幕5发布| 极品无码国模国产在线观看 | 阿拉善盟| 精品国产av一区二区果冻传媒| 少妇人妻真实偷人精品| 欧美影院成年免费版| 中文国产日韩欧美二视频| 久久亚洲精品中文字幕馆| 精品国产乱子伦一区二区三区| 国产亚洲精品AA片在线爽| 国产精品毛片在线看不卡| 中文人妻| 亚洲欧美高清在线精品一区二区| 日本免费一区二区三区久久| 亚洲AV永久无码嘿嘿嘿嘿| 樱桃视频影院在线播放| 福利网午夜视频一区二区| 久久久久国产一区二区| 亚洲av综合色一区二区| 国产成人精品亚洲一区二区| 麻豆成人传媒一区二区| 亚洲性无码av在线| 亚洲激情在线一区二区三区| 亚洲色成人一区二区三区| 昭通市| 污污网站18禁在线永久免费观看| 视频二区中文字幕在线| 欧美xxxxx高潮喷水| 精品无码久久久久国产动漫3d| 国产精品一区在线蜜臀| 女人扒开腿让男人桶到爽| 国产精品综合在线免费看| 精品中文字幕人妻一二| 精品国产av一区二区三区| 日本五十路熟女一区二区| 四虎国产精品免费久久久| 新巴尔虎右旗| 一二三三免费观看视频| 精品亚洲国产成人av制服| 国产色无码精品视频免费| 亚洲中文字幕久久精品品|