loj#3057 「HNOI2019」校园旅行 dp

本文探讨了一个关于图论的问题,即在一个具有特定点权值的无向图中,寻找是否存在一条路径,使得路径上经过的节点权值构成的序列形成回文串。通过将边分类和优化连通块,提出了一种高效算法,将复杂度降低至O(n),并给出了详细的解决方案和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description


给一个n个点m条边的无向图,每个点有个权值0或1。q次询问x,y求是否存在一条从x到y的路径使得经过节点的权值连接起来是一个回文串
n ≤ 5 e 3 , m ≤ 5 e 5 n\le5e3,m\le5e5 n5e3,m5e5

Solution


这个是loj加强过的数据。。原题好像是3e3的

m2的做法就是按照原图dp,设f[x,y]表示x到y有一条合法路径,枚举x和y的邻边转移就可以做到m2。实现的时候开一个队列,像bfs一样把新增的合法路径扔进队尾就可以了

可以发现瓶颈在于边数太多了。考虑按照颜色把边分成3类,嘿嘿、嘿掰、掰掰边。可以发现现在经过一个连通块的路径颜色都是一样的,这样就只和路径长度相关了。由于我们可以反复经过一个点,那么就只和奇偶性有关了。

考虑按照是否存在奇环把连通块分两类。
若不存在奇环,那么我们可以把连通块黑白染色,一条从黑点到白点的路径长度的奇偶性就是确定的。所以我们只需要保留原连通块的连通性,原本绕圈的操作可以通过反复横跳凑够。

若存在奇环,那么就会出现奇偶性改变的情况,这个时候我们只需要随便找个点x连一个自环就行了。可以发现这样就能实现反复横跳的同时改变奇偶性。

然后我们就成功地把边数降到了O(n)级别,套用上面那个做法就可以A辣

Code


#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <vector>
#include <queue>
#define rep(i,st,ed) for (int i=st;i<=ed;++i)

const int N=2005;
const int E=50005;

std:: vector <int> G[N];
std:: queue <int> que;

struct Graph {
	struct edge {int x,y,next;} e[E];

	int d[N],ls[N],edCnt;
	int c[N],flag;

	void add_edge(int x,int y) {
		e[++edCnt]=(edge) {x,y,ls[x]}; ls[x]=edCnt;
		e[++edCnt]=(edge) {y,x,ls[y]}; ls[y]=edCnt;
	}

	void dfs(int x,int id) {
		c[x]=id;
		for (int i=ls[x];i;i=e[i].next) {
			if (c[e[i].y]==-1) {
				dfs(e[i].y,!id);
				G[x].push_back(e[i].y);
				G[e[i].y].push_back(x);
			} else if (c[e[i].y]==id) flag=true;
		}
	}

	void build(int n) {
		rep(i,1,n) c[i]=-1;
		rep(i,1,n) if (c[i]==-1) {
			flag=false;
			dfs(i,1);
			if (flag) G[i].push_back(i);
		}
	}
} BB,BW,WW;

bool f[N][N];

char s[N];

void ins(int x,int y) {
	if (x>y) std:: swap(x,y);
	if (f[x][y]) return ;
	que.push(x),que.push(y);
	f[x][y]=f[y][x]=1;
}

int main(void) {
	freopen("data.in","r",stdin);
	int n,m,k; scanf("%d%d%d",&n,&m,&k);
	scanf("%s",s+1);
	rep(i,1,n) ins(i,i);
	rep(i,1,m) {
		int x,y; scanf("%d%d",&x,&y);
		if (s[x]==s[y]) {
			if (s[x]=='1') BB.add_edge(x,y);
			else WW.add_edge(x,y);
			ins(x,y);
		} else BW.add_edge(x,y);
	}
	BB.build(n),WW.build(n),BW.build(n);
	for (;!que.empty();) {
		int x=que.front(); que.pop();
		int y=que.front(); que.pop();
		for (int i=0;i<G[x].size();++i) {
			for (int j=0;j<G[y].size();++j) {
				int a=G[x][i],b=G[y][j];
				if (s[a]==s[b]) ins(a,b);
			}
		}
	}
	for (;k--;) {
		int x,y; scanf("%d%d",&x,&y);
		puts(f[x][y]?"YES":"NO");
	}
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值