CF325C Monsters and Diamonds - 最短路 - dp -

题目链接:https://codeforces.com/problemset/problem/325/C

题解:
先建出来图,每条边连接一个点(起始的怪物)和一堆点(能爆出来的怪物)
先考虑min的情况,考虑反着跑最短路(从只能爆出钻石的怪物开跑),发现如果一个怪物能由其爆出来的怪物更新的话,爆出来的怪物就一定已经松弛完毕了(换句话说,此时这些点的dis一定是最短路了)
而某一个点是否松弛完毕了我们是可以更新的(用优先队列维护dis最小值,堆首的就是松弛完毕的点,然后用这个点去更新其它点的最短路)
然后每次跑dijkstra的时候,用当前点去(反向)更新原来的怪物,如果对于某条路线,松弛完毕的点就是所有原来的怪物能爆出来的怪物的话,那么就可以用所有爆出来的怪物更新原来的怪物。
再考虑max。直接从任一点开始记忆化搜索,因为记忆化了,所以每个点最多访问常数次。如果当前结点不可到达(第一问是-1),那第二问肯定也是-1,否则访问所有正向边,如果对于某组爆出的怪物来说,存在其中一个为-1(一直消不掉有怪物),那么这组先pass,否则如果有一个-2,那肯定答案是-2,否则就按出现次数 * 对应怪物的答案来更新(注意次序!)
细节很多

// by SkyRainWind
#include <cstdio>
#include <vector>
#include <cassert>
#include <cstring>
#include <map>
#include <iostream>
#include <queue>
#include <set>
#include <algorithm>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
#define pii pair<int,int>

using namespace std;

typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 1e5 + 5, mod = 314000000;

int m,n;
int cnt[maxn], to[maxn], v[maxn];
set<int>S[maxn]; 	// 用于存当前点对应哪些反向边(爆出的怪物 -> 原来的怪物) 

struct pt{
	int to, dis;
	pt(int to,int dis):to(to),dis(dis){}
};
bool operator < (pt a, pt b){
	return a.dis > b.dis;
}
priority_queue<pt>pq;
int dis[maxn];
// dijkstra

vector<int>edge[maxn];	// 正向边(和S[]像) 
int op_edge[maxn], cnt_edge[maxn];	
// op_edge[] 当前反向边最优的情况下由(已经松弛过了的)爆出怪物所产生的钻石数,就是用每次松弛过的点来更新保证最优 
// cnt_edge[] 来判断对于某条边,是否已经凑齐了爆出的怪物(来更新原来的) 
map<int,int>num[maxn];	// 当前正向边某个怪物出现了几次 
int ans[maxn][2];

void upd(int &x){x = x >= mod ? mod : x;}

void solve1(){
	for(int i=1;i<=m;i++){
		// 初始结点就是能只爆钻石的怪物 
		if(num[i].size() == 0)pq.push(pt(to[i], cnt[i])), dis[to[i]] = min(dis[to[i]], cnt[i]);
	}
	while(!pq.empty()){
		pt tp = pq.top();pq.pop();
		int curv = tp.to;if(v[curv])continue;
		v[curv] = 1;
		
		for(int u : S[curv]){	// 利用当前松弛过了的结点(因此钻石数最少)来作为爆出怪物更新原来怪物 
				// *num -> 一个怪物可能爆出多个相同怪物 
			op_edge[u] += 1ll * dis[curv] * num[u][curv] >= mod ? mod : dis[curv] * num[u][curv];
			upd(op_edge[u]);
			
 			cnt_edge[u] ++;
			if(cnt_edge[u] == num[u].size()){	// 所有爆出的怪物都已经松弛过了 
				// +cnt[u] -> 这个怪物可能自身爆出钻石 
				if(op_edge[u] + cnt[u] < dis[to[u]]){
					dis[to[u]] = op_edge[u] + cnt[u];
					upd(dis[to[u]]);
					
					pq.push(pt(to[u], dis[to[u]]));
				}
			}
		}
	}
	for(int i=1;i<=n;i++)ans[i][0] = dis[i] == INF ? -1 : dis[i];
}

int vis[maxn], instk[maxn];	// instk[] 判环 vis[] 记忆化 

int dfs(int x){
	vis[x] = 1;
	instk[x] = 1;
	if(ans[x][0] == -1)return ans[x][1] = -1;
	for(int u : edge[x]){
		int fg = 0, rr = 0;
		for(pii v : num[u]){
			int vf = v.first, vs = v.second;
			// (x -> vf) vs次 
			if(ans[vf][0] == -1){fg = 1;break;}
			if(instk[vf]){fg = 2;continue;}	// 优先级 先 -1 再 -2 
			
			int val = vis[vf] ? ans[vf][1] : dfs(vf);
			
			if(val == -2){fg = 2;continue;}
			rr += 1ll * val * vs >= mod ? mod : val * vs; 
			upd(rr);
		}
		if(fg == 1){continue;}
		if(fg == 2){instk[x] = 0;return ans[x][1] = -2;}
		ans[x][1] = max(ans[x][1], rr + cnt[u]);
		upd(ans[x][1]);
	}
	instk[x] = 0;
	return ans[x][1];
}

void solve2(){
	for(int i=1;i<=n;i++)
		if(!vis[i])dfs(i);
}

signed main(){
	scanf("%d%d",&m,&n);
	memset(dis, 0x3f, sizeof dis);
	for(int i=1;i<=m;i++){
		int cur;
		int t;scanf("%d%d",&cur,&t);
		to[i] = cur;
		edge[cur].push_back(i);
		while(t--){
			int tmp;scanf("%d",&tmp);
			if(tmp == -1){
				++ cnt[i];
			}else{
				S[tmp].insert(i);
				++ num[i][tmp];
			}
		}
	}
	solve1();
	solve2();
	for(int i=1;i<=n;i++)printf("%d %d\n",ans[i][0],ans[i][1]);

	return 0;
}

posted @ 2022-11-27 23:15  SkyRainWind  阅读(35)  评论(0编辑  收藏  举报