poj 3592 Instantaneous Transference(tarjan + 缩点 + 最长路)

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;
}



你可能感兴趣的:(Tarjan,强连通分量,缩点)