[BZOJ 1179]ATM题解 Tarjan缩点+SPFA
[BZOJ 1179]ATM题解 Tarjan缩点+SPFA
Description
Input
第一行包含两个整数N、M。N表示路口的个数,M表示道路条数。接下来M行,每行两个整数,这两个整数都在1到N之间,第i+1行的两个整数表示第i条道路的起点和终点的路口编号。接下来N行,每行一个整数,按顺序表示每个路口处的ATM机中的钱数。接下来一行包含两个整数S、P,S表示市中心的编号,也就是出发的路口。P表示酒吧数目。接下来的一行中有P个整数,表示P个有酒吧的路口的编号
Output
输出一个整数,表示Banditji从市中心开始到某个酒吧结束所能抢劫的最多的现金总数。
Sample Input
6 7
1 2
2 3
3 5
2 4
4 1
2 6
6 5
10
12
8
16
1 5
1 4
4
3
5
6Sample Output
47HINT
50%的输入保证N, M<=3000。所有的输入保证N, M<=500000。每个ATM机中可取的钱数为一个非负整数且不超过4000。输入数据保证你可以从市中心沿着Siruseri的单向的道路到达其中的至少一个酒吧。
观察题目即可发现:1->2->4是一个强连通分量,3,5,6是一个强连通分量。很明显这是一道强连通分量的裸题。使用tarjan或各种神奇的求强连通分量的算法所完点后,剩下的就可以用各种算法来乱搞了。
先是tarjan求强连通分量部分
int stack[maxn],dfn[maxn],low[maxn],vis[maxn],belong[maxn];
int VAL[maxn],val[maxn];
int dfs_num,tot,top;
void tarjan(int curr){
stack[++top]=curr;
vis[curr]=1;
dfn[curr]=low[curr]=++dfs_num;
for(int i=first[curr];i;i=edge[i].next){
int to = edge[i].to;
if(!dfn[to]){
tarjan(to);
low[curr]=min(low[curr],low[to]);
}
else if(vis[to])
low[curr]=min(low[curr],dfn[to]);
}
if(low[curr]==dfn[curr]){
vis[curr]=0;
belong[curr]=++tot;
VAL[belong[curr]]+=val[curr];
while(stack[top]!=curr){
belong[stack[top]]=tot;
VAL[belong[stack[top]]]+=val[stack[top]];
vis[stack[top]]=0;
top--;
}
top--;
}
};
求完强连通分量,需要缩点建新图。注意:加边的时候变量名别搞混了。
void readd(){
for(int i=1;i<=n;i++)
for(int p=first[i];p;p=edge[p].next){
if(belong[i]!=belong[edge[p].to]){
G[++Gnt].from=belong[i];
G[Gnt].to=belong[edge[p].to];
G[Gnt].next=first2[belong[i]];
first2[belong[i]]=Gnt;
}
}
}
接下来读题,既然要使得抢的钱最多,那么我们可以把它转化成一个最大路问题,用spfa求出源点到各点的最大距离,再判断各点是否是酒吧(符合要求的节点)。
void spfa(int s){ queue <int>q ; dist[s]=VAL[s];//起点的钱也要抢走,所以初始值为起点所在SCC内所有的钱(dist数组初值默认为0) v[s]=1; q.push(s); while(!q.empty()){ int from=q.front();q.pop(); for(int i=first2[from];i;i=G[i].next){ if(dist[from]+VAL[G[i].to]>dist[G[i].to]){ //这里是大于号 dist[G[i].to]=dist[from]+VAL[G[i].to]; if(!v[G[i].to]){ v[G[i].to]=1; q.push(G[i].to); } } } v[from]=0; } }
以下是完整AC代码
#include<cstdio> #include<algorithm> #include<queue> using namespace std; const int maxn=500010; int n,m; int read(int &x){ char c=getchar(),last;x=0; while(c<'0'||c>'9')last=c,c=getchar(); while(c<='9'&&c>='0')x=(x<<1)+(x<<3)+c-'0',c=getchar(); if(last=='-')x=-x; return x; } int first[maxn],first2[maxn],cnt,Gnt; struct EDGE{ int from,to,next; }edge[maxn],G[maxn]; void addedge(int u,int v){ edge[++cnt].next=first[u]; first[u]=cnt; edge[cnt].from=u; edge[cnt].to=v; } int stack[maxn],dfn[maxn],low[maxn],vis[maxn],belong[maxn]; int VAL[maxn],val[maxn]; int dfs_num,tot,top; void tarjan(int curr){ vis[stack[++top]=curr]=1; dfn[curr]=low[curr]=++dfs_num; for(int i=first[curr];i;i=edge[i].next){ if(!dfn[edge[i].to]){ tarjan(edge[i].to); low[curr]=min(low[curr],low[edge[i].to]); } else if(vis[edge[i].to]){ low[curr]=min(low[curr],dfn[edge[i].to]); } } if(low[curr]==dfn[curr]){ vis[curr]=0; VAL[belong[curr]=++tot]+=val[curr]; while(stack[top]!=curr){ VAL[belong[stack[top]]=tot]+=val[stack[top]]; vis[stack[top--]]=0; } top--; } }; int v[maxn],dist[maxn]; void readd(){ for(int i=1;i<=n;i++) for(int p=first[i];p;p=edge[p].next){ if(belong[i]!=belong[edge[p].to]){ G[++Gnt].from=belong[i]; G[Gnt].to=belong[edge[p].to]; G[Gnt].next=first2[belong[i]]; first2[belong[i]]=Gnt; } } } void spfa(int s){ queue <int>q ; dist[s]=VAL[s]; v[s]=1; q.push(s); while(!q.empty()){ int from=q.front();q.pop(); for(int i=first2[from];i;i=G[i].next){ if(dist[from]+VAL[G[i].to]>dist[G[i].to]){ dist[G[i].to]=dist[from]+VAL[G[i].to]; if(!v[G[i].to]){ v[G[i].to]=1; q.push(G[i].to); } } } v[from]=0; } } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<=m;i++) { int x,y; scanf("%d %d",&x,&y); addedge(x,y); } for(int i=1;i<=n;i++){ scanf("%d",&val[i]); } int s,p; scanf("%d %d",&s,&p); int ans=0; for(int i=1;i<=n;i++){ if(!belong[i])tarjan(i); } readd(); spfa(belong[s]); for(int i=1;i<=p;i++){ int x; scanf("%d",&x); ans=max(ans,dist[belong[x]]); } printf("%d",ans); return 0; }
顺便给大家提供一组比较典型的数据点,当初提交迷之WA,跟别人的程序对拍了好久才找出来的。
13 16 13 11 12 5 1 3 10 9 3 2 2 10 4 2 3 12 13 7 13 1 7 11 5 10 11 9 3 1 9 12 9 3 10 3 7 1 4 9 6 8 4 5 2 11 2 13 5 7 10 6 9
1
Ouput: 54