寶塔探險(樹圖專題)
T1 升降梯上
題目描述
開啟了升降梯的動力之後,探險隊員們進入了升降梯運行的那條豎直的隧道,映入眼簾 的是一條直通塔頂的軌道、一輛停在軌道底部的電梯、和電梯內一桿控制電梯升降的巨大手 柄。 Nescafe 之塔一共有 N 層,升降梯在每層都有一個停靠點。手柄有 M 個控制槽,第 i 個控制槽旁邊標著一個數 Ci,滿足 C1<??0,表示手柄扳動到該槽 時,電梯將上升 Ci 層;如果 Ci<0,表示手柄扳動到該槽時,電梯將下降-Ci 層;並且一定 存在一個 Ci=0,手柄最初就位於此槽中。註意升降梯只能在 1~N 層間移動,因此扳動到使 升降梯移動到 1 層以下、N 層以上的控制槽是不允許的。 電梯每移動一層,需要花費 2 秒鐘時間,而手柄從一個控制槽扳到相鄰的槽,需要花費 1 秒鐘時間。探險隊員現在在 1 層,並且想盡快到達 N 層,他們想知道從 1 層到 N 層至少 需要多長時間? <??
<??輸入格式 第一行兩個正整數 N、M。 第二行 M 個整數 C1、C2??CM。 <??
<??輸出格式 輸出一個整數表示答案,即至少需要多長時間。若不可能到達輸出-1。 <??
<??樣例輸入 6 3 -1 0 2<??
<?? 樣例輸出 19 <??
<??樣例說明 手柄從第二個槽扳到第三個槽(0 扳到 2),用時 1 秒,電梯上升到 3 層,用時 4 秒。 手柄在第三個槽不動,電梯再上升到 5 層,用時 4 秒。 手柄扳動到第一個槽(2 扳到-1),用時 2 秒,電梯下降到 4 層,用時 2 秒。 手柄扳動到第三個槽(-1 扳倒 2),用時 2 秒,電梯上升到 6 層,用時 4 秒。 總用時為(1+4)+4+(2+2)+(2+4)=19 秒。<??
<?? 數據範圍與約定 對於 30% 的數據,滿足 1≤N≤10,2<=M<=5。 對於 100% 的數據,滿足 1≤N≤1000,2<=M<=20,-N<C1<C2<…<CM<N.<??
T2塔頂試探
題目描述
探險隊員們順利乘坐升降梯來到了塔頂。塔頂有若幹房間,房間之間連有通道。如果把 房間看做節點,通道看做邊的話,整個塔頂呈現一個有根樹結構,並且每個房間的墻壁都塗 有若幹種顏色的一種。 現在探險隊員們在樹根處,他們打算進一步了解塔頂的結構,為此,他們使用了一種特 殊設計的機器人。這種機器人會從隊員身邊,也就是樹根出發,之後對塔頂進行深度優先遍 歷。機器人每進入一個房間(無論是第一次進入還是返回),都會記錄這個房間的顏色。最 後,機器人會回到樹根。 顯然,機器人會訪問每個房間至少一次,並且穿越每條通道恰好兩次(兩個方向各一次), 然後,機器人會得到一個顏色序列。但是,探險隊員發現這個顏色序列並不能唯一確定塔頂 的結構。現在他們想請你幫助他們計算,對於一個給定的顏色序列,有多少種可能的結構會 得到這個序列。由於結果可能會非常大,你只需要輸出答案對 10^9 取模之後的值。 輸入格式 輸入文件包含一行,含有一個字符串,表示機器人得到的顏色序列。
輸出格式 輸出一個整數表示答案。
樣例輸入 ABABABA
樣例輸出 5
樣例說明 有如下 5 種方案。註意子樹之間是有序的,所以(3)和(4)是兩種不同的方案。
數據範圍與約定 對於 24% 的數據,字符串的長度不超過 20。 對於 100% 的數據,字符串的長度不超過 300。
T3禮物運送
題目描述
機器人剛剛探查歸來,探險隊員們突然發現自己的腳下出現了一朵朵白雲,把他們托向 了空中。一陣飄飄然的感覺過後,隊員們發現自己被傳送到了一座空中花園。 “遠道而來的客人,我們是守護 Nescafe 之塔的精靈。如果你們想拜訪護法和聖主的話, 就要由我們引路。因此,你們是不是該給我們一點禮物呢 T_T?” 隊員們往遠處一看,發現花園中有 N 個木箱,M 條柔軟的羊毛小路,每條小路的兩個 端點都是木箱,並且通過它需要 ti 的時間。隊員們需要往每個木箱中放一份禮物,不過由 於精靈們不想讓花園被過多地踩踏,因此運送禮物的任務最多只能由兩位探險隊員完成。 兩位探險隊員同時從 1 號木箱出發,可以在任意木箱處結束運送。運送完畢時,每只木 箱應該被兩位隊員中至少一人訪問過。運送任務所用的時間是兩人中較慢的那個人結束運送 任務的時間。請問這個時間最快是多少呢?
輸入格式 第一行兩個正整數 N、M。 接下來 M 行每行三個整數 xi、yi、ti,表示 xi 和 yi 之間有一條用時為 ti 的小路,小路 是雙向的。
輸出格式 輸出所需的最短時間。
樣例輸入
6 6
1 2
10 2
3 10
3 4
5 4
5 10
5 6
20 2
5 10
樣例輸出
40
數據範圍與約定 對於 50%的數據,1<=N<=9。 對於 100%的數據,1<=N<=18,1<=ti<=1000,1<=xi,yi<=N,兩個木箱之間最多只有一 條小路,不會有自己到自己的小路,保證所有木箱是連通的。
昨天寢室裏開夜車寫作業的太多,燈亮如白晝。沒睡好,所以今天考試靈魂簡直在遊蕩,灌了大半杯咖啡後才好一些,最奇妙的是,我沒有做出簡單題T1,卻把比較有思維量的T2想出來了。。然後就寫掛了,處於爆零邊緣。。解就一起寫了,難免有些水,建立在大家都認真思考過的基礎上吧。
T1:
可能是我閱歷不夠,我首先想到的是dp(不再贅述),但是發現dp有後效性,而且記憶化會死循環(因為有負數),我那時就突然想到了建圖。然後我們考慮對層數i建圖,但是根本不行,你不能直接在節點i上加邊,因為在你的控制槽位置不確定的情況下,你無法確定邊的大小。所以就可以將一個二元組看成一個點。即(i,k),考慮非常顯然的(i,k)到(i+a[j],j)的邊為abs(k-j)+2*abs(a[j]);有這樣一個圖之後,你從起點(1,k0)到終點(n,j),j=1~m,a[k0]=0的;為什麽能,你可以把最短路看成更廣的具後效性的dp,用你的建圖直覺和dp直覺去理解。
到這裏還遠遠不夠,這個題比較煩的應該是代碼實現,(如果你是初次遇到這個題的話),仔細看。註意下面幾點:
1.如何將二元組建在一起 2.有沒有必要建圖 (這裏只專門用數據來存)
1 #include <cstdio> 2 #include <iostream> 3 #include <cstring> 4 #include <cmath> 5 #include <algorithm> 6 #include <queue> 7 #define N 1010 8 #define M 210 9 #define inf 0x3f3f3f3f 10 #define Run(i,l,r) for(int i=l;i<=r;i++) 11 #define Don(i,l,r) for(int i=l;i>=r;i--) 12 using namespace std; 13 struct node{int i,j; bool operator < (const node& a)const{return i<a.i;} }; 14 typedef pair<int,node>pii; 15 priority_queue<pii,vector<pii>,greater<pii> >q; 16 int vis[N][M],dis[N][M]; 17 int n,m,a[M]; 18 int main() 19 { freopen("updown.in","r",stdin); 20 freopen("updown.out","w",stdout); 21 cin>>n>>m; 22 int sj; 23 Run(i,1,m){ 24 cin>>a[i]; 25 if (!a[i]) sj=i; 26 } 27 memset(dis,0x3f,sizeof(dis)); 28 dis[1][sj]=0; 29 q.push(make_pair(dis[1][sj],(node){1,sj})); 30 vis[1][sj]=1; 31 while (!q.empty()) 32 {node u=q.top().second; 33 q.pop(); vis[u.i][u.j]=0; 34 for(int j=1;j<=m;j++) 35 {node v=(node){u.i+a[j],j}; 36 if (v.i<1||v.i>n) continue; 37 if (dis[v.i][v.j]>dis[u.i][u.j]+abs(j-u.j)+2*abs(a[j])) 38 {dis[v.i][v.j]=dis[u.i][u.j]+abs(j-u.j)+2*abs(a[j]); 39 if (!vis[v.i][v.j]) {q.push(make_pair(dis[v.i][v.j],v)); 40 vis[v.i][v.j]=1; 41 }} 42 } 43 44 } 45 int ans=0x3f3f3f3f; 46 Run(i,1,m) ans=min(ans,dis[n][i]); 47 cout<<((ans==inf)?-1:ans)<<endl; 48 return 0; 49 }View Code
T2:
這個題就是我唯一寫了但是寫掛了的QAQ,它其實是個區間dp。我開始也從一般的dp去考慮:
不分析細節,那麽就有這樣一件事情,在同一個串(例ABABABA)裏狡猾的A顏色(除第一個外和最後一個),有可能是dfs回來的根節點,也有可能是
在是[l,r]中s[l]==s[r]時,考慮s為AS1AS2A…….A的形式Sn表示一個字符串集,即
如果我們要專門去討論s1到sn的話,還要生成子集,代碼復雜性提高了不說,時間也過不了。而我們本身寫的就是dp,所以可以利用這個優勢,在劃分[l,r]的手段上變化:具體就是我們不關心s2到sn,只枚舉s1,然後將AS2AS2AS3..SNA原樣(就變成了一個更小的子問題)dp而將s1dp即S1,(s1無頭尾的A),至於l還是從r枚舉s1,就是隨便了。寫出轉移方程:
dp[l,r]=sigma(dp[l+1,k]*dp[k,r]) (s[l]==s[r]&&s[l]==s[k] ).
1 #include <cstdio> 2 #include <iostream> 3 #include <algorithm> 4 #include <cstring> 5 #define N 310 6 #define inf 0x3f3f3f3f 7 #define Mod 1000000001 8 #define Run(i,l,r) for(int i=l;i<=r;i++) 9 #define Don(i,l,r) for(int i=l;i>=r;i--) 10 using namespace std; 11 char s[N]; 12 int f[N][N]; 13 int main() 14 { 15 freopen("probe.in","r",stdin); 16 freopen("probe.out","w",stdout); 17 cin>>s; 18 int len=strlen(s)-1; 19 Run(i,0,len) f[i][1]=1; 20 21 22 Run(l,3,len+1) 23 for(int i=0;i+l-1<=len;i++) 24 {int j=i+l-1; 25 if (s[i]==s[j]){ 26 Don(k,j,i+2) 27 if (s[i]==s[k]) f[i][l]=(f[i][l]+1ll*f[i+1][k-i-1]*f[k][j-k+1]%Mod)%Mod; 28 } 29 } 30 cout<<f[0][len+1]<<endl; 31 return 0; 32 }View Code
T3
先看數據範圍可以猜出是狀壓dp,然而他們是兩個人一起走,而又沒說不能走相同的地方之類的,所以我們不妨將此題看為一個人走。如果不這樣,設計狀態是就要三維,分別為1號人最後走到的地方i,2號人最後走到的地方j,和此時兩人一共走過的集合s
(二進制狀壓),你悄悄算算就會發現一件很奇妙的事情:MLE。所以我們只能考慮記錄一個i,s所以得到的應該是對於這個圖的每一種遍歷過的點集s,走到的末位置為i的情況下的最少步數。考慮兩個人無論誰走都是一樣的,那麽我們想要得到答案就應該是min{max(f[s|1,i],f[((1<<n)-1-s)|1,j])} (ij∈V);(之所以要|1是因為1是出發點,其他點可以取反,但1不能)
所以是這樣的嗎?如果你照著這個思路用普通的狀壓dp擴展你會發現你連樣例都過不了。因為樣例中12還共同走了一段而這一段是必須的。如何把這一段加進去呢——其實,跑最短路將連通圖的每兩個點的最短距離算出來即可,因為在運用最短路擴展時,計算dis[][]數組會加進一些必要經過的點並且這些點是最優的,所以現在我們完善狀態:一定會經過s中點,但經過的點不一定是S(子集關系)應為有可能多經過其他點,而更優。那所以到這裏狀態轉移方程才是正確的。建議用floyd求多源點最短路。
1 #include <cstdio> 2 #include <iostream> 3 #include <cstring> 4 #include <queue> 5 #include <algorithm> 6 #include <vector> 7 #include <cmath> 8 #include <ctime> 9 #define N 20 10 #define inf 0x3f3f3f3f 11 #define Run(i,l,r) for(int i=l;i<=r;i++) 12 #define Don(i,l,r) for(int i=l;i>=r;i--) 13 using namespace std; 14 int n,m,dis[N][N]; 15 int f[1<<N][N]; 16 int main() 17 { 18 freopen("transport.in","r",stdin); 19 freopen("transport.out","w",stdout); 20 cin>>n>>m; 21 memset(dis,0x3f,sizeof(dis)); 22 Run(i,1,m) 23 {int u,v,w; 24 cin>>u>>v>>w; 25 dis[u][v]=dis[v][u]=w; 26 } 27 Run(i,1,n) dis[i][i]=0; 28 Run(k,1,n)Run(i,1,n)Run(j,1,n) if(dis[i][k]+dis[k][j]<dis[i][j]) dis[i][j]=dis[i][k]+dis[k][j]; 29 memset(f,0x3f,sizeof(f)); 30 31 f[1][1]=0; 32 Run(i,1,(1<<n)-1) Run(j,1,n) 33 if ((1<<j-1)&i) 34 Run(k,1,n)if (((1<<k-1)&(i))&&f[i][j]>f[i-(1<<j-1)][k]+dis[k][j]&&i!=j) f[i][j]=f[i-(1<<j-1)][k]+dis[k][j]; 35 36 Run(i,1,(1<<n)-1) 37 Run(j,1,n) 38 f[i][0]=min(f[i][0],f[i][j]); 39 int ans=inf; 40 Run(i,1,(1<<n)-1) 41 {ans=min(ans,max(f[i|1][0],f[((1<<n)-1-i)|1][0])); 42 } 43 Run(i,1,(1<<n)-1) cout<<f[i][0]<<" "; 44 cout<<endl; 45 cout<<ans<<endl; 46 return 0; 47 }View Code
總結:
1.區間DP的l要放在最外邊;
3.註意spfa可以用priotity優化,然後加入隊列的條件是v不在隊列裏且被更新了;
判環的方法就是更新次數超過n-1次;
3.flyod的k應該在最外面(先記著,有感性的理解,說不定那天有靈感就相出來了)
4.寫dp時,務必註意初值!!!和方程;請動腦子打程序,不然會花去十倍的時間調試;這也是你為什麽比其他人落後的原因,常規一樣,初中也一樣,不改必誤你一生;
寶塔探險(樹圖專題)