IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> CF662B Graph Coloring题解--zhengjun -> 正文阅读

[数据结构与算法]CF662B Graph Coloring题解--zhengjun

题目传送门

题目大意

给你一张无向图,图中每条边是蓝色或者红色的,让你每次选一个点,就会把与这个点相连的边的颜色反转(红变蓝,蓝变红),求最少步数的方案使得最后所有边的颜色都一样。

思路

好像没有 2 ? s a t 2-sat 2?sat 的题解,那我就来一发。

首先分类讨论:要么都变成红色,要么都变成蓝色。

如果一条边的颜色与当前我们期望的颜色相同,那么要么两个点都选,要么都不选,所以如果一个点不选,那么另一个点也不选,一个点选了,另一个点也要选,建图就行了,如下图。

A_zjzj

那么同理,如果一条边的颜色与期望的颜色不同,所以,如果一个点选,另一个点就不能选,如果一个点不选,那么另一个点就必须能选,图的话,略吧

这样我们就理出来一个 2 ? s a t 2-sat 2?sat 的模型。

判断无解的情况,这就是 2 ? s a t 2-sat 2?sat 的经典操作,判断 u , u ′ u,u' u,u 是否处于一个强联通分量即可。

对于有解的情况,还要输出解,那么由于这个图是上下对称的,所以也就是说,如果你选了 i 1 , i 2 … , i m , j 1 ′ , j 2 ′ … , j m ′ ′ i_1,i_2\dots,i_m,j_1',j_2'\dots,j_{m'}' i1?,i2?,im?,j1?,j2?,jm? 这个强联通分量,那么你也一定可以不选它,选 i 1 ′ , i 2 ′ … , i m ′ , j 1 , j 2 … , j m ′ i_1',i_2'\dots,i_m',j_1,j_2\dots,j_{m'} i1?,i2?,im?,j1?,j2?,jm? 这个强联通分量,那么为了让选的个数尽可能的少,所以看看这两个东西中 u ′ u' u 个数谁多谁少,加到答案里面,然后再把选的这些点放到最后的方案里面就行了。

详见代码。

不知道为什么其他题解说这题细节多,可能我的方法没啥细节。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
void read(){}//学起来吧,可以这么用:read(a,b,c,……,n);
template<typename _Tp,typename... _Tps>
void read(_Tp &x,_Tps &...Ar){
	x=0;char c=getchar();bool flag=0;
	while(c<'0'||c>'9')flag|=(c=='-'),c=getchar();
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
	if(flag)x=-x;
	read(Ar...);
}
const int N=1e5+10;
int n,m;
struct zj{
	int u,v,w;
}a[N];
struct edges{
	int to,nex;
}edge[N*4];//注意对于每一条边,要建两条双向边,所以大小要开 4 倍
int head[N*2],kk;
void addedge(int u,int v){//链式前向星基本操作,直接建双向边了
	edge[++kk]=(edges){v,head[u]};head[u]=kk;
	edge[++kk]=(edges){u,head[v]};head[v]=kk;
}
int dfn[N*2],low[N*2],s[N*2],top,cnt,scc[N*2],scct;//点数别忘了开 2 倍
void tarjan(int u){//tarjan 缩点模板
	dfn[u]=low[u]=++cnt;s[++top]=u;
	for(int i=head[u];i;i=edge[i].nex){
		int v=edge[i].to;
		if(!dfn[v])tarjan(v),low[u]=min(low[u],low[v]);
		else if(!scc[v])low[u]=min(low[u],low[v]);
	}
	if(dfn[u]==low[u]){
		++scct;
		while(s[top]!=u)scc[s[top--]]=scct;
		scc[s[top--]]=scct;
	}
}
int cnt1,cnt2,k1[N],k2[N],s1[N],s2[N];//记录 dfs 中到达的 u 和 u' 的数量和编号
bool vis[N];
void dfs(int u){
	if(vis[(u-1)%n+1])return;//1,2,……,n 不变, n+1,n+2,……,n+n 变成 1,2,……,n
	vis[(u-1)%n+1]=1;u<=n?(k1[++cnt1]=u):(k2[++cnt2]=u-n);
	for(int i=head[u];i;i=edge[i].nex){
		int v=edge[i].to;
		dfs(v);
	}
}
int get(){
	int i;char c[5];bool flag1,flag2;int ans1=0,ans2=0;
	for(read(n,m),i=1;i<=m;i++)read(a[i].u,a[i].v),scanf("%s",c),a[i].w=(c[0]=='R');//适量压行可以增强代码可读性(
	for(i=1;i<=m;i++)a[i].w?(addedge(a[i].u,a[i].v),addedge(a[i].u+n,a[i].v+n)):(addedge(a[i].u,a[i].v+n),addedge(a[i].u+n,a[i].v));//就是我上面阐述的内容
	for(i=1;i<=n*2;i++)if(!dfn[i])tarjan(i);
	for(flag1=1,i=1;i<=n;i++)if(scc[i]==scc[i+n])flag1=0;
	if(flag1)for(i=1;i<=n;i++){
		if(vis[i])continue;
		cnt1=cnt2=0;
		dfs(i);
		if(cnt1<cnt2){//看看 u,u' 哪个少就选哪个
			ans1+=cnt1;
			while(cnt1)s1[++s1[0]]=k1[cnt1--];
		}
		else{
			ans1+=cnt2;
			while(cnt2)s1[++s1[0]]=k2[cnt2--];
		}
		//此处不能为了节省代码写成这样:
		//if(cnt1>cnt2)swap(k1,k2),swap(cnt1,cnt2);
		//ans1+=cnt1;
		//while(cnt1)s1[++s1[0]]=k1[cnt1--];
		//这样就会导致复杂度变成 O(n^2),直接 T 飞
	}
	//-----------------这些是全部变成蓝色的情况-----------------
	memset(head,0,sizeof(head));kk=0;
	memset(dfn,0,sizeof(dfn));cnt=0;
	memset(low,0,sizeof(low));
	memset(scc,0,sizeof(low));scct=0;
	memset(s,0,sizeof(s));top=0;
	memset(vis,0,sizeof(vis));
	//-----------------一定要注意清空-----------------
	for(i=1;i<=m;i++)a[i].w?(addedge(a[i].u+n,a[i].v),addedge(a[i].u,a[i].v+n)):(addedge(a[i].u,a[i].v),addedge(a[i].u+n,a[i].v+n));
	for(i=1;i<=n*2;i++)if(!dfn[i])tarjan(i);
	for(flag2=1,i=1;i<=n;i++)if(scc[i]==scc[i+n])flag2=0;
	if(flag2)for(i=1;i<=n;i++){
		cnt1=cnt2=0;
		dfs(i);
		if(cnt1<cnt2){
			ans2+=cnt1;
			while(cnt1)s2[++s2[0]]=k1[cnt1--];
		}
		else{
			ans2+=cnt2;
			while(cnt2)s2[++s2[0]]=k2[cnt2--];
		}
	}
	//-----------------这些是全部变成红色的情况-----------------
	if(!flag1&&!flag2)return printf("-1"),0;//两种情况都无解那就无解了
	if(!flag1||(flag2&&s1[0]>s2[0]))swap(s1,s2);//如果第一种情况无解或者第一种情况没有第二种情况优,那就选第二种
	for(printf("%d\n",s1[0]),i=1;i<=s1[0];i++)printf("%d ",s1[i]);
	return 0;//完美撒花
}
int main(){
	return get();//主函数一行,主要可以方便多组测试数据或者开关 freopen,要不然还要把滚轮划上去注释掉,太麻烦了!
}

温馨提醒

如果有什么笔误之类的,评论或私聊我,希望能帮到大家

谢谢–zhengjun

  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2021-07-25 11:55:27  更:2021-07-25 11:57:41 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/28 0:19:55-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码