这篇文章的所有题目均来自于自行整理,代码均来自于自行梳理调试(思路可能比较暴力)。初衷在于整理练习思路,且起到督促自己学习的作用
本文分成将三个模块
1.普及组 (洛谷黄题)
2.提高组 (洛谷绿题)
3.省选组 (洛谷蓝题及以上)
一、普及
1.P3367 【模板】并查集
普通并查集(纯板子,不加解释)
传送门https://www.luogu.com.cn/problem/P3367
// Problem:
// P3367 【模板】并查集
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3367
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
using namespace std;
const int N=1e4+10;
int e[N];
int find(int x){
if(x!=e[x]) e[x]=find(e[x]);
return e[x];//返回根节点
}
void merge(int a,int b){
int x=find(a);int y=find(b);
//e[x]=y;连接到另外一个上,我看了看板子更加复杂,但是符合原理都能过
这里前面加一个if(x!=y)会更好,若不然可能增加路径压缩的次数
}
int main(){
int n,m;cin>>n>>m;
for(int i=1;i<=n;++i) e[i]=i;
while(m--){
int a,b,c;cin>>a>>b>>c;
if(a==1){
merge(b,c);
}
else{
int k1=find(b);int k2=find(c);
if(k1==k2) cout<<"Y"<<endl;
else cout<<"N"<<endl;
}
}
return 0;
}
2.P1551 亲戚
传送门https://www.luogu.com.cn/problem/P1551
一道算是应用场景的题目,可以拿来熟熟手
// Problem:
// P1551 亲戚
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1551
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
using namespace std;
const int N=5005;
int fa[N];
int find(int x){
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];//返回根节点
}
void merge(int a,int b){
int x=find(a);int y=find(b);
fa[x]=y;
}
int main(){
int n,m,p;cin>>n>>m>>p;
for(int i=1;i<=n;++i) fa[i]=i;
while(m--){
int a,b;cin>>a>>b;
merge(a,b);
}
while(p--){
int a,b;cin>>a>>b;
int x=find(a),y=find(b);
if(x==y) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}
3.P1111 修复公路
传送门https://www.luogu.com.cn/problem/P1111
五花八门的答案,结合了一点排序知识
本人用了结构体排序后全遍历,O(nm)的时间复杂度(1e8)擦边过了
// Problem:
// P1111 修复公路
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1111
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
#include<algorithm>
using namespace std;
const int N=1e3+10;
int fa[N];
struct node{
int a,b,c;
bool operator < (const node &p)const {return c<p.c;};//重载
}nodes[100005];
int find(int x){
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void merge(int x,int y){
int a=find(x),b=find(y);
if(a!=b) fa[a]=b;//还是这么写吧,时间更优一点
}
int main(){
int n,m;cin>>n>>m;
for(int i=1;i<=n;++i) fa[i]=i;
for(int i=1;i<=m;++i){
cin>>nodes[i].a>>nodes[i].b>>nodes[i].c;
}
sort(nodes+1,nodes+1+m);
for(int i=1;i<=m;++i){
merge(nodes[i].a,nodes[i].b);
bool check=true;
for(int i=2;i<=n;++i){
if(find(i)!=find(i-1)){
//这里注意合并完没有merge的fa是没有直接更新的,不能直接比较fa[i]
check=false;break;
}
}
if(check){
cout<<nodes[i].c<<endl;return 0;
}
}
cout<<-1<<endl;
return 0;
}
这个地方有更优的方法O(m),遍历一遍,如果遇到根节点不同,连通块数量减一并合并,这样连通块数量等于1的时候,输出答案就好了(只需要操作n-1次)
4.P2814 家谱
传送门https://www.luogu.com.cn/problem/P2814
这道题我个人结合了map,调到吐血,越改越复杂,细节还是蛮多的(不熟练)
#include<iostream>
#include<map>
using namespace std;
const int N=5e4+10;
int fa[N];
map<string,int> mp;//记录每个点的标记
map<int,string> mpp;//在最后返回根节点的字符串
int cnt;
int find(int x){
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void merge(int f,int a){
int x=find(f),y=find(a);
if(x!=y) fa[y]=x;
}
int main(){
string s;
int now;
while(cin>>s){
string k=s.substr(1);
if(s=="$") break;
else if(s[0]=='#'){
if(!mp[k]){
mp[k]=++cnt;
fa[cnt]=cnt;
now=cnt;
mpp[cnt]=k;
}
else now=mp[k];
}
else if(s[0]=='+'){
if(!mp[k]){
mp[k]=++cnt;
fa[cnt]=cnt;
mpp[cnt]=k;
}
merge(now,mp[k]);
}
else{
int t=find(mp[k]);
//这个地方WA了n发,因为最后步find,这里还保留的是上一次的father,要find一次才能更新
cout<<k<<' '<<mpp[t]<<endl;
}
}
return 0;
}
除了我的代码外,这里奉上大佬的一个题解,一样的思路,但是代码简单,炉火纯青。
(我被板子局限了思路,确实没想到路径压缩可以直接压缩map)
5.P1536 村村通
传送门https://www.luogu.com.cn/problem/P1536
这道题如果把前面的吃透了,想起本文第三题结尾大佬的一个办法,计算连通块数量,如果find查找的根节点不同,就连通块数量-1并合并,连通块开始初始化为n-1。
// Problem:
// P1536 村村通
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P1536
// Memory Limit: 125 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
using namespace std;
const int N=1005;
int fa[N];
int find(int x){
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void merge(int a,int b){
int x=find(a),y=find(b);
if(x!=y) fa[x]=y;
}
int main(){
int a,b;
while(cin>>a){
if(a==0) break;
for(int i=1;i<=a;++i) fa[i]=i;
cin>>b;
int ans=a-1;
while(b--){
int x,y;cin>>x>>y;
if(find(x)!=find(y)) ans--,merge(x,y);
}
if(ans>=0) cout<<ans<<endl;
else cout<<0<<endl;
}
return 0;
}
这种方法的初衷其实更适合实时在某一刻卡断一类的,就是说能知道各时刻连通块的数量,这道题其实暴力就可以解决掉(因为给的全部都要操作)
6.P3958 [NOIP2017 提高组] 奶酪
传送门https://www.luogu.com.cn/problem/P3958非常伤心,这道题卡了我两个钟,最终发现原因(h的范围是1e9,不能直接暴力,写假了)
其实就是一段算成一个点,合并集合,最后把上下两个面再维护一下,看看上下两个面在不在一个集合里,可以配合这篇:远古大神 食用~
// Problem:
// P3958 [NOIP2017 提高组] 奶酪
//
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3958
// Memory Limit: 250 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
const int N=1003;
#define int long long
int fa[N];
int n,h,r;
struct node{
int a,b,c;
}nodes[N];
bool check(node x,node y){
int num=(x.a-y.a)*(x.a-y.a)+(x.b-y.b)*(x.b-y.b)+(x.c-y.c)*(x.c-y.c);
if(num>(2*r*r*2)) return false;
return true;
}
int find(int x){
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void merge(int a,int b){
int x=find(a),y=find(b);
if(x!=y) fa[y]=x;//高的往低处接
}
signed main(){
int t;cin>>t;
while(t--){
cin>>n>>h>>r;
for(int i=1;i<=n+2;++i) fa[i]=i;
for(int i=1;i<=n;++i){
cin>>nodes[i].a>>nodes[i].b>>nodes[i].c;
}
for(int i=1;i<=n;++i){
for(int j=i+1;j<=n;++j){
if(check(nodes[i],nodes[j])){
merge(i,j);
}
}}
for(int i=1;i<=n;++i){
if(nodes[i].c-r<=0) merge(i,n+1);
if(nodes[i].c+r>=h) merge(i,n+2);//看看能不能串起来
}
if(find(n+1)==find(n+2)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
return 0;
}