2022CCPC 广州 C. Customs Controls 2 codeforces104053C

72 篇文章 0 订阅
10 篇文章 0 订阅

https://codeforces.com/gym/104053/problem/C

题目大意:有一个n个点m条边的有向无环图,要求从1号点到n号点的所有路径的带权路径长度相同,问每个点的点权应该是多少

1<=n<=2e5;1<=m<=5e5

思路:我们发现,对于每一个有多条入边的点,所有到该点的父结点的路径长度应该相等,这样到达该点的所有路径长度就都是相等的,那么我们可以把这样的点的所有父结点用并查集缩点,然后构建新的图,再重新判断这个图是否是无环图,同时,如果两个点有相同的子节点,但这两个点之间也有连边的话,也是没法使所有路径长度相等的,然后找出1到每个点边权为1时的最长路,也就是到每个点最多要经过多少个点,然后让每一条到n的路的点权和都等于到n的最多点数即可,也就是对于每两个节点u,v 令v的点权等于到v的最长路-到u的最长路

#include<iostream>
#include<cstdio>
#include<vector>
#include<map>
#include<queue>
using namespace std;
const int N = 2e5 + 5;
int head[N], cnt = 0;
struct Edge 
{
	int u, v, w, next;
}e[500005];
void addedge(int u, int v, int w)
{//链式前向星存图
	e[++cnt].u = u;
	e[cnt].v = v;
	e[cnt].w = w;
	e[cnt].next = head[u];
	head[u] = cnt;
}
vector<int>e1[N], e2[N];
int n, m;
int fa[N], ans[N];
bool vis1[N], vis2[N];
int dis[N];
void init()
{
	cnt = 0;
	for (int i = 1; i <= n; i++)
	{
		vis1[i] = vis2[i] = 0;
		head[i] = -1;
		e1[i].clear();
		e2[i].clear();
		fa[i] = i;
		dis[i] = 0;
	}
}
int find(int x)
{//并查集
	return fa[x] == x ? x : find(fa[x]);
}
bool dfs(int u)
{//判断有没有环
	if (vis1[u])
		return 0;//记录先序遍历访问过的节点,如果当前点被访问过,说明出现了环
	if (vis2[u])
		return 1;//记录后序遍历访问过的节点
	vis1[u] = 1;
	for (int i = head[u]; ~i; i = e[i].next)
	{
		int v = e[i].v;
		if (!vis2[v])
		{
			if (!dfs(v))
				return 0;
		}
	}
	vis1[u] = 0;//后序遍历回溯时重置先序遍历过的点
	vis2[u] = 1;
	return 1;
}
void bfs()
{
	dis[1] = 1;
	priority_queue<pair<int, int> >q;//长度从大到小排序
	q.push(make_pair(1, 1));
	while (!q.empty())
	{
		int u = q.top().second, d = q.top().first;
		q.pop();
		for (int i = head[u]; ~i; i = e[i].next)
		{
			int v = e[i].v;
			if (dis[v] < dis[u] + 1)
			{//求最长路
				dis[v] = dis[u] + 1;
				q.push(make_pair(dis[v], v));
			}
		}
	}
	queue<int>qu;//再进行一次bfs求所有点的点权
	qu.push(1);
	ans[1] = 1;
	while (!qu.empty())
	{
		int u = qu.front();
		qu.pop();
		if (vis1[u])
			continue;
		vis1[u] = 1;
		for (int i = 0; i < e1[u].size(); i++)
		{
			int v = e1[u][i];
			ans[v] = dis[find(v)] - dis[find(u)];//子节点和父节点相差的长度
			qu.push(v);
		}
	}
}
int main()
{
	int t;
	cin >> t;
	while (t--)
	{		
		scanf("%d%d", &n, &m);
		init();
		for (int i = 1; i <= m; i++)
		{
			int u, v;
			scanf("%d%d", &u, &v);
			e1[u].push_back(v);//正向存的原始图
			e2[v].push_back(u);//反向图
		}
		for (int i = 1; i <= n; i++)
		{
			if (e2[i].empty())
				continue;
			int u = e2[i][0];
			for (int j = 0; j < e2[i].size(); j++)
			{
				fa[find(e2[i][j])] = fa[find(u)];//对于反向图中的每一个点,将他的所有子节点放入一个并查集中
			}
		}
		map<pair<int, int>, int>ma;
		bool flag = 1;
		for (int i = 1; i <= n; i++)
		{
			int u = find(i);			
			for (int j = 0; j < e1[i].size(); j++)
			{
				int v = find(e1[i][j]);
				if (u == v)//两个有共同子节点的点之间有连边
				{
					flag = 0;
					break;
				}
				if (ma.find(make_pair(u, v)) == ma.end())
				{//新图中不计重边
					addedge(u, v, 1);
					ma[make_pair(u, v)] = 1;
				}
			}
			if (!flag)
				break;
		}
		if (!flag||!dfs(1))
		{
			printf("No\n");
			continue;
		}
		printf("Yes\n");
		for (int i = 1; i <= n; i++)
		{
			vis1[i] = 0;
		}
		bfs();
		for (int i = 1; i <= n; i++)
		{
			printf("%d ", ans[i]);
		}
		printf("\n");
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timidcatt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值