POJ3592 Instantaneous Transference题解
题意:
给一个矩形,矩形中某些点有一定数量的矿石,有些点为传送点,有些点为障碍。你驾驶采矿车(ore-miner truck,我也不知道是什么),从左上角出发,采尽量多的矿石,矿石不可再生。不能往左边或者上面走。传送点可以往左边或上面传。2<=n,m<=40
分析:
可以把矩形看作一张图,每个格子为一个点,每个格子与它右边和下面的点连接一条有向边。每个传送点与它传送的位置连接一条有向边。如果右边或下面为#那么不连。值得注意的是题目并没有保证传送点传送到的一定不是#,所以需要进行判断。
这样,我们得到了一个点数|V|=n*m,边数最大|E|=O(n*m)的有向有环图。答案就是限制只能取一次的最长路。
我们现在的任务就是把只能取一次抽象成另一种能实现的东西,不然暴力是指数级的。
很显然,向后传送不需要考虑,只考虑传回前面,在图上的表现为成环,或者说同属一个强连通分量。
很自然地就可以发现同属一个强连通分量的点都可以同时取到,我们考虑用tarjan缩点,这样建的就是一个DAG,在这个DAG上跑最长路,就是答案。
这里值得一提的是,这里不能用dijkstra,同时我们可以下结论:求最长路时,dijkstra算法只适用于负权图,求最短路时,dijkstra算法只适用于正权图。所以这里要写SPFA。
代码:用emacs写的,所以不要吐槽两格缩进
1 #include<iostream> 2 #include<cstdio> 3 #include<cstdlib> 4 #include<vector> 5 #include<cstring> 6 #include<stack> 7 #include<queue> 8 #include<algorithm> 9 using namespace std; 10 int low[1650],dfn[1650],arr[1650],scc[1650],al,cl,w[1650]; 11 vector<int> g[1650]; 12 vector<int> ng[1650]; 13 stack <int> sta; 14 void tarjan(int now){ 15 low[now]=dfn[now]=++cl; 16 sta.push(now); 17 for(int i=0;i<g[now].size();i++){ 18 int k=g[now][i]; 19 if(arr[k])continue; 20 if(!dfn[k]){ 21 tarjan(k); 22 low[now]=min(low[now],low[k]); 23 }else 24 low[now]=min(low[now],dfn[k]); 25 } 26 if(low[now]==dfn[now]){ 27 al++; 28 while(1){ 29 int u=sta.top(); 30 sta.pop(); 31 arr[u]=1; 32 scc[u]=al; 33 if(u==now) break; 34 } 35 } 36 } 37 int a[50][50]; 38 void readint(int n,int m){ 39 memset(arr,0,sizeof(arr)); 40 memset(w,0,sizeof(w)); 41 memset(dfn,0,sizeof(dfn)); 42 memset(low,0,sizeof(low)); 43 memset(scc,0,sizeof(scc)); 44 al=cl=0; 45 for(int i=1;i<=n*m;i++)g[i].clear(),ng[i].clear(); 46 for(int i=1;i<=n;i++) 47 for(int j=1;j<=m;j++){ 48 char x;cin>>x; 49 if(x=='*')a[i][j]=10; 50 else 51 if(x=='#')a[i][j]=-1; 52 else a[i][j]=(int)(x-48); 53 } 54 for(int i=1;i<=n;i++){ 55 for(int j=1;j<=m;j++){ 56 if(j+1<=m&&a[i][j+1]!=-1)g[i*m-m+j].push_back(i*m-m+j+1); 57 if(i+1<=n&&a[i+1][j]!=-1)g[i*m-m+j].push_back(i*m+j); 58 if(a[i][j]==10){ 59 int x,y;cin>>x>>y; 60 if(a[x+1][y+1]==-1)continue; 61 else g[i*m-m+j].push_back(x*m+y+1); 62 } 63 } 64 } 65 } 66 void dij(int n){//SPFA找最长路 67 queue <int> que; 68 int dist[2000]; 69 memset(dist,0,sizeof(dist)); 70 dist[scc[1]]=w[scc[1]]; 71 que.push(scc[1]); 72 while(!que.empty()){ 73 int k=que.front(); 74 for(int i=0;i<ng[k].size();i++){ 75 if(dist[k]+w[ng[k][i]]>dist[ng[k][i]]){ 76 dist[ng[k][i]]=dist[k]+w[ng[k][i]]; 77 que.push(ng[k][i]); 78 } 79 } 80 que.pop(); 81 } 82 int maxx=0; 83 for(int i=1;i<=n;i++)maxx=max(maxx,dist[i]); 84 cout<<maxx<<endl; 85 } 86 87 int main(){ 88 int t;cin >> t; 89 while(t--){ 90 int n,m; 91 cin>>n>>m; 92 readint(n,m); 93 for(int i=1;i<=n*m;i++) 94 if(!arr[i]) 95 tarjan(i);//求强连通分量 96 for(int i=1;i<=n*m;i++){ 97 for(int j=0;j<g[i].size();j++){ 98 if(scc[i]==scc[g[i][j]])continue; 99 ng[scc[i]].push_back(scc[g[i][j]]); 100 } 101 }//缩点 102 for(int i=1;i<=n;i++){ 103 for(int j=1;j<=m;j++){ 104 if(a[i][j]==-1||a[i][j]==10)continue; 105 w[scc[i*m-m+j]]+=a[i][j]; 106 } 107 }//算新的点权 108 dij(al);//求最长路 109 } 110 return 0;
111 }