#417 Div2 Problem B Sagheer, the Hausmeister (DFS && 枚举)
题目链接:http://codeforces.com/contest/812/problem/B
题意 : 给出一个 n (1 ≤ n ≤ 15)层的教学楼, 每一层楼包含 m (1 ≤ m ≤ 100)个房间, 另外每一层的两边还有楼梯口, 接下来有n 行每行有 m+2(包含楼梯口) 用0和1来表示这栋楼的信息, 0代表这个房间的灯没亮, 1代表亮, 现在保安在整栋楼的左下角的楼梯口, 他的目的是关掉这栋楼所有的灯, 而且保安在上楼时他所在的当前楼层灯需全灭才能继续上楼, 而且每经过一个房间和上下楼梯都需要消耗1分钟, 问你最后最少需要多少分钟才能将整栋楼的灯关掉(注意灯全灭的楼层可以不必理会)!还有保安在关掉所有灯后就不会进行任何移动操作了!
分析 : 这题的关键是保安上楼时的决策, 即若保安现在左楼梯, 那他到底是关灯后下一层通过右楼梯走上上一层(这时耗时就是m+1), 还是先去关掉这一层所有的灯再走回左边楼梯上楼。由于这题楼层最多只有15层, 如果枚举每一层保安所有可能的走法那也就是2^15次方的复杂度, 可以接受, 所以可以采用DFS来枚举所有保安走法即可, 但是这里需要注意楼顶的层数并不一定是n, 因为可能在某一层例如第k层以后, 上面的灯就全都是灭的, 那保安就没必要继续上楼了, 枚举到第k层即可!
瞎想 : 可否贪心模拟?我一开始是考虑对于每一层保安所在的楼梯口进行贪心策略, 看通过哪一个楼梯口上楼消耗的时间更短, 但是挂在了第九个用例, 因为只考虑了当前楼层, 而没有结合以后楼层的情况进行考虑, 所以并不是最优, 说到这里, 这就有点DP的味道了!的确, 看了大佬们的代码, 看到了很多用DP解决。
瞎搞 : 其实贪心是可以很快写出来的, 又是没有使用模块化思想, 代码又长又臭, Debug了挺久。还有就是又没有考虑清楚当前的贪心策略会不会有BUG和没有考虑清楚顶楼情况, 导致代码写出来比赛已经OVER了 /(ㄒoㄒ)/~~, 最后还错了!!!
贪心错误做法:
#include<bits/stdc++.h> #define LL long long #define ULL unsigned long long #define lowbit(i) (i&(-i)) using namespace std; const int INF = 0x3f3f3f3f; int main(void) { int G[20][1000]; int n, m; bool flag; int cnt = 0; scanf("%d%d", &n, &m); for(int i=1; i<=n; i++){ flag = false; for(int j=1; j<=m+2; j++){ char ch; scanf("%c", &ch); if(isdigit(ch)) G[i][j] = ch-'0'; else j--; if(G[i][j]==1) flag = true; } if(flag && !cnt) cnt = i; } bool L = true; bool even; if((m+2)%2==0) even = true; else even = false; int ans; if(cnt==0) {puts("0");return 0;} else ans = n-cnt; //printf("%d %d", cnt, ans);puts(""); for(int i=n; i>=1; i--){ if(i==cnt){ if(L){ int temp = -1; for(int j=m+2; j>=1; j--){ if(G[i][j]==1){ temp = j; break; } } if(temp==-1); else{ ans+=temp-1; } }else{ int temp = -1; for(int j=1; j<=m+2; j++){ if(G[i][j]==1){ temp = j; break; } } if(temp==-1); else{ ans+=(m+2)-temp; } } break; } if(L){ int temp = -1; for(int j=m+2; j>=1; j--){ if(G[i][j]==1){ temp = j; break; } } if(temp==-1); else{ if(even){ if(temp>(m+2)/2){ ans+=m+1; L = false; }else{ ans+=temp-1; ans+=temp-1; } }else{ if(temp>((m+2)/2) + 1){ ans+=m+1; L = false; }else{ ans+=temp-1; ans+=temp-1; } } } }else{ int temp = -1; for(int j=1; j<=m+2; j++){ if(G[i][j]==1){ temp = j; break; } } if(temp==-1); else{ if(even){ if(temp<=(m+2)/2){ ans+=m+1; L = true; }else{ ans+=(m+2)-temp; ans+=(m+2)-temp; } }else{ if(temp<=((m+2)/2) + 1){ ans+=m+1; L = true; }else{ ans+=(m+2)-temp; ans+=(m+2)-temp; } } } } } printf("%d\n", ans); return 0; }
以下代码枚举做法, 由于有黏贴贪心时所写的代码, 所以又长又臭, 凑合着看吧
#include<bits/stdc++.h> #define LL long long #define ULL unsigned long long #define lowbit(i) (i&(-i)) using namespace std; const int INF = 0x3f3f3f3f; LL ans = INF; int F; int G[20][1000]; int n, m; bool flag; int cnt = 0; void dfs(bool pre, bool now, int x, LL sum)//参数分别代表上一层所在的楼梯口位置, 和当前将要去往的楼梯口位置, 当前楼层数, 以及耗费了多少时间 { if(pre){//如果上一层是在左楼梯 if(x==F){//如果在顶楼, 需要特殊处理 int tmp = -1; for(int j=m+2; j>=1; j--){ if(G[x][j]==1){ tmp = j; break; } } if(tmp!=-1)sum+=tmp-1; }else{ if(now){ int tmp = -1; for(int j=m+2; j>=1; j--){ if(G[x][j]==1){ tmp = j; break; } } if(tmp!=-1){ sum += 2*(tmp-1); } }else{ sum += m+1; } } }else{ if(x==F){ int tmp = -1; for(int j=1; j<=m+2; j++){ if(G[x][j]==1){ tmp = j; break; } } if(tmp!=-1) sum+=(m+2)-tmp; }else{ if(now){ sum += m+1; }else{ int tmp = -1; for(int j=1; j<=m+2; j++){ if(G[x][j]==1){ tmp = j; break; } } if(tmp!=-1){ sum += 2*(m+2 - tmp); } } } } if(x!=F){ dfs(now, true, x+1, sum); dfs(now, false, x+1, sum); }else{ if(sum<ans) ans = sum; return; } } int main(void) { scanf("%d%d", &n, &m); for(int i=n; i>=1; i--){ flag = false; for(int j=1; j<=m+2; j++){ char ch; scanf("%c", &ch); if(isdigit(ch)) G[i][j] = ch-'0'; else j--; if(G[i][j]==1) flag = true; } if(flag && !cnt) cnt = i; } bool L = true; if(cnt==0) {puts("0");return 0;}//所有楼层都是灯灭的 else F = cnt;//记录有效顶楼 dfs(true, true, 1, 0);//从第一层去往左楼梯上楼 dfs(true, false, 1, 0);//从第一层去往右楼梯上楼 printf("%lld\n", ans+F-1);//每一层消耗的体力还要加上上楼梯花费的体力 return 0; }