题意:一辆坦克从N*M矩阵的左上角出发,每次往右或往下走一格,每格可以是'#'(表示不可以走),'*'表示传送门,或者是数字,表示在该格可以获得的值(只能取一次),传送门可以将到达该处的坦克传送到指定位置,你可以选择被传送或者走相邻的格,问坦克可以获得的值的和最大为多少。
思路:N*M矩阵上每个点看成图中的一个点,能够走的连边。那么显然在一个强连通分量里的点的值是全部能够得到的。那么思路就是先求强连通分量,然后缩点(此时点上的权值是原图所包含点得权值之和)。进而求一条从左上角点所在强连通分量出发的最长路。可以用spfa来求,也可以用dp的思想,做dp的时候对点的处理顺序蕴含了拓扑排序的思想。
写dp的时候wa了很多次,是在建新图时的入度数组写错了。有重边时在新图中也只有一条边,所以d数组也只增加1,需要注意。。
版本1:dp
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <cstdlib> using namespace std; #define clc(s,t) memset(s,t,sizeof(s)) #define INF 0x3fffffff #define N 42 char s[N][N]; int T,n,m,w[N*N],t[N*N],res;//w是原始价值,t是缩点后的价值 struct edge{ int y,next; }e[N*N*3]; int first[N*N],top; int strong[N*N],dfn[N*N],low[N*N],stack[N*N],inst[N*N],con,idx,ts; int id(int x,int y){ return x*m+y; } int g[N*N][N*N],d[N*N],dp[N*N]; void init(){ clc(first, -1); clc(dfn, -1); clc(inst, 0); clc(t,0); clc(g, 0); clc(strong, 0); clc(d, 0); clc(dp, 0); clc(w,0); top = con = idx = ts = res = 0; } void add(int x,int y){ e[top].y = y; e[top].next = first[x]; first[x] = top++; } void tarjan(int x){ dfn[x] = low[x] = ++idx; stack[ts++] = x; inst[x] = 1; for(int i = first[x];i!=-1;i=e[i].next){ if(dfn[e[i].y] == -1){ tarjan(e[i].y); low[x] = min(low[x],low[e[i].y]); }else if(inst[e[i].y]) low[x] = min(low[x],dfn[e[i].y]); } if(dfn[x] == low[x]){ con++; do{ strong[stack[--ts]] = con; inst[stack[ts]] = 0; t[con] += w[stack[ts]]; }while(x != stack[ts]); } } int main(){ scanf("%d",&T); while (T--) { int i,j,a,b,now; init(); scanf("%d %d",&n,&m); //clc(s,'#'); for(i = 0;i<n;i++) scanf("%s",s[i]); for(i = 0;i<n;i++) for(j = 0;j<m;j++){ if(s[i][j] == '#') continue; if(i+1<n && s[i+1][j] != '#') add(id(i,j) , id(i+1,j)); if(j+1<m && s[i][j+1] != '#') add(id(i,j) , id(i,j+1)); if(s[i][j] == '*'){ scanf("%d %d",&a,&b); if(s[a][b] != '#')//WA点:要判断传送的终点位置是否可行 add(id(i,j),id(a,b)); }else w[id(i,j)] = s[i][j]-'0'; } tarjan(0); for(i = 0;i<n*m;i++){ if(strong[i] == 0) continue; for(j = first[i];j!=-1;j=e[j].next) if(strong[e[j].y] && strong[i] != strong[e[j].y]){ g[strong[i]][strong[e[j].y]]++; if(g[strong[i]][strong[e[j].y]] == 1)//这个地方没有注意WA了很多次。。。(因为建新图时忽略重边的话d数组就错了) d[strong[e[j].y]]++; } } queue<int> q; q.push(strong[0]); dp[strong[0]] = t[strong[0]]; while(!q.empty()){ now = q.front(); q.pop(); for(i = 1;i<=con;i++){ if(g[now][i] && d[i]>=1){ dp[i] = max(dp[i],dp[now]+t[i]); d[i]--; if(!d[i]) q.push(i); } } } for(i = 1;i<=con;i++) res = max(res,dp[i]); printf("%d\n",res); } return 0; }
#include <cstdio> #include <cstring> #include <algorithm> #include <cmath> #include <queue> #include <cstdlib> using namespace std; #define clc(s,t) memset(s,t,sizeof(s)) #define INF 0x3fffffff #define N 42 char s[N][N]; int T,n,m,w[N*N],t[N*N],res;//w是原始价值,t是缩点后的价值 struct edge{ int y,next; }e[N*N*3]; int first[N*N],top; int strong[N*N],dfn[N*N],low[N*N],stack[N*N],inst[N*N],con,idx,ts; int id(int x,int y){ return x*m+y; } struct ee{ int y,w,next; }e2[N*N*3]; int f2[N*N],top2,dis[N*N]; void init(){ clc(first, -1); clc(dfn, -1); clc(inst, 0); clc(t,0); clc(strong, 0); clc(w,0); top = con = idx = ts = res = 0; } void add(int x,int y){ e[top].y = y; e[top].next = first[x]; first[x] = top++; } void tarjan(int x){ dfn[x] = low[x] = ++idx; stack[ts++] = x; inst[x] = 1; for(int i = first[x];i!=-1;i=e[i].next){ if(dfn[e[i].y] == -1){ tarjan(e[i].y); low[x] = min(low[x],low[e[i].y]); }else if(inst[e[i].y]) low[x] = min(low[x],dfn[e[i].y]); } if(dfn[x] == low[x]){ con++; do{ strong[stack[--ts]] = con; inst[stack[ts]] = 0; t[con] += w[stack[ts]]; }while(x != stack[ts]); } } void add2(int x,int y,int w){ e2[top2].y = y; e2[top2].w = w; e2[top2].next = f2[x]; f2[x] = top2++; } int relax(int x,int y,int w){ if(dis[x]+w > dis[y]){ dis[y] = dis[x]+w; return 1; } return 0; } void spfa(int s){ int i,now; int used[N*N] ; clc(dis,0); dis[s] = t[s]; clc(used,0); used[s] = 1; queue<int> q; q.push(s); while(!q.empty()){ now = q.front(); q.pop(); used[now] = 0; for(i = f2[now];i!=-1;i=e2[i].next) if(relax(now,e2[i].y,e2[i].w) && !used[e2[i].y]){ used[e2[i].y] = 1; q.push(e2[i].y); } } } int main(){ scanf("%d",&T); while (T--) { int i,j,a,b; init(); scanf("%d %d",&n,&m); for(i = 0;i<n;i++) scanf("%s",s[i]); for(i = 0;i<n;i++) for(j = 0;j<m;j++){ if(s[i][j] == '#') continue; if(i+1<n && s[i+1][j] != '#') add(id(i,j) , id(i+1,j)); if(j+1<m && s[i][j+1] != '#') add(id(i,j) , id(i,j+1)); if(s[i][j] == '*'){ scanf("%d %d",&a,&b); if(s[a][b] != '#')//WA点:要判断传送的终点位置是否可行 add(id(i,j),id(a,b)); }else w[id(i,j)] = s[i][j]-'0'; } tarjan(0); clc(f2,-1); top2 = 0; for(i = 0;i<n*m;i++){ if(strong[i] == 0) continue; for(j = first[i];j!=-1;j=e[j].next) if(strong[e[j].y] && strong[i] != strong[e[j].y]) add2(strong[i],strong[e[j].y],t[strong[e[j].y]]); } spfa(strong[0]); for(i = 1;i<=con;i++) res = max(res,dis[i]); printf("%d\n",res); } return 0; }