AcWing 237. 程序自动分析
题目链接
典型的并查集题目,利用题目约束的传递性做题。
此题既不需要带权,也不需要拓展域。
注意题目范围,需要先进行离散化处理(并查集的题目一般都需要进行离散化处理)
离散化
void my_read(int n){
int i,j,e;
total = 0;
for(int k=1;k<=n;k++){
scanf("%d%d%d",&i,&j,&e);
query[k] = (Query){i,j,e};
if(e){
a[++total] = i;
a[++total] = j;
}
}
sort(a+1,a+1+total);
total = unique(a+1,a+1+total) - a - 1;
// cout << "total=" << total << endl;
// for(int k=1;k<=total;k++) cout << a[k] << " ";
// cout << "\n";
}
思路:先处理所有具有传递性的等于条件,再依次判断不等于的条件是否成立。
#include <bits/stdc++.h>
using namespace std;
const int N = 100010;
struct Query{
int i;
int j;
int e;
}query[N];
int fa[2*N];
int a[2*N];
int total;
int find(int x){
if(x==fa[x]) return x;
return fa[x] = find(fa[x]);
}
void my_read(int n){
int i,j,e;
total = 0;
for(int k=1;k<=n;k++){
scanf("%d%d%d",&i,&j,&e);
query[k] = (Query){i,j,e};
if(e){
a[++total] = i;
a[++total] = j;
}
}
sort(a+1,a+1+total);
total = unique(a+1,a+1+total) - a - 1;
}
void solve(){
int n;
cin >> n;
my_read(n);
bool f = 1;
for(int i=1;i<=total;i++) fa[i] = i;
for(int k=1;k<=n;k++){
int i = query[k].i,j = query[k].j,e = query[k].e;
if(!e) continue;
int x = lower_bound(a+1,a+total+1,i) - a;
int y = lower_bound(a+1,a+total+1,j) - a;
if(i==j){
continue;
}
if(e==1){
int rx = find(x);
int ry = find(y);
fa[rx] = ry;
}
}
for(int k=1;k<=n;k++){
int i = query[k].i,j = query[k].j,e = query[k].e;
if(e) continue;
int x = lower_bound(a+1,a+total+1,i) - a;
int y = lower_bound(a+1,a+total+1,j) - a;
if(i==j){
f = 0;
break;
}
if(a[x]!=i || a[y]!=j) continue;
int rx = find(x);
int ry = find(y);
if(rx==ry){
f = 0;
break;
}
}
if(f) puts("YES");
else puts("NO");
}
int main()
{
int t;
cin >> t;
while(t--){
solve();
}
return 0;
}
AcWing 238. 银河英雄传说
题目链接
典型带权并查集,记录每个结点到根结点的距离。
注意find函数的写法,很容易写错,理解了之后基本上就固定模式了。
/*d数组是边权*/
int find(int x){
if(fa[x]==x) return x;
int f = find(fa[x]);
d[x] += d[fa[x]];
return fa[x] = f;
}
完整代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 300005;
int fa[N];
int sz[N];
int d[N];
int find(int x){
if(fa[x]==x) return x;
int f = find(fa[x]);
d[x] += d[fa[x]];
return fa[x] = f;
}
void merge(int x,int y){
int rx = find(x),ry = find(y);
if(rx==ry) return;
fa[rx] = ry;
d[rx] += sz[ry]; //d数组表示到树根的距离.
sz[ry] += sz[rx];
}
bool same(int x,int y){
return find(x)==find(y);
}
int main()
{
for(int i=1;i<N;i++) fa[i] = i,sz[i] = 1;
memset(d,0,sizeof d);
int t;
cin >> t;
char s[5];
int a,b;
while(t--){
scanf("%s%d%d",s,&a,&b);
if(*s=='M'){
merge(a,b);
}else{
if(a==b){
puts("0");
continue;
}
if(same(a,b)){
printf("%d\n",abs(d[a]-d[b])-1);
}else{
puts("-1");
}
}
}
return 0;
}
AcWing 239. 奇偶游戏
题目链接
分析:
记sum[i]为前i个数的1的个数(也就是前缀和)。
若[l~r]为奇数:则sum[r] 和 sum[l-1]奇偶性不相同。
若[l~r]为偶数:则sum[r] 和 sum[l-1]奇偶性相同。
因此需要看sum[l-1] 与 sun[r] 是否产生矛盾。
有两种方法:
- 边带权并查集:记录每个点与父结点的奇偶性关系,相同为0,不同为1。(仔细想想,这点正是异或运算律)因此可以借助异或运算来判断同一集合中不同的两点的奇偶性。在进行路径压缩的时候,根据异或运算,可以得出与根节点的奇偶性关系。
若p和q在同一集合中,则check d[x] ^d[y];若不在,则加入同一集合, d[p] = d[x] ^ d[y]^ query[i].d; - 拓展域并查集:把每个结点x 分为x_odd与x_even。这个思路相对简单,代码量较大。
解1:边带权并查集:
#include <bits/stdc++.h>
#include <functional>
#define pi acos(1.0)
#define fi first
#define se second
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int N = 10005,M = 2*N;
const int MOD = 1e9+7;
struct Query{
int l,r;
int d; // 0:偶 1:奇
}query[N];
int fa[N];
int a[N];
int total;
int d[N];
int find(int x){
if(x==fa[x]) return x;
int f = find(fa[x]);
d[x] ^= d[fa[x]];
return fa[x] = f;
}
void myread(int n){
total = 0;
int l,r;
string s;
int d;
for(int i=1;i<=n;i++){
cin >> l >> r >> s;
if(s=="even") d = 0;
else d = 1;
query[i] = (Query){l,r,d};
a[++total] = l-1;
a[++total] = r;
}
sort(a+1,a+1+total);
total = unique(a+1,a+1+total) - a - 1;
}
void solve(){
int n,m;
cin >> m >> n;
myread(n);
for(int i=1;i<=total;i++) fa[i] = i;
int ans = n;
for(int i=1;i<=n;i++){
int x = lower_bound(a+1,a+1+total,query[i].l-1) - a;
int y = lower_bound(a+1,a+1+total,query[i].r) - a;
int p = find(x);
int q = find(y);
if(p!=q){
fa[p] = q;
d[p] = d[x]^d[y]^query[i].d;
}else{
// check
if(query[i].d != (d[x]^d[y])){
ans = i-1;
break;
}
}
}
printf("%d\n",ans);
}
int main()
{
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int T = 1;
// cin >> T;
while(T--){
solve();
}
return 0;
}
解法2:拓展域并查集
#include <bits/stdc++.h>
#include <functional>
#define pi acos(1.0)
#define fi first
#define se second
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
typedef long long ll;
const int N = 10005,M = 2*N;
const int MOD = 1e9+7;
int a[2*N],fa[2*N];
struct Query{
int l,r,d;
}query[N];
int total = 0;
int find(int x){
if(fa[x]==x) return x;
return fa[x] = find(fa[x]);
}
void myread(int m){
string s;
int l,r;
int d;
for(int i=1;i<=m;i++){
cin >> l >> r >> s;
if(s=="even") d = 0;
else d = 1;
query[i] = (Query){l-1,r,d};
a[++total] = l-1;
// a[++total] = l-1+m;
a[++total] = r;
// a[++total] = r+m;
}
sort(a+1,a+1+total);
total = unique(a+1,a+1+total) - a - 1;
}
void solve(){
int n,m;
cin >> n >> m;
myread(m);
for(int i=1;i<=2*total;i++) fa[i] = i;
int ans = m;
for(int i=1;i<=m;i++){
int l = query[i].l,r = query[i].r,d = query[i].d;
int x_odd = lower_bound(a+1,a+1+total,l) - a;
// int x_even = lower_bound(a+1,a+1+total,l+total) - a;
int y_odd = lower_bound(a+1,a+1+total,r) - a;
// int y_even = lower_bound(a+1,a+1+total,r+total) - a;
int x_even = x_odd + total;
int y_even = y_odd + total;
int fa_x_odd = find(x_odd),fa_x_even = find(x_even),fa_y_odd = find(y_odd),fa_y_even = find(y_even);
if(d){
if(fa_x_odd==fa_y_odd){
ans = i-1;
break;
}
fa[fa_x_odd] = fa_y_even;
fa[fa_x_even] = fa_y_odd;
}else{
if(fa_x_odd==fa_y_even){
ans = i-1;
break;
}
fa[fa_x_odd] = fa_y_odd;
fa[fa_x_even] = fa_y_even;
}
}
cout << ans << endl;
return;
}
signed main()
{ std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int T = 1;
while(T--){
solve();
}
return 0;
}
AcWing 240. 食物链
题目链接
很经典的一道题,两种并查集都可做,注意使用拓展域的并查集时一定要考虑全面,所有域的信息都有用。
// 拓展域
/*
1.同类关系 x_1 = x
2.捕食关系 x_2 = x + n
3.天敌关系 x_3 = x + n + n 目前来看:第三种关系貌似不需要
*/
#include <bits/stdc++.h>
using namespace std;
const int N = 50005;
int a[3*N],fa[3*N];
int find(int x){
if(x==fa[x]) return x;
return fa[x] = find(fa[x]);
}
void solve(){
int n,k;
cin >> n >> k;
for(int i=1;i<=3*n;i++) fa[i] = i;
int ans = 0;
for(int i=1;i<=k;i++){
int d,x,y;
cin >> d >> x >> y;
if(x>n || y>n){
ans++;
continue;
}else if(d==2 && x==y){
ans++;
continue;
}
int x_1 = x,x_2 = x + n,x_3 = x + n + n;
int y_1 = y,y_2 = y + n,y_3 = y + n + n;
if(d==1){
if(find(x_1)==find(y_2) || find(x_2)==find(y_1)){
ans++;
continue;
}
fa[find(x_1)] = find(y_1);
fa[find(x_2)] = find(y_2);
fa[find(x_3)] = find(y_3);
}else{
if(find(x_1)==find(y_1) || find(y_2)==find(x_1)){
ans++;
continue;
}
fa[find(x_2)] = find(y_1); //在x的捕食域中加入y的同类域
fa[find(y_2)] = find(x_3); //在y的捕食域中加入x的天敌域
fa[find(y_3)] = find(x_1); //在y的天敌域中加入x的同类域
}
}
cout << ans << endl;
return;
}
int main()
{
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);
int T = 1;
// cin >> T;
while(T--){
solve();
}
return 0;
}