Codeforces Round #738 (Div. 2) D2题解
D2. Mocha and Diana (Hard Version)
至于D1,由于范围是1000,我们直接枚举所有的边,看看能不能加上去就行,复杂度是\(O(n^2logn)\).至于\(n\)到了\(1e5\),就要重新考虑解法。
考虑到树的边数是\(n-1\)。也就是说我们枚举的大多数边都是无效的,这个时候就要考虑我们能不能只连有效的边,对于两个连通块而言,显然在这两个连通块之间我们只需要连一条边就可以了。还有一般这种两个图的题,一般都是在一个图中操作,在另一个图中进行合不合法的判定。这里我们以第一个图作为操作的对象,以第二个图作为判定的依据。首先想到的就是我们先将两个图都划分连通块,然后枚举第一个图任意两个连通块x,y,考虑两个连通块的点,若存在u属于x,v属于y并且u和v在图二中不属于同一个连通块,那么u和v就可以相连,这两个连通块就可以联通。并且两个连通块不能连的充要条件是这两个连通块内的所有点在图二中都属于同一个连通块内。枚举任意两个连通块显然复杂度是过不去的,我们能不能固定一个连通块(比如说连通块1),然后尝试将其他连通块与它联通,但这样会不会少连一些边?比如连通块x与连通块y都不能与1联通,但这两个能不能联通呢?考虑下刚才提到的充要条件,这两个连通块不能和1联通,说明这两个连通块内的所有点和1的所有点在图二中都属于同一个连通块内。那这两个连通块的点在图二也都属于同一个连通块内啊!肯定不能连啊。好了,那我们固定一号连通块,尝试将其他的所有连通块与它联通。显然我们找点的时候我们肯定不能枚举所有的点来判断,既然要求找到一个点对即可。我们考虑保存下当前一号连通块内的所有点在图二中的连通块编号,以及该编号中的其中一个点(方便输出方案)。当下一个连通块来的时候我们直接枚举这个连通块内的所有点来判断是否有没有符合要求的点对。并且当可以联通时,我们还需要在图二中连边,合并两个连通块,这个可以用并查集实现,但有时这两个连通块(符合要求的点对在图二中的两个连通块)都在我们维护的一号连通块内(图一中),这就需要我们维护的东西支持删除操作,这个我用了set,不想再动脑了。你以为这就结束了吗?不不不....我们发现当一号连通块内对应的在图二中的连通块个数越多,越容易与其它连通块联通,为了避免一些恶心情况(因为当前1号连通块对应的图二中的连通块个数为1,而失败联通,但之后有其他的连通块对应图二有更多的连通块,调整下顺序就能联通原本不能联通的),我们将图一中的连通块按照在图二中对应地连通块的个数排序,个数多着在前,这样我们就先尽可能的增加我们维护的一号图在二号图中的连通块的个数,之后就能尽可能多的接纳其他的连通块。总复杂度\(O(nlogn)\),多少次想放弃,但还是想坚持下去....
查看代码
//不等,不问,不犹豫,不回头. #include
#define _ 0 #define ls p<<1 #define db double #define rs p<<1|1 #define P 1000000007 #define ll long long #define INF 1000000000 #define get(x) x=read() #define PLI pair #define PII pair #define ull unsigned long long #define put(x) printf("%d\n",x) #define putl(x) printf("%lld\n",x) #define rep(x,y,z) for(int x=y;x<=z;++x) #define fep(x,y,z) for(int x=y;x>=z;--x) #define go(x) for(int i=link[x],y=a[i].y;i;y=a[i=a[i].next].y) using namespace std; const int N=1e5+10; int n,m1,m2,f[3][N],vis[N],num,bx[N],by[N],id[N]; struct wy{int len,v;}a[N]; vector v[N]; map mp; set s; inline int read()
{
int x=0,ff=1;
char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') ff=-1;ch=getchar();}
while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*ff;
}inline int getf(int op,int x){return f[op][x]==x?x:f[op][x]=getf(op,f[op][x]);}
inline void init()
{
get(n);get(m1);get(m2);
rep(i,1,n) f[1][i]=f[2][i]=i;
rep(i,1,m1)
{
int get(x),get(y);
int t1=getf(1,x),t2=getf(1,y);
if(t1!=t2) f[1][t1]=t2;
}
rep(i,1,m2)
{
int get(x),get(y);
int t1=getf(2,x),t2=getf(2,y);
if(t1!=t2) f[2][t1]=t2;
}
}inline bool cmp(wy a,wy b) {return a.len>b.len;}
inline void prework()
{
rep(i,1,n) f[1][i]=getf(1,i),f[2][i]=getf(2,i);
rep(i,1,n)
{
if(!vis[f[1][i]])
{
vis[f[1][i]]=1;
id[f[1][i]]=++num;
}
a[id[f[1][i]]].len++;
a[id[f[1][i]]].v=f[1][i];
}
sort(a+1,a+num+1,cmp);
rep(i,1,num) id[a[i].v]=i;
rep(i,1,n) v[id[f[1][i]]].push_back(i);
memset(vis,0,sizeof(vis));
for(auto x:v[1])
{
if(vis[f[2][x]]) continue;
vis[f[2][x]]=1;
s.insert(f[2][x]);
mp[f[2][x]]=x;
}
}inline void solve()
{
int ans=0;
rep(i,2,num)//枚举从第二个连通块开始的所有连通块,尝试与第一个连通块合并。
{
bool flag=false;
for(auto x:s)//枚举每个连通块
{
if(flag) break;
for(auto y:v[i])
{
int t=getf(2,y);
if(t!=x&&!flag)
{
bx[++ans]=mp[x];
by[ans]=y;
f[2][t]=x;
if(s.find(t)!=s.end()) s.erase(t);
flag=true;
}
else if(flag&&!vis[t])
{
vis[t]=1;
s.insert(t);
mp[t]=y;
}
}
}
}
put(ans);
rep(i,1,ans) printf("%d %d\n",bx[i],by[i]);
}int main()
{
// freopen("1.in","r",stdin);
//freopen("sol.out","w",stdout);
init();
prework();
solve();
return (0_0);
}
//以吾之血,铸吾最后的亡魂.
原文地址:https://www.cnblogs.com/gcfer/p/15152888.html
- Python编写渗透工具学习笔记一 | 0x04 nmap实现端口扫描(准确性更高)
- spring-boot & ffmpeg 搭建一个音频转码服务
- java 实现二维码生成工具类
- WriteUp分享 | CTF-web
- 机器学习的Boosting技术(以AdaBoost为例)
- 10.25 systemd管理服务
- JS基础知识之new操作符 | 前端卧谈会第9期
- 利用通用伪造签名绕过ElGamal
- 10.24 chkconfig工具
- SPI框架实现之旅一:背景介绍
- 10.23 linux任务计划cron
- Linux基础(day35)
- SPI框架实现之旅二:整体设计
- 10.22 firewalld关于service的操作
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法