BZOJ 2816 [ZJOI2012]网络 Link Cut Tree

题目描述

有一个无向图G,每个点有个权值,每条边有一个颜色。这个无向图满足以下两个条件:

  1. 对于任意节点连出去的边中,相同颜色的边不超过两条。

  2. 图中不存在同色的环,同色的环指相同颜色的边构成的环。

在这个图上,你要支持以下三种操作:

  1. 修改一个节点的权值。

  2. 修改一条边的颜色。

  3. 查询由颜色c的边构成的图中,所有可能在节点u到节点v之间的简单路径上的节点的权值的最大值。

输入输出格式

输入格式:

输入文件network.in的第一行包含四个正整数N, M, C, K,其中N为节点个数,M为边数,C为边的颜色数,K为操作数。

接下来N行,每行一个正整数vi,为节点i的权值。

之后M行,每行三个正整数u, v, w,为一条连接节点u和节点v的边,颜色为w。满足1 ≤ u, v ≤ N,0 ≤ w < C,保证u ≠ v,且任意两个节点之间最多存在一条边(无论颜色)。

最后K行,每行表示一个操作。每行的第一个整数k表示操作类型。

  1. k = 0为修改节点权值操作,之后两个正整数x和y,表示将节点x的权值vx修改为y。

  2. k = 1为修改边的颜色操作,之后三个正整数u, v和w,表示将连接节点u和节点v的边的颜色修改为颜色w。满足0 ≤ w < C。

  3. k = 2为查询操作,之后三个正整数c, u和v,表示查询所有可能在节点u到节点v之间的由颜色c构成的简单路径上的节点的权值的最大值。如果不存在u和v之间不存在由颜色c构成的路径,那么输出“-1”。

输出格式:

输出文件network.out包含若干行,每行输出一个对应的信息。

  1. 对于修改节点权值操作,不需要输出信息。

  2. 对于修改边的颜色操作,按以下几类输出:

a) 若不存在连接节点u和节点v的边,输出“No such edge.”。

b) 若修改后不满足条件1,不修改边的颜色,并输出“Error 1.”。

c) 若修改后不满足条件2,不修改边的颜色,并输出“Error 2.”。

d) 其他情况,成功修改边的颜色,并输出“Success.”。

输出满足条件的第一条信息即可,即若同时满足b和c,则只需要输出“Error 1.”。

  1. 对于查询操作,直接输出一个整数。

输入输出样例

输入样例#1:
4 5 2 7
1
2
3
4
1 2 0
1 3 1
2 3 0
2 4 1
3 4 0
2 0 1 4
1 1 2 1
1 4 3 1
2 0 1 4
1 2 3 1
0 2 5
2 1 1 4
输出样例#1:
4
Success.
Error 2.
-1
Error 1.
5

说明

颜色0为实线的边,颜色1为虚线的边,

由颜色0构成的从节点1到节点4的路径有1 – 2 – 4,故max{v1, v2, v4} = max{ 1, 2, 4 } = 4。

将连接节点1和节点2的边修改为颜色1,修改成功,输出“Success.”

将连接节点4和节点3的边修改为颜色1,由于修改后会使得存在由颜色1构成的环( 1 – 2 – 4 – 3 – 1 ),不满足条件2,故不修改,并输出“Error 2”。

不存在颜色0构成的从节点1到节点4的边,输出“-1”。

将连接节点2和节点3的边修改为颜色1,由于修改后节点2的连出去的颜色为1的边有3条,故不满足条件1,故不修改,并输出“Error 1.”。

将节点2的权值修改为5。

由颜色1构成的从节点1到节点4的路径有 1 – 2 – 4,故max{v1, v2, v4} = max{ 1, 5, 4 } = 5。

【数据规模】

对于30%的数据:N ≤ 1000,M ≤ 10000,C ≤ 10,K ≤ 1000。

另有20%的数据:N ≤ 10000,M ≤ 100000,C = 1,K ≤ 100000。

对于100%的数据:N ≤ 10000,M ≤ 100000,C ≤ 10,K ≤ 100000。


题面是洛谷上的……

传送门bzoj

传送门洛谷

这题可以说LCT里面的难题了吧……(蒟蒻)

整理一下题目的要求:

1.判断是否存在一个节点连出去>=3条以上的相同色边;

2.判断是否存在一个由相同色边构成的环;

3.更改一个点u的权值;

4.更改u~v之间的边颜色;

5.查询u~v之间由同一色w构成的简单路径(树)中经过点的最大权值。

6.判断u->v的边是否存在。



首先如果颜色只有一种(%20的数据),那么建立一个LCT就可以了。

我们先讨论这种情况。

这种情况下如何维护呢?

对于1,只要记录一下每个点的入度,然后在更改之前判断点的入度是否已经>=2即可;

对于2,在连接u和v之前,如果u和v已经联通,那么加入这条边肯定会成环;

对于3,直接更改,并且splay(全部up)u一遍;

对于4,待会儿讨论;

对于5,在LCT中维护最大值即可。

对于6,放到map里面(类似哈希)判断就好了


那么多种颜色呢?我们观察到颜色最多10种。

那直接建立10个LCT不就好了……

在4的修改上,我们求出原来边的颜色是q,要更改成w,

那么我们只要在LCT[q]里面删除这条边,然后在LCT[w]里面连上就好了。

同样地,3的地方我们也要进行更改:所有的LCT里面的u都要splay一遍。


差不多思想就好了,但是此题还有一些细节……

首先,如何保存原来的边的颜色呢?

我们用一个map存储就好了。

然后,此题有个坑!

就是更改的颜色可能=原来的颜色!

这种情况success。。

那么特判吧……然而!

特别判断的顺序也有关系…………

必须在判断no such edge之后!!

气啊……



#include<bits/stdc++.h>
using namespace std;
int read(){
    int x=0,f=1;char ch=getchar();
    while (ch<'0' || ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int 
	N=10005,
	M=100005;
int n,m,C,K;
int v[N],stk[N];
struct Edge{
	int u,v;
	bool operator <(Edge x)const{
		if (x.u!=u) return x.u>u;
			else return x.v>v; 
	}
};
map<Edge,int>E;
struct LinkCutTree{
	int pre[N],son[N][2],Max[N],cnt[N];
	bool rev[N];
	bool isroot(int x){
		return son[pre[x]][0]!=x &&
				son[pre[x]][1]!=x;
	}
	void up(int x){
		Max[x]=v[x];
		int l=son[x][0],r=son[x][1];
		Max[x]=max(Max[l],max(Max[x],Max[r]));
	}
	void down(int x){
		if (rev[x]){
			int l=son[x][0],r=son[x][1];
			swap(son[x][0],son[x][1]);
			rev[l]^=1,rev[r]^=1,rev[x]^=1;
		}
	}
	void Rotate(int x){
		int y=pre[x],z=pre[y],l,r;
		if (son[y][0]==x) l=0; else l=1;
		r=l^1;
		if (!isroot(y))
			if (son[z][0]==y) son[z][0]=x;
				else son[z][1]=x;
		pre[x]=z; pre[y]=x;
		pre[son[x][r]]=y;
		son[y][l]=son[x][r];
		son[x][r]=y; up(y);
	}
	void splay(int x){
		int y,z,top=0;
		stk[++top]=x;
		for (int i=x;!isroot(i);i=pre[i])
			stk[++top]=pre[i];
		while (top) down(stk[top--]);
		while (!isroot(x)){
			y=pre[x],z=pre[y];
			if (!isroot(y))
				if (son[y][0]==x^son[z][0]==y) Rotate(x);
					else Rotate(y);
			Rotate(x);
		}
		up(x);
	}
	void access(int x){
		for (int y=0;x;y=x,x=pre[x])
			splay(x),son[x][1]=y,up(x);
	}
	void makeroot(int x){
		access(x),splay(x);
		rev[x]^=1;
	}
	void split(int x,int y){
		makeroot(x);
		access(y),splay(y);
	}
	int findroot(int x){
		access(x),splay(x);
		while (son[x][0]) x=son[x][0];
		return x;
	}
	bool sameset(int x,int y){
		return findroot(x)==findroot(y);
	}
	void cut(int x,int y){
		split(x,y);
		cnt[x]--,cnt[y]--;
		pre[x]=son[y][0]=0;
		up(y);
	}
	void link(int x,int y){
		makeroot(x);
		cnt[x]++,cnt[y]++;
		pre[x]=y;
	}
	int querymax(int x,int y){
		split(x,y);
		return Max[y];
	}
}LCT[15];
int main(){
	n=read(),m=read(),C=read(),K=read();
	for (int i=1;i<=n;i++) v[i]=read();
	int opt,x,y,w; Edge t1;
	while (m--){
		x=read(),y=read(),w=read();
		if (x>y) swap(x,y);
		t1=(Edge){x,y},E[t1]=w;
		LCT[w].link(x,y);
	}
	while (K--){
		opt=read();
		if (!opt){
			x=read(),y=read();
			v[x]=y;
			for (int i=0;i<C;i++) LCT[i].splay(x);
		}	else
		if (opt==1){
			x=read(),y=read(),w=read();
			if (x>y) swap(x,y);
			t1=(Edge){x,y};
			if (!E.count(t1)){
				puts("No such edge.");
				continue;
			}
			if (E[t1]==w){
				puts("Success.");
				continue;
			}
			if (LCT[w].cnt[x]>=2 || LCT[w].cnt[y]>=2){
				puts("Error 1.");
				continue;
			}
			if (LCT[w].sameset(x,y)){
				puts("Error 2.");
				continue;
			}
			puts("Success.");
			int tmp=E[t1];
			LCT[tmp].cut(x,y),LCT[w].link(x,y);
			E[t1]=w;
			t1=(Edge){y,x}; E[t1]=w;
		}	else{
			w=read(),x=read(),y=read();
			if (x==y) printf("%d\n",v[x]);
				else
			if (!LCT[w].sameset(x,y))
				puts("-1"); else
				printf("%d\n",LCT[w].querymax(x,y));
		}
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值