http://poj.org/problem?id=3592
题意:给定一个n*m格子的有向图,每个格子上是数字,‘#’或 '*' ,数字代表该格子上的矿石数目,'#'代表该格子不能走,' * '代表一个传送阵,送往某个给定的坐标。每次矿车只能向下或向右走一格。问矿车从左上角出发,最后能最多得到多少矿石。
思路:因为矿车每次只能向右或向下走一格,说明这是这是一个有向图,最后问最多得到多少矿石,说明是求最长路的问题。
建图时, 数字 需要向其下方和右方建边,' * '需要向其下方,右方和指定坐标建边,而' # ' 代表不通,不用建边。
在这里因为传送阵的存在,它能到达某个给定的坐标,可能会导致环的出现,即强连通分量,所以我们需要对每个强连通分量缩点,该点的权值是该强连通分量中所有点的权值之和,最后形成一个有向无环图,对这个有向无环图求最长路。
注意:求最长路时,起点是(0,0)点所在强连通分量的缩点,终点是任意位置。
' * '位置仍然要向下方和右方建边。
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
const int maxm = 5000;
const int maxn = 1600;
const int INF = 0x3f3f3f3f;
struct node
{
int u,v,w;
int next;
}edge[maxm],edge2[maxm];
int cnt,cnt2,head[maxm],head2[maxm];
char map[50][50];
int n,m;
int val[maxn];
int val_sum[maxn];
int dfn[maxn],low[maxn],instack[maxn],set[maxn],dep,scc;
stack <int> st;
void add(int u, int v)
{
edge[cnt].u = u;
edge[cnt].v = v;
edge[cnt].next = head[u];
head[u] = cnt++;
}
void add2(int u, int v, int w)
{
edge2[cnt2].u = u;
edge2[cnt2].v = v;
edge2[cnt2].w = w;
edge2[cnt2].next = head2[u];
head2[u] = cnt2++;
}
void build()
{
int x,y;
memset(val,0,sizeof(val));
memset(head,-1,sizeof(head));
cnt = 0;
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
int num = i*m+j;
if(map[i][j] == '#')
val[num] = 0;
else if(map[i][j] == '*')
{
val[num] = 0;
if(i < n-1 && map[i+1][j] != '#')
add(num,num+m);
if(j < m-1 && map[i][j+1] != '#')
add(num,num+1);
scanf("%d %d",&x,&y);
if(map[x][y] != '#')
add(num,x*m+y);
}
else
{
val[num] = map[i][j]-'0';
if(i < n-1 && map[i+1][j] != '#')
add(num,num+m);
if(j < m-1 && map[i][j+1] != '#')
add(num,num+1);
}
}
}
}
void init()
{
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(instack,0,sizeof(instack));
memset(val_sum,0,sizeof(val_sum));
while(!st.empty()) st.pop();
dep = 0;
scc = 0;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++dep;
instack[u] = 1;
st.push(u);
for(int i = head[u]; i != -1; i = edge[i].next)
{
int v = edge[i].v;
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u],low[v]);
}
else if(instack[v])
low[u] = min(low[u],dfn[v]);
}
if(dfn[u] == low[u])
{
scc++;
while(1)
{
int tmp = st.top();
st.pop();
instack[tmp] = 0;
set[tmp] = scc;
val_sum[scc] += val[tmp];
if(tmp == u)
break;
}
}
}
void Suo()
{
for(int i = 0; i < n*m; i++)
{
if(!dfn[i])
tarjan(i);
}
memset(head2,-1,sizeof(head2));
cnt2 = 0;
for(int i = 0; i < cnt; i++)
{
int u = edge[i].u;
int v = edge[i].v;
if(set[u] != set[v])
add2(set[u],set[v],val_sum[set[v]]);
}
}
int spfa(int s)
{
queue<int>que;
int dis[maxn];
int inque[maxn];
while(!que.empty()) que.pop();
for(int i = 1; i <= scc; i++)
{
dis[i] = -INF;
inque[i] = 0;
}
que.push(s);
inque[s] = 1;
dis[s] =0;
while(!que.empty())
{
int u = que.front();
que.pop();
inque[u] = 0;
for(int i = head2[u]; i != -1; i = edge2[i].next)
{
int v = edge2[i].v;
int w = edge2[i].w;
if(dis[v] < dis[u] + w)
{
dis[v] = dis[u] + w;
if(!inque[v])
{
inque[v] = 1;
que.push(v);
}
}
}
}
int ans = -1;
for(int i = 1; i <= scc; i++)
ans = max(ans,dis[i]);
return ans+val_sum[s];
}
int main()
{
int test;
int ans;
scanf("%d",&test);
while(test--)
{
scanf("%d %d",&n,&m);
for(int i = 0; i < n; i++)
scanf("%s",map[i]);
build(); //建图
init();
Suo(); //缩点
ans = spfa(set[0]); //求最长路
printf("%d\n",ans);
}
return 0;
}