loj#3057 「HNOI2019」校园旅行 dp

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

Description


给一个n个点m条边的无向图,每个点有个权值0或1。q次询问x,y求是否存在一条从x到y的路径使得经过节点的权值连接起来是一个回文串
n≤5e3,m≤5e5n\le5e3,m\le5e5n5e3,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;
}
可并堆是一种支持合并操作的堆数据结构,常见的可并堆有左偏树、斜堆、二项堆等。对于 LOJ#P188 可并堆的问题,下面以左偏树为例给出解题思路和代码实现。 ### 解题思路 1. **左偏树的性质**: - 左偏树是一种可并堆,它满足堆性质(小根堆或大根堆),即每个节点的值小于(或大于)其子节点的值。 - 左偏树还满足左偏性质,即每个节点的左子树的距离(到最近的叶子节点的距离)不小于右子树的距离。 2. **合并操作**: - 合并两个左偏树时,比较两个根节点的值,将值较大的根节点的树合并到值较小的根节点的右子树中。 - 合并后,检查右子树的距离是否大于左子树的距离,如果是,则交换左右子树,以维护左偏性质。 3. **插入操作**: - 插入一个新节点可以看作是合并一个只有一个节点的左偏树和原左偏树。 4. **删除操作**: - 删除根节点后,将其左右子树合并成一个新的左偏树。 ### 代码实现 ```python class Node: def __init__(self, val): self.val = val self.left = None self.right = None self.dist = 0 def merge(x, y): if not x: return y if not y: return x if x.val > y.val: x, y = y, x x.right = merge(x.right, y) if not x.left or (x.right and x.left.dist < x.right.dist): x.left, x.right = x.right, x.left x.dist = (x.right.dist + 1) if x.right else 0 return x def insert(root, val): new_node = Node(val) return merge(root, new_node) def delete(root): return merge(root.left, root.right) # 示例使用 root = None root = insert(root, 3) root = insert(root, 1) root = insert(root, 5) print(root.val) # 输出堆顶元素 root = delete(root) print(root.val) # 输出删除堆顶元素后的堆顶元素 ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值