并查集
P1955 程序自动分析(并查集 + 离散化)
P1955 [NOI2015] 程序自动分析 - 洛谷
并查集 + 离散化,和算法竞赛那本书的例题一模一样。
思路:先建立并查集,把所有 e == 1(相等)的约束合并起来。因为还需要判断不相等的情况,所以得跑两轮——第二轮遇到 e == 0 且两个数已经在同一集合里,就说明前面的约束和它矛盾,直接 break。
变量范围到 1e9 而约束只有 1e5 条,所以要先离散化,把出现过的数映射到 1..m 的连续编号上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| #include<bits/stdc++.h> using namespace std; using ll=long long; #define endl '\n'
const int maxn=100005;
struct Constraint{ int x,y,e; };
int fa[maxn*2]; vector<int> nums; vector<Constraint> cons;
void init(int n){ for(int i=1;i<=n;i++){ fa[i]=i; } }
int get(int x){ if(fa[x]==x) return x; return fa[x]=get(fa[x]); }
int find_id(int val){ return lower_bound(nums.begin(),nums.end(),val)-nums.begin()+1; }
void merge(int x,int y){ fa[get(x)]=get(y); }
void solve(){ int n; cin>>n; nums.clear(); cons.clear();
for(int i=0;i<n;i++){ int x,y,e; cin>>x>>y>>e; cons.push_back({x,y,e}); nums.push_back(x); nums.push_back(y); }
sort(nums.begin(),nums.end()); nums.erase(unique(nums.begin(),nums.end()),nums.end());
int m=nums.size();
init(m);
for(int i=0;i<n;i++){ if(cons[i].e==1){ int id_x=find_id(cons[i].x); int id_y=find_id(cons[i].y); merge(id_x,id_y); } }
bool is_possi=true; for(int i=0;i<n;i++){ if(cons[i].e==0){ int id_x=find_id(cons[i].x); int id_y=find_id(cons[i].y);
if(get(id_x)==get(id_y)){ is_possi=false; break; } } }
if(is_possi) cout<<"YES"<<endl; else cout<<"NO"<<endl; }
int main(){ ios::sync_with_stdio(false); cin.tie(0); int t; cin>>t; while(t--){ solve(); } return 0; }
|
这道题顺带学到的:
- 去重方法:
sort 之后 nums.erase(unique(nums.begin(), nums.end()), nums.end()),是离散化的标准起手式,之后就能直接用 lower_bound 定位。
- 多组数据要记得清空:
nums.clear(); cons.clear(); 放在 solve() 开头,别靠全局变量残留。注意 fa 数组不需要清空,因为 init(m) 会重新覆盖到 m 位。
- 两轮处理的顺序不能颠倒:必须先合并完所有相等关系,再检查不等关系,否则合并是「边检查边做」的,会漏判。
附:LeetCode 1353 最多可以参加的会议数目
1353. 最多可以参加的会议数目 - 力扣
这道题其实似乎用不到并查集,但是无伤大雅。
贪心 + 优先队列:优先参加结束时间相对早的会议,要不然后面来不及参加了。
怎么找到结束时间最早的会议? 用优先队列。priority_queue 的小根堆写法是笔者之前在做合并果子这道题的时候学的:
1
| priority_queue<int,vector<int>,greater<int>> q;
|
队列里只保存 endDay。按开始时间排序后,用 day 从左往右扫,每天把当天开始的会议全部入队,然后弹掉已经过期的(q.top() < day),再取队首那个结束最早的参加掉——参加一场,ans 加一。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #include<bits/stdc++.h> using namespace std; using ll=long long; #define endl '\n'
int main(){ ios::sync_with_stdio(false); cin.tie(0);
int n; cin>>n;
vector<vector<ll>> events(n); for(int i=0;i<n;i++){ ll l,r; cin>>l>>r; events[i]={l,r}; }
sort(events.begin(),events.end());
priority_queue<int,vector<int>,greater<int>> q; int i=0; int day=1; int ans=0;
while(i<n || !q.empty()){ while(i<n && events[i][0]==day){ q.push(events[i][1]); i++; }
while(!q.empty()&& q.top()<day) q.pop();
if(!q.empty()){ q.pop(); ans++; }
day++; }
cout<<ans<<endl; return 0; }
|
总结
- 并查集的核心是
get 的路径压缩和 merge 的方向,模板本身很短,难点在于看出题目里的「等价关系」。
- 一旦值域大而数据量小,先想离散化;
sort + unique + lower_bound 是固定套路。
- 需要判断「不相等」时,先合并全部相等、再统一检查不等,两轮走。
- 小根堆
priority_queue<int,vector<int>,greater<int>> 是「每次取最小」场景的通用工具,贪心题里出现频率极高。