插頭dp:
\(A:\)插頭dp是什么?
\(B:\)一種基於連通性狀態壓縮的動態規划問題
\(A:\)請問有什么應用呢?
\(B:\)各種網格覆蓋問題,范圍允許狀壓解決,常用於計算方案數與聯通塊權值
\(A:\)輪廓線與插頭呢???
\(B:\)輪廓線是狀壓的部分,用於解決插頭的情況,不同於常見的狀壓dp,為了更好地處理狀態,通常要要到括號表示法、最小表示法等,插頭是格與格中的轉移,可理解成將拼圖連接的位置
\(A:\)本文將講解哪些類型的題目呢?
\(B:\)單回路覆蓋方案數,多回路覆蓋方案數,骨牌覆蓋,聯通塊最大權,單回路最大權,最小聯通塊方案,特殊型覆蓋,多種限制的最大權
\(A:\)那我們開始吧!!
各種例題:
ps:以下的圖都並非唯一狀態,基於讓讀者更好理解采用最經典的圖例
模板: P5056 【模板】插頭dp
題目大意
給出n*m的方格,有些格子不能鋪線,其它格子必須鋪,形成一個閉合回路。問有多少種鋪法?n,m(2<=n,m<=12)
狀態:
此題面對一種狀態,只考慮格子上方(我們叫它下插頭)和左方(我們叫它右插頭)插頭情況,因為暫時只被這兩個插頭所影響,通過對插頭不同情況的處理完成狀態轉移
此格與下一格的輪廓線如下圖
狀態轉移實際如下
\(b1\) 、\(b2\)分別表示右插頭和下插頭
我們知道一條線,是有左端點和右端點的,\(0\)表示無插頭,\(1\)表示左端點,\(2\)表示右端點
來看這副圖,在\((3,4)\)輪廓線上有四個插頭,括號表示法狀態:(##)(##),四進制表示此狀態:\(10021002\),明明只出現\(0\) \(1\) \(2\),為什么不用三進制?因為位移更方便
以前的狀壓不是通常有就為\(1\)無就為\(0\)嗎?
那我們為什么這里要用括號匹配四進制,因為\(b1==2\) \(and\) \(b2==1\)和\(b1==1\) \(and\) \(b2==2\)的狀態產生的結果截然不同,不懂沒關系,慢慢來,后文自有介紹
當前點不能走(障礙)
\((1)\) 則\(!b1\) \(and\) \(!b2\)才能產生狀態轉移,有插頭就代表會走到該點,
不能走當然得兩邊都沒有插頭,,轉移后當然這兩小段插頭也為空,直接添加狀態就好了,這里就不放圖了,腦補\(qwq\)
當前點能走(非障礙必須走)
\((1)\) \(!b1\) \(and\) \(!b2\),則要加兩個插頭(一個左插頭,一個有插頭),確保該點走過
\((2)\) \(!b1\) \(and\) \(b2\)只有一個插頭,該插頭向下延伸,選擇直走或往右拐,等同於延續一個連通分量
此時插頭的括號狀態不變,通俗地說,就是插頭本來是幾轉移后也是幾
注意,插頭在輪廓線上的位置變了,如果直走\(b2\)的位置為\(0\),\(b1\)為原來的\(b2\)
\((3)\) \(b1\) \(and\) \(!b2\)則向右延伸,直走或向下拐,與\((2)\)同理
\((4)\) \(b1==1\) \(and\) \(b2==1\) 兩個插頭相遇,等同於合並兩個連通分量,下一次\(b1\)和\(b2\)的位置就為\(0\)了
但這里合並后並非直接刪去插頭即可,因為這樣就只剩下兩個右插頭了,起不了匹配的效果
得\(O(m)\)往右掃找另一邊匹配的右括號,讓兩個右括號匹配
而我們 為什么要匹配?
因為要求一條回路,\(b1==2\) \(and\) \(b2==1\)和\(b1==1\) \(and\) \(b2==2\)的狀態產生的結果截然不同(請看下文),必須要注意 括號匹配
\((5)\) \(b1==2\) \(and\) \(b2==2\) 和上面一樣\(O(m)\)往左掃找另一邊的左括號,讓兩個左括號匹配,腦補吧\(qwq\)
\((6)\) \(b1==2\) \(and\) \(b2==1\) 直接刪掉兩個插頭(閉合)就好了,不需要處理另一端的插頭了,兩邊的插頭會自動匹配\((1212->1002)\)
\((7)\) \(b1==1\) \(and\) \(b2==2\)出現這種狀態,達到了最終閉合狀態,當前點為終點更新答案,否則狀態不合法
本題利用\(hash\)和滾動節省空間,為方便與快捷用位運算實現四進制,具體細節看代碼,
或許你看到每行之間多了一個這樣的狀態轉移,在此提一下,相當於到每行第一個格子時,前面補一個空插頭,剩下的部分從上一行最后一個格子那里直接搬過來
for(LL j=1;j<=cnt[now];++j)
que[now][j]<<=2;
可能你會想,這八種情況,真的能把所有情況全表示出來嗎,我們來看一張圖
這種情況怎么來呢,是由\((3,4)\)的下插頭向下延伸與\((3,2)\)的下插頭一直向右延伸交由\((4,3)\)
然后由\((4)\),\((5)\),\((6)\),\((7)\)的某種轉移而來
當然,可以嚴格證明出所有狀態都能有上面的八種情況得到,因為只需要所有狀態都在情況內
My complete code:
#include<cstdio>
#include<cstring>
using namespace std;
typedef long long LL;
const LL maxn=13;
const LL hs=299987;
LL n,m,ex,ey,now,last,ans;
LL a[maxn][maxn],head[300000],next[2<<24],que[2][2<<24],val[2][2<<24],cnt[2],inc[13];
inline void init(){
scanf("%lld%lld",&n,&m);
for(LL i=1;i<=n;++i){
char s[100];
scanf(" %s",s+1);
for(LL j=1;j<=m;++j){
if(s[j]=='.'){
a[i][j]=1;
ex=i; ey=j;
}
}
}
inc[0]=1;
for(LL i=1;i<=13;++i)
inc[i]=inc[i-1]<<2;
}
inline void insert(LL bit,LL num){
LL u=bit%hs+1;
for(LL i=head[u];i;i=next[i]){
if(que[now][i]==bit){
val[now][i]+=num;
return;
}
}
next[++cnt[now]]=head[u];
head[u]=cnt[now];
que[now][cnt[now]]=bit;
val[now][cnt[now]]=num;
}
inline void work(){
cnt[now]=1; val[now][1]=1; que[now][1]=0;
for(LL i=1;i<=n;++i){
for(LL j=1;j<=cnt[now];++j)
que[now][j]<<=2;
for(LL j=1;j<=m;++j){
memset(head,0,sizeof(head));
last=now; now^=1;
cnt[now]=0;
for(LL k=1;k<=cnt[last];++k){
LL bit=que[last][k],num=val[last][k];
LL b1=(bit>>((j-1)*2))%4,b2=(bit>>(j*2))%4;
if(!a[i][j]){
if(!b1&&!b2)
insert(bit,num);
}else if(!b1&&!b2){
if(a[i+1][j]&&a[i][j+1])
insert(bit+inc[j-1]+inc[j]*2,num);
}else if(!b1&&b2){
if(a[i][j+1])
insert(bit,num);
if(a[i+1][j])
insert(bit-inc[j]*b2+inc[j-1]*b2,num);
}else if(b1&&!b2){
if(a[i+1][j])
insert(bit,num);
if(a[i][j+1])
insert(bit-inc[j-1]*b1+inc[j]*b1,num);
}else if(b1==1&&b2==1){
LL k1=1;
for(LL l=j+1;l<=m;++l){
if((bit>>(l*2))%4==1)
++k1;
if((bit>>(l*2))%4==2)
--k1;
if(!k1){
insert(bit-inc[j]-inc[j-1]-inc[l],num);
break;
}
}
}else if(b1==2&&b2==2){
LL k1=1;
for(LL l=j-2;l>=0;--l){
if((bit>>(l*2))%4==1)
--k1;
if((bit>>(l*2))%4==2)
++k1;
if(!k1){
insert(bit-inc[j]*2-inc[j-1]*2+inc[l],num);
break;
}
}
}else if(b1==2&&b2==1){
insert(bit-inc[j-1]*2-inc[j],num);
}else if(i==ex&&j==ey){
ans+=num;
}
}
}
}
}
int main(){
init();
work();
printf("%lld",ans);
return 0;
}
來放松一下吧
P5074 Eat the Trees
其實對於剛學插頭dp也較難完成,但建議讀者先獨立思考
題目大意
給出n*m的方格,有些格子不能鋪線,其它格子必須鋪,可以形成多個閉合回路。問有多少種鋪法?n,m(2<=n,m<=12)
上一題的轉化題,可以形成多個回路了,\(b1\) \(and\) \(b2\)時雖然\(1\)與\(2\) 實際上對整體情況有影響,但對狀態轉移無影響
怎么理解呢?上一題的括號匹配提過,再加上此題可以形成多條回路,所以實際情況肯定會有影響;至於狀態轉移,反正我們只需要把加方案數,自然無影響
此題不需要括號匹配,多條回路\(b1\) \(and\) \(b2\)實際閉合刪插頭,用二進制,\(1\)表示有插頭,\(0\)表示無插頭
ps:如果整個圖都沒有1,只有0,也算一種合法方案!
上一題放的是hash代碼,為了讓大家更熟悉,這里放個費空間的
#include<cstring>
#include<string>
#include<cstdio>
using namespace std;
typedef long long LL;
const LL maxn=13;
const LL hs=299987;
LL n,m,now,last,ans,kase,T,M,up;
LL a[maxn][maxn],inc[maxn],dp[maxn][maxn][1<<maxn];
inline LL read(){
LL x=0,f=1; char c=getchar();
while(c<'0'||c>'9'){
if(c=='-') f=-1; c=getchar();
}
while(c>='0'&&c<='9'){
x=x*10+c-'0'; c=getchar();
}return x*f;
}
inline void solve(){
memset(dp,0,sizeof(dp));
dp[0][m][0]=1;
for(LL i=1;i<=n;++i){
for(LL j=0;j<=up;++j)
dp[i][0][j<<1]=dp[i-1][m][j];
for(LL j=1;j<=m;++j){
for(LL k=0;k<=up;++k){
LL bit=k,num=dp[i][j-1][k];
LL b1=(bit>>(j-1))&1,b2=(bit>>(j))&1;
if(!a[i][j]){
if(!b1&&!b2)
dp[i][j][bit]+=num;
}else if(!b1&&!b2)
dp[i][j][bit+inc[j-1]+inc[j]]+=num;
else if(!b1&&b2){
if(a[i][j+1])
dp[i][j][bit]+=num;
if(a[i+1][j])
dp[i][j][bit+inc[j-1]-inc[j]]+=num;
}else if(b1&&!b2){
if(a[i][j+1])
dp[i][j][bit-inc[j-1]+inc[j]]+=num;
if(a[i+1][j])
dp[i][j][bit]+=num;
}else
dp[i][j][bit-inc[j-1]-inc[j]]+=num;
}
}
}
//printf("Case %lld: There are %lld ways to eat the trees.\n",++kase,dp[n][m][0]);
printf("%lld\n",dp[n][m][0]);
}
int main(){
T=read();
inc[0]=1;
for(LL i=1;i<=13;++i)
inc[i]=inc[i-1]<<1;
while(T--){
n=read(); m=read();
up=(1<<(m+1))-1;
for(LL i=1;i<=n;++i)
for(LL j=1;j<=m;++j)
a[i][j]=read();
solve();
}
return 0;
}/*
*/
P3886 [JLOI2009]神秘的生物
題目大意
n*n的帶權網格,求某個聯塊的最大和 n<=9
這里不需要形成回路,而是經典的聯通塊題,上面的方法無用武之地了(判斷聯通),通常我們采取最小表示法
\(n<=9\)用\(8\)進制(與四進制同理)表示,同一個聯通塊用相同數字表示,數字也只是暫時的而已,選擇一個塊后,由於可能會改變聯通狀態,要重新標記數字
此題的輪廓線有所不同,因為兩相鄰塊均選是直接聯通的,如下圖
每個方格有兩種基本狀態
不選
當然得考慮狀態合理,無下插頭或下插頭所在聯通塊還有其他插頭,否則下插頭被孤立而不形成聯通塊了
大家發現沒有?
這里每個狀態僅合理而已,並不能確定這是可取的最終狀態,因為最后得保證只有一個聯通塊(其實上文也),詳細請看\(update\)
那為什么不考慮右插頭呢?而要判斷下插頭呢?
因為下插頭以后再也不會判斷了,所以要考慮狀態合理,右插頭到下一行自然會考慮狀態
右插頭來自上一個轉移狀態,很可能形成新塊\(7\),故這樣轉移條件會變得嚴苛,最終答案通常會小於正確答案
選
\((1)\) \(!b1\) \(and\) \(!b2\)單獨形成新塊,此塊命名為7
\((2)\) \(b1\) \(or\) \(b2\) 此塊與插頭相連,更新聯通塊狀態,這就是偉大的最小表示法
s[j]=max(b1,b2);
for(LL l=1;l<=m;++l)
if(s[l]&&s[l]==min(b1,b2))
s[l]=max(b1,b2);
\(Ps\):在\(updateup\)時,只有當存在唯一聯通塊時才\(update\) \(ans\)
My complete code:
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
typedef long long LL;
const LL maxn=24;
const LL hs=299987;
LL n,ans=-2*1e9,now=0,last=1;
LL val[2][1<<maxn],que[2][1<<maxn],next[1<<maxn],head[300000],a[maxn][maxn],cnt[2],inc[maxn],s[maxn];
inline void insert(LL bit,LL num){
LL u=bit%hs+1;
for(LL i=head[u];i;i=next[i]){
if(que[now][i]==bit){
val[now][i]=max(val[now][i],num);
return;
}
}
next[++cnt[now]]=head[u];
head[u]=cnt[now];
que[now][cnt[now]]=bit;
val[now][cnt[now]]=num;
}
inline LL update(LL num){
LL belong[maxn];
memset(belong,0,sizeof(belong));
LL sum=0;
for(LL i=1;i<=n;++i)
if(s[i]){
if(belong[s[i]])
s[i]=belong[s[i]];
else
s[i]=belong[s[i]]=++sum;
}
LL bit=0;
for(LL i=1;i<=n;++i)
bit+=(s[i]<<inc[i-1]);
if(sum==1)
ans=max(ans,num);
return bit;
}
inline void get_bitset(LL bit){
for(LL i=1;i<=n;++i)
s[i]=(bit>>inc[i-1])%8;
}
inline void solve(){
insert(0,0);
for(LL i=1;i<=n;++i){
for(LL j=1;j<=n;++j){
swap(now,last);
cnt[now]=0;
memset(head,0,sizeof(head));
for(LL k=1;k<=cnt[last];++k){
get_bitset(que[last][k]);
LL b1=s[j-1],b2=s[j];
LL num=val[last][k];
LL sum=0;
s[j]=0;
for(LL l=1;l<=n;++l)
if(s[l]==b2)
++sum;
if(!b2 || sum)
insert(update(num),num);
num+=a[i][j];
get_bitset(que[last][k]);
if(!b1&&!b2)
s[j]=7;
else{
s[j]=max(b1,b2);
for(LL l=1;l<=n;++l)
if(s[l]&&s[l]==min(b1,b2))
s[l]=max(b1,b2);
}
insert(update(num),num);
}
}
}
}
int main(){
for(LL i=1;i<=10;++i)
inc[i]=i*3;
scanf("%lld",&n);
for(LL i=1;i<=n;++i)
for(LL j=1;j<=n;++j)
scanf("%lld",&a[i][j]);
solve();
printf("%lld",ans);
return 0;
}
P3190 [HNOI2007]神奇游樂園
題目大意
n*m的帶權方格,求最大權回路 2<=n<=100,2<=m<=6
和模板題差不多,在\(insert\)時加一個最大值,自己寫一遍吧,相信你能\(A\)掉的
核心代碼:
inline void insert(LL bit,LL num){
LL u=bit%hs+1;
for(LL i=head[u];i;i=next[i])
if(que[now][i]==bit){
val[now][i]=max(val[now][i],num);
return;
}
next[++cnt[now]]=head[u];
head[u]=cnt[now];
que[now][cnt[now]]=bit;
val[now][cnt[now]]=num;
}
\({}\)
\({}\)
以下這題由於斯坦納樹做法更簡單,不作例題講重點講,稍微提怎么求方案
P4294 [WC2008]游覽計划
題目大意
n*m的帶權網格,求聯通k個方格的最小權聯通塊方案(求某個方案而非方案數) n<=10 m<=10 k<=10
\(road[now][bit]=\)++\(tot\)表示\(val[now][val]\)最小時的狀態,和\(val\)與\(que\)一樣是滾動數組,因為之后就不需要了
\(w[tot]\)表示此時選或不選,\(pre[tot]\)表示上一個點的\(w\)下標,方便輸出方案數,在更新成功\(val[now][val]\)時同時更新
Insert:
inline void insert(LL y,LL pe,LL bit,LL num){
LL u=bit%hs+1;
for(LL i=head[u];i;i=next[i])
if(que[now][i]==bit){
if(val[now][i]>num){
val[now][i]=num;
pre[road[now][i]]=pe;
w[road[now][i]]=(bit>>inc[y-1])%8;
if(f)
tmp=road[now][i];
}
return;
}
next[++cnt[now]]=head[u];
head[u]=cnt[now];
que[now][cnt[now]]=bit;
val[now][cnt[now]]=num;
road[now][cnt[now]]=++tot;
pre[tot]=pe;
w[tot]=(bit>>inc[y-1])%8;
if(f)
tmp=tot;
}
P3272 [SCOI2011]地板
顯然,\(L\)型地板:拐彎且僅拐彎一次。
發現沒有,一個存在的插頭只有兩種狀態:拐彎過和沒拐彎過,因此我們這樣定義插頭:
0表沒有插頭,1表沒拐過的插頭,2表已經拐過的插頭。\(b1\)代表當前點的右插頭,\(b2\)代表當前點的下插頭
那么會有下面的幾種情況:
\((1)\) \(b1==0\) \(and\) \(b2==0\)
-
\(b1\)添加一個插頭\(1\),相當於加入一個從該點出發向下的\(L\)地板
-
\(b2\)添加一個插頭\(1\),相當於加入一個從該點出發向右的\(L\)地板
-
\(b1\),\(b2\)均改成插頭\(2\),相當於該點作為\(L\)地板的拐彎處
\((2)\) \(b1==0\) \(and\) \(b2==1\)
-
\(b1=1\),\(b2=0\) 相當於繼續直走,不拐彎,所以\(b1\)轉為\(b2\),\(b2\)此時無插頭了
-
\(b1=0\),\(b2=2\) 相當於向右拐
\((3)\) \(b1==1\) \(and\) \(b2==0\)
跟\((2)\)差不多
\((4)\) \(b1==0\) \(and\) \(b2==2\)
-
結束,將插頭去掉,如果為終點更新答案,否則去掉插頭則可
-
\(b2\)向下延伸,\(b1==2\) $ $ \(b2==0\)
\((5)\) \(b1==2\) \(and\) \(b2==0\)
跟\((4)\)差不多
\((6)\) \(b1==1\) \(and\) \(b2==1\)
- 直接刪去就好了,相當於兩個沒拐過的插頭會和,形成\(L\)拐彎交界處
做完一個題當然得總結一下:這題雖然與前面回路的題不同,看似無從下手,但從\(L\)型的性質,考慮狀態,全部考慮到,自然就\(A\)掉了
P2337 [SCOI2012]喵星人的入侵
這題深刻得體會到了插頭dp的毒瘤\(emmm\)
這里在聯通路徑情況下,增加了特殊限制,相當於每個點的權值由周圍八連通塊炮台數量決定
要求最后方案最小路徑最大,而可以放無限個障礙,顯然,為了簡化狀態,我們最后留一條路徑就好了
這里一種狀態由什么來表示?插頭,路徑狀態,已放炮台數量
和原來好像完全不一樣了,別擔心,多開幾個數組存狀態,把所有的狀態全部考慮一遍就好了嘛
以下借鑒了dalao@ComeIntoPower的思想與代碼
重點:
這里的插頭狀態:\(0\)空插頭 \(1\)左插頭 \(2\)右插頭 \(3\)獨立插頭
獨立插頭不知道意思?其實之前算是已經接觸過了,記不記得,在求最大聯通塊權,有一個新塊\(7\),差不多就是這個意思
由於起點終點只延伸一個方向,不參與括號匹配,所以單獨標記為3
插頭輪廓狀態表示如下
棋盤輪廓狀態:\(0\)障礙 \(1\)路徑 \(3\)炮台,狀態表示如下,故判斷是提取八聯通塊中已確定的四塊
\(Conn\)函數為當前的縱坐標為\(u\),插頭狀態為\(st\),將插頭\(op\)另一端改成\(v\)
LL Conn(LL st,LL op,LL u,LL v)
\(nxt\)函數表將當前棋盤狀態換完\(a\)
nxt(a) (stb-(yb<<bit(j+1))+((a)<<bit(j+1)))
滾動數組存三種小狀態
本題狀態較多,詳情請看完整代碼(內有注釋),相信你能看懂
My complete code:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define bit(x) ((x)<<1)
#define nxt(a) (stb-(yb<<bit(j+1))+((a)<<bit(j+1)))
using namespace std;
typedef long long LL;
const LL maxn=10000000;
const LL hs=299987;
LL n,m,now,last,K,ans;
LL cnt[2],val[2][maxn],que[2][maxn],nxt[maxn],stA[2][maxn],stB[2][maxn],stC[2][maxn],head[300000];
char a[30][30],tmp[30][30];
LL Hash(LL x,LL y,LL z){
return ((((x)<<16|y)<<4|z));
}
void Add(LL x,LL y,LL z,LL w){
LL u=Hash(x,y,z)%hs+1,h=Hash(x,y,z);
for(LL i=head[u];i;i=nxt[i])
if(que[now][i]==h){
val[now][i]=max(val[now][i],w);
return;
}
nxt[++cnt[now]]=head[u],
head[u]=cnt[now],
val[now][cnt[now]]=w,
que[now][cnt[now]]=h,
stA[now][cnt[now]]=x,
stB[now][cnt[now]]=y,
stC[now][cnt[now]]=z;
}
LL Conn(LL st,LL op,LL u,LL v){
if(op==1){
LL b=1;
for(;;++u){
LL p=(st>>bit(u))&3;
if(p==1)
b++;
if(p==2)
b--;
if(!b)
return st-(p<<bit(u))+(v<<bit(u));
}
} else {
LL b=1;
for(;;--u){
LL p=(st>>bit(u))&3;
if(p==2)
b++;
if(p==1)
b--;
if(!b)
return st-(p<<bit(u))+(v<<bit(u));
}
}
}
inline void Solve(){
now=0,last=1;
val[now][++cnt[now]]=0;
for(LL i=0;i<n;++i){
for(LL j=1;j<=cnt[now];++j)
stA[now][j]<<=2,
stB[now][j]=(stB[now][j]-(((stB[now][j]>>bit(m+1))&3)<<bit(m+1)))<<2;
for(LL j=0;j<m;++j){
swap(now,last);
memset(head,0,sizeof(head));
cnt[now]=0;
for(LL s=1;s<=cnt[last];++s){
LL sta=stA[last][s],
stb=stB[last][s],
k=stC[last][s],
w=val[last][s];
if(sta>=(1<<bit(m+1)))//上一行是否最后添加插頭,添加則超出格限制,跳過
continue;
LL b1,b2,xb,yb,zb,pb;
b1=sta>>bit(j)&3,
b2=sta>>bit(j+1)&3;
xb=stb>>bit(j)&3,
yb=stb>>bit(j+1)&3;
zb=stb>>bit(j+2)&3,
pb=stb>>bit(j+3)&3;
LL nsta=sta-(b1<<bit(j))-(b2<<bit(j+1));
if((xb==1&&!b1)||(zb==1&&!b2)){//不走就攔
if(!b1&&!b2){
Add(sta,nxt(0),k,w);//障礙
w+=(xb==1)+(yb==1)+(zb==1)+(pb==1);
if(k<K&&a[i][j]=='.')
Add(sta,nxt(3),k+1,w);//炮台
}
}else if(a[i][j]=='#'){
if(!b1&&!b2)
Add(nsta,nxt(0),k,w);
}else if(a[i][j]=='.'){
LL w1=w+(xb==1)+(yb==1)+(zb==1)+(pb==1),
w2=w;
w+=(xb==3)+(yb==3)+(zb==3)+(pb==3);
if(!b1&&!b2){
if(k<K)
Add(nsta,nxt(3),k+1,w1);//記錄對走過的路徑造成的影響
Add(nsta+(1<<bit(j))+(2<<bit(j+1)),nxt(1),k,w);//加兩個插頭
Add(nsta,nxt(0),k,w2);//不走
}else if(!b1){// 延伸
Add(nsta+(b2<<bit(j)),nxt(1),k,w);
Add(nsta+(b2<<bit(j+1)),nxt(1),k,w);
}else if(!b2){//延伸
Add(nsta+(b1<<bit(j)),nxt(1),k,w);
Add(nsta+(b1<<bit(j+1)),nxt(1),k,w);
}else if(b1==2&&b2==1){//合並
Add(nsta,nxt(1),k,w);
}else if(b1==1&&b2==1){//找到右替換成左
Add(Conn(nsta,1,j,1),nxt(1),k,w);
}else if(b1==2&&b2==2){//找到左替換成右
Add(Conn(nsta,2,j,2),nxt(1),k,w);
}else if(b1==3&&b2==1){//找到右替換成獨立
Add(Conn(nsta,1,j,3),nxt(1),k,w);
}else if(b1==3&&b2==2){//找到左替換成獨立
Add(Conn(nsta,2,j,3),nxt(1),k,w);
}else if(b1==1&&b2==3){//找到右替換成獨立
Add(Conn(nsta,1,j,3),nxt(1),k,w);
}else if(b1==2&&b2==3){//找到左替換成獨立
Add(Conn(nsta,2,j,3),nxt(1),k,w);
}else if(b1==3&&b2==3){//合並
Add(nsta,nxt(1),k,w);
}
}else if(a[i][j]=='S'||a[i][j]=='T'){
w+=(xb==3)+(yb==3)+(zb==3)+(pb==3);
if(a[i][j]=='S'&&!b1&&!b2){//起點且無插頭
Add(sta+(3<<bit(j)),nxt(1),k,w);
Add(sta+(3<<bit(j+1)),nxt(1),k,w);//延伸
}else if(a[i][j]=='T'&&!b1&&!b2){//終點且無插頭
Add(sta+(3<<bit(j)),nxt(1),k,w);
Add(sta+(3<<bit(j+1)),nxt(1),k,w);//延伸
}else if(!b1&&b2!=3&&b2!=0){//有非獨立插頭改成獨立插頭
Add(Conn(nsta,b2,j,3),nxt(1),k,w);
}else if(!b2&&b1!=3&&b1!=0){//有非獨立插頭改成獨立插頭
Add(Conn(nsta,b1,j,3),nxt(1),k,w);
}else if(!b1&&b2==3){//兩端已連接,不需要再添加
Add(nsta,nxt(1),k,w);
}else if(b1==3&&!b2){////兩端已連接,不需要再添加
Add(nsta,nxt(1),k,w);
}
}
}
}
}
}
int main(){
scanf("%lld%lld%lld",&n,&m,&K);
for(LL i=0;i<n;++i)
scanf(" %s",tmp[i]);
if(n<m){
for(LL i=0;i<n;++i)
for(LL j=0;j<m;++j)
a[j][i]=tmp[i][j];
swap(n,m);
}else{
for(LL i=0;i<n;++i)
for(LL j=0;j<m;++j)
a[i][j]=tmp[i][j];
}
Solve();
for(LL i=1;i<=cnt[now];++i)
if(!stA[now][i])
ans=max(ans,val[now][i]);
printf("%lld\n",ans);
return 0;
}