第10周总结

下面是这周学到的一些并查集的做题方法:

NYOJ 1022 合纵连横
描述
乱世天下,诸侯割据。每个诸侯王都有一片自己的领土。但是不是所有的诸侯王都是安分守己的,实力强大的诸侯国会设法吞并那些实力弱的,让自己的领土面积不断扩大。而实力弱的诸侯王为了不让自己的领土被吞并,他会联合一些其他同样弱小的诸侯国,组成联盟(联盟不止一个),来共同抵抗那些强大的诸侯国。 强大的诸侯国为了瓦解这些联盟,派出了最优秀的间谍来离间他们,使一些诸侯国退出联盟。最开始,每个诸侯国是一个联盟。

有两种操作

1、U x y 表示x和y在同一个联盟。(0≤x,y<n)

2、D x 表示x退出联盟。

输入
多组测试数据
第一行两个数,n和m(1 ≤ n≤ 10^5, 1 ≤ m ≤10^5),分别表示诸侯国的个数和操作次数。
接下来有m行操作

输出
输出联盟的个数

样例输入
5 7
U 0 1
U 1 2
U 0 3
D 0
U 1 4
D 2
U 0 2
10 1
U 0 9

样例输出
Case #1: 2
Case #2: 9

一开始的思路是,将U数据的合并,记下D的个数,然后循环找联盟根节点的种数,最后将根节点的种数减去D的个数,结果为答案。但在测试时不对。因为如果一个国家退出一个联盟的话,他之后是有可能再与其他国家联盟。

之后就想着先存下输入数据,再标记退出的国家,在后面处理U数据时若发现这个国家以后会退出,那么让另国家做根节点,在处理D时再将该国家的根节点指向自己。但在敲代码时想到:万一要合并的两个国家都在后面退出呢?那这个思路就不对了。

最后看了别人的做题思路。开一个2*n+m大小的数组,在初始化数组时,将小于n的数组根节点指向i+n,大于n的数组的根节点指向自己,在联盟时使i+n与i1+n合并,在退出联盟时将i指向2*n之外的数,最后从0开始循环到n找出根节点的种数,也就是答案。

#include<bits/stdc++.h>
#define q 100001
using namespace std;
bool b[2*q];
int x,y;
int set1[2*q],ans;
int find(int a)
{int b=a,t;
while(b!=set1[b])
    b=set1[b];
while(b!=a)
{t=a;
a=set1[a];
set1[t]=b;
}
return b;
}
void merge(int a,int b)
{int fa=find(a);
int fb=find(b);
if(fa!=fb)
set1[fa]=fb;
}
int main()
{std::ios::sync_with_stdio(false);
int n,m,z1;char z;int k=1;
while(cin>>n>>m)
{memset(b,0,sizeof(b));
for(int i1=0;i1<n;i1++)
    set1[i1]=i1+n;
for(int i=n;i<2*n+m;i++)
set1[i]=i;
int ans=0,cnt=2*n;
while(m--)
{cin>>z;
if(z=='U'){
cin>>x>>y;
merge(x,y);
}
else{
cin>>z1;
set1[z1]=cnt++;
}
}
 for(int i1=0;i1<n;i1++)
 {if(!b[find(i1)])
 {b[find(i1)]=1;ans++;
 }
 }
cout<<"Case #"<<k++<<": "<<ans<<endl;
}
}

 还是畅通工程 (hdu.edu.cn)

带权并查集,只要在原来并查集大的基础上开一个结构体额外记下边权,在输入完后根据边权的大小从小到大排序,再按顺序进行合并,在合并时如果这两个村的在一个集合里就跳过(因为边权已经是从小到大排序过了,如果两个村已经在一个集合里,说明这两个村有更短的路),在合并时将相应的边权相加,最后得到的和就是所求的最短路长。

#include<bits/stdc++.h>
#define q 100001
using namespace std;
struct A
{int st,en,ans;
}aa[q];
bool cmp(A a,A b)
{
    return a.ans<b.ans;
}
int set1[q],ans;
bool flag;
int find(int a)
{int b=a,t;
while(b!=set1[b])
    b=set1[b];
while(b!=a)
{t=a;
a=set1[a];
set1[t]=b;
}
return b;
}
void merge(int a,int b)
{int fa=find(a);
int fb=find(b);
if(fa!=fb){
set1[fa]=fb;flag=1;}
}
int main()
{std::ios::sync_with_stdio(false);
int n;
while(cin>>n)
{if(n==0)break;
for(int i1=0;i1<n*(n-1)/2;i1++)
    set1[i1]=i1;
ans=0;
for(int i1=0;i1<n*(n-1)/2;i1++)
    cin>>aa[i1].st>>aa[i1].en>>aa[i1].ans;
sort(aa,aa+n*(n-1)/2,cmp);
for(int i=0;i<n*(n-1)/2;i++)
{flag=0;
merge(aa[i].st,aa[i].en);
if(flag)ans+=aa[i].ans;
}
cout<<ans<<endl;
}
}

find  the most comfortable road (hdu.edu.cn)

这个题要是不知道要用并查集,我肯定会用深搜去做,看了别人的分析后知道该题使用深搜必超时。思路是:根据速度的大小排序,从小到大选择一个最小速度,从选择最小速度的地方开始枚举,记录下枚举过程中的最大速度,在枚举过程中判断要求两点是否联通,一旦联通就更新最小的最大速度差。因为这个题要求最小速度差,不是最短路径,所以可以在只选择部分路线(哪怕要求两点不连通),只要选择的路线使要求的两点联通,就可以更新数据,而该最大速度差是走遍这次枚举的所有路线的最大速度差。

关于最终求出来的为什么是答案,个人理解:在根据速度大小排序后,相邻的路线就是最小的速度差,根据这个在地图上添加路线,就可以保证尽可能使要求两点在联通时速度差较小且所有将速度差小的路线全部枚举,类比于求两点最小的距离(也就是上面那个题,那个题是将给出的村与村距离按从小到大进行排序,最后求出来的一定是最短的),只不过上面那个题是使路线最短,而该题是速度差最小,这个不太好想,也许是我做的少。代码的话和上面那个题差不多,这题就不打了。


The Door Problem - Codeforces

 这个题要是没说用并查集,我肯定是用深搜。在一开始是真不知道这题怎么才能跟并查集扯上关系,看了题解也是琢磨了半天还不是很清楚,个人理解:深搜的话是直接处理中间过程比较结果符不符合,而这个方法是根据结果处理过程,最后再看过程是否矛盾(一个开关的开和关在一个集合里)。思路:存下每个门的状态和被控制的开关(一定是两个,假设是a,b),枚举每个门,如果门的状态是0,那就让a与b,a+m与b+m联通(开关a,b必须同时开或者关,+m表示状态关),否则就让a与b+m,a+m与b联通(开关a,b必须一个开一个关),最后枚举每个开关(范围0到n)的根节点,如果一样就说明矛盾,输出NO,如果枚举完那就没有矛盾,输出YES。

#include<bits/stdc++.h>
#define q 100001
using namespace std;
vector<int>z[q];
int set1[q*2],ans;
int door[q];
int find(int a)
{int b=a,t;
while(b!=set1[b])
    b=set1[b];
while(b!=a)
{t=a;
a=set1[a];
set1[t]=b;
}
return b;
}
void merge(int a,int b)
{int fa=find(a);
int fb=find(b);
if(fa!=fb){
set1[fa]=fb;}
}
int main()
{std::ios::sync_with_stdio(false);
int n,m;
cin>>n>>m;
for(int i1=0;i1<m*2;i1++)
    set1[i1]=i1;
for(int i=0;i<n;i++)
    cin>>door[i];
for(int i=0;i<m;i++)
{int j,j1;cin>>j;
    for(int i1=0;i1<j;i1++)
    cin>>j1;
    z[j1].push_back(i);
}
for(int i=0;i<n;i++)
{int dx=z[i][0];
int dy=z[i][1];
    if(!door[i])
    {merge(dx,dy);
    merge(dx+m,dy+m);
    }
    else{
        merge(dx,dy+m);
        merge(dx+m,dy);
    }
}
bool flag=1;
for(int i=0;i<m;i++)
{if(find(i)==find(i+m))
{flag=0;break;
}
}
if(flag)cout<<"YES"<<endl;
else cout<<"NO"<<endl;
}

看的拓扑排序题太少,就目前看到的题做题思路几乎一样,无非就是输出按要求输出排名,或者输出是否产生环。还有就是拓扑排序是根据入度或者出度来操作。

产生冠军 - HDU 2094 - Virtual Judge (vjudge.net)

使胜利者的入度加一,再拓扑排序,再循环加上所有人的入度,如果为0就输出YES,否则NO。

#include <bits/stdc++.h>
#define q 10001
using namespace std;
vector<int>name[q];
char na1,na2;
int n,ans;
int vis[q];
void toposort()
{
 for(int i=0;i<n;i++)
 {for(int i1=0;i1<n;i1++)
     if(!vis[i1]){
        for(int i2=0;i2<name[i1].size();i2++)
        {vis[name[i1][i2]]--;
        }
     }
 }

}
int main()
{std::ios::sync_with_stdio(false);

while(cin>>n)
{if(n==0)break;
memset(vis,0,sizeof(vis));
for(int i=0;i<n;i++)
{
cin>>na1>>na2;
vis[na1-'a']++;
name[na2-'a'].push_back(na1-'a');
}
toposort();
ans=0;
for(int i=0;i<n;i++)
    ans+=vis[i];
if(ans==0)
    cout<<"Yes"<<endl;
else
    cout<<"No"<<endl;
}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值