YbtOJ 「图论」 第1章 并查集

并查集

判断该节点的祖先的一种结构 在向上合并的时候使用路径压缩降低复杂度 注意需要初始化fa[i]=i

int fa[100001];//fa是标记每个元素的代表元素(祖先) 
//并查集是森林,集合是树,合并操作是把两棵树并到一起 
int find ( int x )//查找 
{
	if ( fa[x] == x ) return x;
	return fa[x] = find ( fa[x] );//在路径上就把每个根节点接到了祖先节点上 
	//== fa[x] = find ( fa[x] ); return fa[x]; 
}
void merge ( int x , int y )//合并
{
	int fx = find ( x ) , fy = find ( y );
	fa[fx] = fy;
	return; 
}  

A. 【例题1】【模板】并查集

[题目描述]

如题,现在有一个并查集,你需要完成合并和查询操作。

[输入格式]

第一行包含两个整数 N,M ,表示共有 N 个元素和 M 个操作。

接下来 M 行,每行包含三个整数 Zi,Xi,Yi

Zi=1 时,将 XiYi 所在的集合合并。

Zi=2 时,输出 XiYi 是否在同一集合内,是的输出
Y ;否则输出 N

[输出格式]

对于每一个 Zi=2 的操作,都有一行输出,每行包含一个大写字母,为 Y 或者 N

[算法分析]

裸题

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e4 + 5;
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int fa[N] , n , m;

int find ( int x )
{
	if ( x == fa[x] ) return x;
	else return fa[x] = find(fa[x]);	
}

void merge ( int x , int y ) 
{
	int fx = find(x) , fy = find(y);
	if ( fx != fy ) fa[fx] = fy;
}

signed main ()
{
	n = read() , m = read();
	for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
	for ( int i = 1 , op , u , v ; i <= m ; i ++ )
	{
		op = read() , u = read() , v = read();
		if ( op == 1 ) merge ( u , v );
		else printf ( "%c\n" , find(u) == find(v) ? 'Y' : 'N' );
	}
	return 0;
}

B. 【例题2】银河英雄传说

[题目背景]

公元 5801 年,地球居民迁至金牛座 α 第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。

宇宙历 799 年,银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征,气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。

[题目描述]

杨威利擅长排兵布阵,巧妙运用各种战术屡次以少胜多,难免恣生骄气。在这次决战中,他将巴米利恩星域战场划分成 30000 列,每列依次编号为 1,2,,30000。之后,他把自己的战舰也依次编号为 1,2,,30000,让第 i 号战舰处于第 i 列,形成“一字长蛇阵”,诱敌深入。这是初始阵形。当进犯之敌到达时,杨威利会多次发布合并指令,将大部分战舰集中在某几列上,实施密集攻击。合并指令为 M i j,含义为第 i 号战舰所在的整个战舰队列,作为一个整体(头在前尾在后)接至第 j 号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。

然而,老谋深算的莱因哈特早已在战略上取得了主动。在交战中,他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。

在杨威利发布指令调动舰队的同时,莱因哈特为了及时了解当前杨威利的战舰分布情况,也会发出一些询问指令:C i j。该指令意思是,询问电脑,杨威利的第 i 号战舰与第 j 号战舰当前是否在同一列中,如果在同一列中,那么它们之间布置有多少战舰。

作为一个资深的高级程序设计员,你被要求编写程序分析杨威利的指令,以及回答莱因哈特的询问。

[输入格式]

第一行有一个整数 T1T5×105),表示总共有 T 条指令。

以下有 T 行,每行有一条指令。指令有两种格式:

  1. M i jij 是两个整数(1i,j30000),表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令,并且保证第 i 号战舰与第 j 号战舰不在同一列。

  2. C i jij 是两个整数(1i,j30000),表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。

[输出格式]

依次对输入的每一条指令进行分析和处理:

  • 如果是杨威利发布的舰队调动指令,则表示舰队排列发生了变化,你的程序要注意到这一点,但是不要输出任何信息。
  • 如果是莱因哈特发布的询问指令,你的程序要输出一行,仅包含一个整数,表示在同一列上,第 i 号战舰与第 j 号战舰之间布置的战舰数目。如果第 i 号战舰与第 j 号战舰当前不在同一列上,则输出 1

[算法分析]

开一个dis数组表示i节点到队首节点的距离 那么两艘飞船之间的距离就是 i节点到队首的距离和j节点到队首的距离的差值(的绝对值)减一

再开一个sz数组表示以i为队头的队列的飞船数量

在每一次合并的时候 将第一列接到第二列尾部 所以要将第一列的队头dis赋值为第二列的队头sz 再将第二列的队头sz加上第一列的队头sz

这样 在每一次寻找的时候 每一个节点到队头的距离就等于每一个节点到祖先的距离上这个祖先到队头的距离

这个操作的正确性是通过路径压缩来保证的 即查询时每一个点向上跳到它的祖先节点最多只有两层(第一层是它原本集合的祖先节点 第二层是这个祖先合并到的集合的祖先节点)

注意 每一个节点到祖先的距离指的就是原来的fa[x] 也就是并查集中的根节点 而并不是查询后的根节点

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 3e4 + 5;
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int fa[N] , n , m , sz[N] , dis[N];//记录每一个祖先集合的大小 和这个节点离祖先的距离
char op;

int find ( int x )
{
	if ( x == fa[x] ) return x;
	else 
	{
		int fx = find(fa[x]);//每一次将祖先找到
		dis[x] += dis[fa[x]];//将这个节点到队头的距离赋值为上一个
		return fa[x] = fx;
	}
}

void merge ( int x , int y ) //x接到y后面
{
	int fx = find(x) , fy = find(y);
	fa[fx] = fy;
	dis[fx] = sz[fy];//记录这个节点到队头的距离
	sz[fy] += sz[fx];//以这个顶点为队头的队列中有多少个节点
}

signed main ()
{
	m = read();
	for ( int i = 1 ; i <= 30000 ; i ++ )
		sz[i] = 1 , fa[i] = i;
	for ( int i = 1 , u , v ; i <= m ; i ++ )
	{
		cin >> op >> u >> v;
		if ( op == 'M' ) merge ( u , v );
		else 
		{
			int fu = find(u) , fv = find(v);
			if ( fu == fv ) printf ( "%d\n" , abs ( dis[u] - dis[v] ) - 1 );
			else printf ( "-1\n" );
		}
	}
	return 0;
}

C. 【例题3】食物链

[题目描述]

动物王国中有三类动物 A,B,C,这三类动物的食物链构成了有趣的环形。ABBCCA

现有 N 个动物,以 1N 编号。每个动物都是 A,B,C 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 N 个动物所构成的食物链关系进行描述:

  • 第一种说法是 1 X Y,表示 XY 是同类。
  • 第二种说法是2 X Y,表示 XY

此人对 N 个动物,用上述两种说法,一句接一句地说出 K 句话,这 K 句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  • 当前的话与前面的某些真的话冲突,就是假话;
  • 当前的话中 XYN 大,就是假话;
  • 当前的话表示 XX,就是假话。

你的任务是根据给定的 NK 句话,输出假话的总数。

[输入格式]

第一行两个整数,N,K,表示有 N 个动物,K 句话。

第二行开始每行一句话(按照题目要求,见样例)

[输出格式]

一行,一个整数,表示假话的总数。

[算法分析]

并查集还是维护同类的关系 即"A的天敌和B的猎物是否是同类"这样的关系 那么我们为每一个动物开三个点 分别表示这个动物本身,天敌和同类(一倍存本身 二倍存天敌 三倍存猎物)

  1. 查询同类: 如果第一个点是第二个点的天敌或者猎物 那么是假话 否则是真话 将这两个点的本身,天敌,猎物分别合并到同一个集合中
  2. 查询xy时 如果xy的同类或者猎物 那么是假话 否则是真话 合并对应集合

[代码实现]

#include <bits/stdc++.h>

using namespace std;

#define inl inline

const int N = 5e4 + 5;

inl int read ()

{

	int x = 0 , f = 1;

	char ch = getchar();

	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }

	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }

	return x * f;

} 

int fa[N*3] , n , k , cnt;

int find ( int x )

{

	if ( fa[x] == x ) return x;

	return fa[x] = find ( fa[x] );

}



void merge ( int x , int y )

{

	int fx = find(x), fy = find(y);

	fa[fy] = fx;

}

signed main ()

{

	n = read() , k = read();

	for ( int i = 1 ; i <= 3 * n ; i ++ ) fa[i] = i;

	for ( int i = 1 , op , x , y ; i <= k ; i ++ )

	{

		op = read() , x = read() , y = read();

		if ( x > n || y > n ) { cnt ++; continue; }

		if ( op == 1 ) 

		{

			if ( find(x+n) == find(y) || find(x+n*2) == find(y) ) { cnt ++; continue; }

			merge ( x , y ) , merge ( x + n , y + n ) , merge ( x + 2 * n , y + 2 * n );

		}

		else 

		{

			if ( find(x) == find(y) || find(x+2*n) == find(y) ) { cnt ++; continue; }

			merge ( x , y + 2 * n ) , merge ( x + n , y ) , merge ( x + 2 * n , y + n );

		}

	}

	printf ( "%d" , cnt );

	return 0;

} 


D. 【例题4】超市购物

[题面翻译]

  • 给定 n 件物品,第 i 件物品有如下信息:
    • 卖出去可以得到 pi 的收益。
    • 过期时间为 di,过了过期时间就不能再卖出去。
  • 卖掉一件物品要用 1 的时间,求最大收益。
  • 多组数据,每组数据一行,首先一个整数 n 然后 n 对数 pi,di,以文件终止符结束。
  • 0n1041pi,di104

[算法分析]

对于每一个点按照权值大小进行排序

初始对于每一个截止日期维护一个并查集 集合的根就表示从这个日期开始向前数(包括自己) 第一个空闲的日期 初始fa[i]=i

我们的贪心策略是:每一件物品都尽量晚地卖出去

所以 每一次搜索这个集合中还有没有能安排给它的空闲时间了 如果有 那么这个集合的根节点的父亲改为这个空闲时间(也就是老的根节点)-1

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e4 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
} 
int fa[N] , n , ans , longest;
struct node 
{
	int p , d;
	bool operator < ( const node &a ) const
	{
		return p > a.p;
	}
}e[N];
int find ( int x )
{
	if ( fa[x] == x ) return x;
	return fa[x] = find ( fa[x] );
}
void init()
{
	longest = 0;
	ans = 0;
	memset ( e , 0 , sizeof ( e ) );
}
int main()
{
	while ( scanf ( "%d" , &n ) == 1 )
	{
		init();
		for ( int i = 1 ; i <= n ; i ++ )
		{
			e[i].p = read() , e[i].d = read();
			longest = max ( longest , e[i].d );//longest是最长的过期时间
		}
		sort ( e + 1 , e + n + 1 );//权值最大的放在前面
		for ( int i = 1 ; i <= longest ; i ++ ) fa[i] = i;//每一个时间节点开一个并查集
		//每一个日期的并查集的root就是从这个日期开始向前数(包括自己) 第一个空闲的日期
		for ( int i = 1 ; i <= n ; i ++ )
		{
			int xx = find ( e[i].d );//能安排给他的完成时间
			if ( xx != 0 )//如果前面还有空闲时间
			{
				ans += e[i].p;
				fa[xx] = xx - 1;//贪心策略就是这个物品越晚卖掉越好
			}
		}
		printf ( "%d\n" , ans );
	}
	return 0;
} 

E. 【例题5】逐个击破

[题目背景]

三大战役的平津战场上,傅作义集团在以北平、天津为中心,东起唐山西至张家口的铁路线上摆起子一字长蛇阵,并企图在溃败时从海上南逃或向西逃窜。为了就地歼敌不让其逃走,指挥官制定了先切断敌人东西两头退路然后再逐个歼灭敌人的战略方针。秉承伟大军事家的战略思想,作为一个有智慧的军长你,遇到了一个类似的战场局面。

[题目描述]

现在有 N 个城市,其中 K 个被敌方军团占领了,N 个城市间有 N1 条公路相连,破坏其中某条公路的代价是已知的,现在,告诉你 K 个敌方军团所在的城市,以及所有公路破坏的代价,请你算出花费最少的代价将这 K 个地方军团互相隔离开,以便第二步逐个击破敌人。

[输入格式]

第一行包含两个正整数 NK

第二行包含 K 个整数,表示哪个城市别敌军占领。

接下来 N1 行,每行包含三个正整数 a,b,c,表示从 a 城市到 b 城市有一条公路,以及破坏的代价 c。城市的编号从 0 开始。

[输出格式]

输出一行一个整数,表示最少花费的代价。

[算法分析]

将所有敌军军团称为特殊点 则本题需要求的就是使这些点不联通的最小删边方案

正难则反 我们要求最小删边方案 也就是求这些点不联通的最大加边方案 (答案减边权相当于原图中加上了这条边 因为我们要求删边最小值 所以要满足连边最大值)

即:先将所有节点按照代价降序排序 每一次看最大代价的边的两个端点是否都是特殊点

  • 如果都是特殊点 加上这条边就连通了 显然不可以
  • 如果两条边在同一集合中显然可以(因为保证了在一个集合中的所有点只有小于等于一个特殊点)
  • 如果不是两个都是特殊点就连边 再将非特殊点集合并入特殊点集合中

[代码实现]

#include <bits/stdc++.h>
using namespace std;
#define inl inline


#define int long long


const int N = 1e6 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
} 

int sum , ans;
struct edge { int u , v , w; } e[N];
int fa[N] , n , k , m , in[N];
int find ( int x ) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

signed main ()
{
	n = read() , m = read() , k = read();
	for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
	for ( int i = 1 ; i <= k ; i ++ ) in[read()+1] = 1;
	for ( int i = 1 ; i <= m ; i ++ ) e[i].u = read() + 1, e[i].v = read() + 1 , e[i].w = read() , ans += e[i].w;
	sort ( e + 1 , e + m + 1 , [](const edge a , const edge b) { return a.w > b.w; } );
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int fu = find(e[i].u) , fv = find(e[i].v);
		if ( fu == fv ) ans -= e[i].w;//如果在同一个集合中 进行连边
		else if ( !in[fu] || !in[fv] )/
		{
			ans -= e[i].w;
			if ( !in[fu] ) fa[fu] = fv;//并入特殊点的集合
			else fa[fv] = fu;
		}
		//最终目的是让所有集合中有些只有一个敌方节点
	}	
	printf ( "%lld" , ans );
	return 0;
}

F. 1.躲避拥挤

观察到询问的最小边权如果按照从小到大排序的话 后面的询问答案一定是在前面的询问答案基础之上加上一些贡献得到的

所以考虑从小到大排序边权和询问 对于每一次询问 将所有符合条件的边全部加入 用并查集统计答案并维护图的连通性

需要注意:在加边的时候 如果祖先节点相同的话 说明这两个点在前面连边的时候已经计算过贡献了 直接跳过

#include <bits/stdc++.h>
using namespace std;
#define inl inline
const int N = 1e5 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , m , q , fa[N] , sz[N] , ans[N] , res;

struct node { int u , v , w; } a[N];
struct query { int id , val; } qq[N];

int find ( int x ) 
{
	if ( x == fa[x] ) return x;
	else return fa[x] = find(fa[x]);
}

void merge ( int x , int y )
{
	int fx = find(x) , fy = find(y);
	if ( fx == fy ) return;
	fa[fx] = fy;
	res += sz[fx] * sz[fy] * 2;
	sz[fy] += sz[fx];
	
}

void init()
{
	memset ( a , 0 , sizeof a );
	memset ( ans , 0 , sizeof ans );
	memset ( qq , 0 , sizeof qq );
	res = 0;
}

signed main ()
{
	int T = read();
	while ( T -- )
	{
		init();
		n = read() , m = read() , q = read();
		for ( int i = 1 ; i <= n ; i ++ ) sz[i] = 1 , fa[i] = i;
		for ( int i = 1 ; i <= m ; i ++ ) a[i].u = read() , a[i].v = read() , a[i].w = read();
		for ( int i = 1 ; i <= q ; i ++ ) qq[i].id = i , qq[i].val = read();
		sort ( a + 1 , a + m + 1 , [](const node a , const node b) { return a.w < b.w; } );
		sort ( qq + 1 , qq + q + 1 , [](const query a , const query b) { return a.val < b.val; } );
		int stp = 1;
		for ( int i = 1 ; i <= q ; i ++ )
		{
			while ( stp <= m && a[stp].w <= qq[i].val )
			{
				merge ( a[stp].u , a[stp].v );
				stp ++;
			}
			ans[qq[i].id] = res;
		}
		for ( int i = 1 ; i <= q ; i ++ ) cout << ans[i] << endl;
	}
	return 0;
}

G. 2.约束条件

因为值域很大 所以想到离散化 将所有相等的条件排在前面处理 如果后面的不等关系出现冲突则输出NO 否则输出YES

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e6 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , fa[N] , b[N] , cnt ;

struct node { int e , x , y; } a[N];

int find ( int x ) 
{
	if ( x == fa[x] ) return x;
	else return fa[x] = find(fa[x]);
}

void init()
{
	memset ( a , 0 , sizeof a );
	cnt = 0;
	
}

signed main ()
{
	int T = read();
	while ( T -- )
	{
		init();
		n = read();
		for ( int i = 1 ; i <= 1e6 ; i ++ ) fa[i] = i;
		for ( int i = 1 ; i <= n ; i ++ ) 
		{
			a[i].x = read() , a[i].y = read() , a[i].e = read();
			b[++cnt] = a[i].x , b[++cnt] = a[i].y;
		}
		sort ( b + 1 , b + cnt + 1 );
		int sz = unique ( b + 1 , b + cnt + 1 ) - b - 1;
		for ( int i = 1 ; i <= n ; i ++ ) 
		{
			a[i].x = lower_bound ( b + 1 , b + sz + 1 , a[i].x ) - b;
			a[i].y = lower_bound ( b + 1 , b + sz + 1 , a[i].y ) - b;
		}
		sort ( a + 1 , a + n + 1 , [](const node &a , const node &b) { return a.e > b.e; } );
		int flag = 1;
		for ( int i = 1 ; i <= n ; i ++ ) 
		{
			int fx = find(a[i].x) , fy = find(a[i].y);
			if ( fx == fy && !a[i].e ) { cout << "NO" << endl; flag = 0; break; } 
			if ( fx != fy && a[i].e ) fa[fx] = fy;
		}
		if ( flag ) cout << "YES" << endl;
	}	
	return 0;
}

H. 3.染色操作

一道神题 设置fa[i]表示从i位置开始向右第一个黑点的位置(包括自己) 在l,r区间内不断跳 合并集合并更新答案即可

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e6 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , m , fa[N] , ans;//fa[i]表示从i位置开始向右第一个黑点的位置(包括自己

struct node { int u , v , w; } a[N];

int find ( int x ) 
{
	if ( x == fa[x] ) return x;
	else return fa[x] = find(fa[x]);
}

signed main ()
{
	n = read() , m = read();
	ans = n;
	for ( int i = 1 ; i <= n + 1 ; i ++ ) fa[i] = i;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int l = read() , r = read();
		while(1)
		{
			int fl = find(l);
			if ( fl > r ) break;
			else fa[fl] = fl + 1;
			ans --;
		}
		cout << ans << endl;
	}
	return 0;
}

I. 4.数列询问

又一道神题...

如果发现一组询问l,r,k就说明brbl1kmod p意义下相等

我们用并查集维护这种关系 并查集中每一条边维护该点和父亲的差mod p的结果

对于一组询问 如果l1r不在一个并查集中 那么就将两者放入同一个并查集中

对于查询 如果 sum[l1]+sum[r]k(mod p) 那么显然 这两个数之间的距离是合法的 否则不合法

在加边的时候 我们l1到祖先fl的距离是sum[l1] r到祖先fr的距离是sum[r] 那么我们想要树上l1r的距离mod pk

所以sum[fr]=(ksum[l1]sum[r]+p) mod p

upd on 2023.9.12: 重新看了一下这题 发现这种劣质错误算法居然能过 已修正

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e6 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , m , p , fa[N] , sum[N];

int find ( int x )
{
	if ( fa[x] == x ) return x;
	int fx = find(fa[x]);
	sum[x] = ( sum[fa[x]] + sum[x] ) % p;
	return fa[x] = fx;
}

signed main ()
{
	n = read() , m = read() , p = read();
	for ( int i = 1 ; i <= n ; i ++ ) fa[i] = i;
	for ( int i = 1 ; i <= m ; i ++ )
	{
		int l = read() , r = read() , k = read();
		int fl = find ( l - 1 ) , fr = find(r);
		if ( fl == fr ) 
		{
			if ( ( sum[r] + sum[l-1] + p ) % p != k ) { cout << i - 1 << endl; return 0; }
		}
		else
		{
			fa[fr] = fl;
			sum[fr] = k - sum[r] - sum[l-1];
		}
	}
	cout << m << endl;
	return 0;
}

J. 5.小明反击

和构造完全图有异曲同工之妙

关于排序:如果不排序 会导致出错 因为可能在以前加边的时候在这两个点之间加入了其他小边 不能保证两个点之间的最小边是这个大边

#include <bits/stdc++.h>
using namespace std;
#define inl inline
#define int long long
const int N = 1e6 + 5; 
inl int read ()
{
	int x = 0 , f = 1;
	char ch = getchar();
	while ( !isdigit ( ch ) ) { if ( ch == '-' ) f = -1; ch = getchar (); }
	while ( isdigit ( ch ) ) { x = ( x << 1 ) + ( x << 3 ) + ( ch ^ 48 ); ch = getchar (); }
	return x * f;
}

int n , fa[N] , b[N] , ans , sz[N];

struct node { int u , v , w; } a[N];

int find ( int x ) 
{
	if ( x == fa[x] ) return x;
	else return fa[x] = find(fa[x]);
}

void init()
{
	memset ( a , 0 , sizeof a );
	ans = 0;
}

signed main ()
{
	int T = read();
	while ( T -- )
	{
		init();
		n = read();
		for ( int i = 1 ; i <= n ; i ++ ) sz[i] = 1 , fa[i] = i;
		for ( int i = 1 ; i < n ; i ++ ) a[i].u = read() , a[i].v = read() , a[i].w = read();
		sort ( a + 1 , a + n , [](const node &a , const node &b) { return a.w < b.w; } );
		for ( int i = 1 ; i < n ; i ++ )
		{
			int fu = find(a[i].u) , fv = find(a[i].v);
			ans += sz[fu] * sz[fv] * ( a[i].w + 1 ) - 1;
			fa[fu] = fv;
			sz[fv] += sz[fu];
		}
		cout << ans << endl;
	}	
	return 0;
}
`
posted @   Echo_Long  阅读(250)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示