前言:
并查集与最小生成树的训练。
正文:
链接:并查集与最小生成树 - Virtual Judge (vjudge.net)
题目:
A - 关押罪犯:
#include <bits/stdc++.h>
using namespace std;
const int N=200005;
int fa[N],d[N];
typedef struct stu{
int a;
int b;
int c;
}node;
node q[N];
int find(int x){
if(fa[x]==x)return x;
return fa[x]=find(fa[x]);
}
void merge(int x,int y){
x=find(x);y=find(y);
fa[x]=y;
}
bool cmp(node x,node y){
return x.c>y.c;
}
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>>q[i].a>>q[i].b>>q[i].c;
}
sort(q+1,q+m+1,cmp);
for(int i=1;i<=m+1;i++){
if(find(q[i].a)==find(q[i].b)){
cout<<q[i].c;
break;
}
else{
if(!d[q[i].a])d[q[i].a]=q[i].b;
else merge(d[q[i].a],q[i].b);
if(!d[q[i].b])d[q[i].b]=q[i].a;
else merge(d[q[i].b],q[i].a);
}
}
return 0;
}
我们先将所有囚犯之间的矛盾按怨气大小排序,怨气最大的那两个人一定是优先放在俩个地方,我们枚举排序过的囚犯组合,先判断他们是否再同一并查集内,若是则直接输出答案,不是就分别判断他俩目前是否有敌人,若无则将敌人记录下来,若有则将该囚犯的两个敌人合并至一个并查集内(敌人的敌人就是朋友),不断进行以上操作最终可以得到答案。
B - Shichikuji and Power Grid:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
typedef pair<int,int> PII;
struct node{
int x,y,c,k;
}a[2005];
struct nod{
int fa,ne,w;
}p[4000040];
int cnt = 0;
int sum = 0;
vector<int> ans;
vector<PII> res;
int f[2005];
int find(int x){
if(f[x] == x)
return x;
return f[x] = find(f[x]);
}
bool cmp(nod a,nod b){
return a.w < b.w;
}
void solve(){
int n;
cin >> n;
for(int i = 1;i <= n;i++){
cin >> a[i].x >> a[i].y;
}
for(int i = 1;i <= n;i++)
f[i] = i;
for(int i = 1;i <= n;i++){
cin >> a[i].c;
p[++cnt].fa = 0;
p[cnt].ne = i;
p[cnt].w = a[i].c;
}
for(int i = 1;i <= n;i++){
cin >> a[i].k;
}
for(int i = 1;i <= n;i++){
for(int j = i + 1;j <= n;j++){
p[++cnt].fa = i;
p[cnt].ne = j;
p[cnt].w = (abs(a[i].x - a[j].x) + abs(a[i].y - a[j].y))*(a[i].k + a[j].k);
}
}
sort(p + 1,p +1 + cnt,cmp);
int s = 0;
for(int i = 1;i <= cnt;i++){
int x = find(p[i].fa);
int y = find(p[i].ne);
if(x == y){
continue;
}
if(p[i].fa == 0){
ans.push_back(p[i].ne);
}
else{
res.push_back({p[i].fa,p[i].ne});
}
f[x] = y;
sum += p[i].w;
s++;
if(s == n)
break;
}
cout << sum <<"\n";
cout << ans.size() <<"\n";
for(auto i:ans)
cout <<i << ' ';
cout <<"\n";
cout << res.size() <<"\n";
for(auto t:res){
cout <<t.first <<" "<<t.second <<"\n";
}
}
signed main(){
int t = 1;
while(t--)
{
solve();
}
return 0;
}
超级源点问题。
C - Power Tree:(待补)
D - 食物链:
#include <bits/stdc++.h>
using namespace std;
const int N = 50010;
int n, m;
int p[N], d[N];
int find(int x){
if (p[x] != x){
int t = find(p[x]);
d[x] += d[p[x]];
p[x] = t;
}
return p[x];
}
int main(){
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ ) p[i] = i;
int res = 0;
while (m -- ){
int t, x, y;
scanf("%d%d%d", &t, &x, &y);
if (x > n || y > n) res ++ ;
else{
int px = find(x), py = find(y);
if (t == 1){
if (px == py && (d[x] - d[y]) % 3) res ++ ;
else if (px != py){
p[px] = py;
d[px] = d[y] - d[x];
}
}
else{
if (px == py && (d[x] - d[y] - 1) % 3) res ++ ;
else if (px != py){
p[px] = py;
d[px] = d[y] + 1 - d[x];
}
}
}
}
printf("%d\n", res);
return 0;
}
非常经典的带权并查集题目了,我们可以开一个数组d,d[i]表示i这个点到根节点的距离,我们我们将同类的点距离设为0,捕食关系距离设为1,被捕食关系距离设为2,我们就可以据此通过两个点之间的距离%3来判断他们之间的关系。
E - How Many Answers Are Wrong:(待补)
F - 银河英雄传说:
#include <bits/stdc++.h>
using namespace std;
const int N=300005;
int fa[N],d[N],a[N];
int find(int x){
if(fa[x]!=x){
int u=fa[x];
fa[x]=find(fa[x]);
d[x]+=d[u];
a[x]=a[fa[x]];
}
return fa[x];
}
int main(){
int n;
cin>>n;
for(int i=1;i<=300000;i++){
fa[i]=i;d[i]=0;a[i]=1;
}
for(int i=1;i<=n;i++){
char s;
int x,y,fx,fy;
cin>>s>>x>>y;
if(s=='M'){
fx=find(x);fy=find(y);
fa[fx]=fy;
d[fx]+=a[fy];
a[fx]+=a[fy];a[fy]=a[fx];
}
if(s=='C'){
fx=find(x);fy=find(y);
if(fx!=fy){
cout<<-1<<endl;
continue;
}
else{
cout<<abs(d[x]-d[y])-1<<endl;
}
}
}
return 0;
}
这题和上题类似,不过多了一个a数组,主要作用是维护连通块的大小以正确表达出该点到根节点的距离。
G - News Distribution:
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0);
using namespace std;
const int maxn=1e6+5;
int n,m,f[maxn],num[maxn];
int find_x(int x) {
if(f[x]!=x)return f[x]=find_x(f[x]);
else return f[x];
}
void unite(int x,int y) {
int aa=find_x(x);
int bb=find_x(y);
if(aa!=bb)f[aa]=bb;
}
int main() {
IOS;
cin>>n>>m;
for(int i=0; i<=n; i++)f[i]=i;
while(m--){
int k,a,b;
cin>>k;
if(k!=0)cin>>a;
for(int i=1; i<k; i++) {
cin>>b;
unite(a,b);
}
}
for(int i=1; i<=n; i++)num[find_x(i)]++;
cout<<num[find_x(1)];
for(int i=2; i<=n; i++)cout<<" "<<num[find_x(i)];
cout<<endl;
}
题目可理解为输出1 - n每个号码所在团体总人数,利用并查集不断向集合添加成员,记录每个集合的人数,保存在根节点的sum[]中,查询每个节点的根节点,输出根节点sum
H - Phase Shift(待补):
I - 星球大战:
#include <bits/stdc++.h>
using namespace std;
const int N = 4 * 1e5 + 10; // 全局变量;
int res; // 储存答案;
int n, m, k;
// n 星球数目, m 以太隧道数目, k 遭受攻击的星球数目
int fa[N];
// 并查集
int h[N], s[N], e[N], ne[N], idx;
// 存图, e[] 存边到达的终点, s 存边的起点
bool st[N];
void add(int a, int b)
{
e[idx] = b, s[idx] = a, ne[idx] = h[a], h[a] = idx ++;
}
int find(int x) // 并查集, 找祖先 (路径压缩)
{
if (x == fa[x]) return x;
return fa[x] = find(fa[x]);
}
int main()
{
memset(h, -1, sizeof h); // 初始化表头
memset(st, true, sizeof st);// 初始时没有点被摧毁
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i ++ ) fa[i] = i;
for (int i = 1; i <= m; i ++ )
{
int x, y;
scanf("%d %d", &x, &y);
add(x, y), add(y, x); // 读入无向边
}
scanf("%d", &k); // 读入遭受攻击的星球数目
stack<int> pk, ans;
// 分别储存 pk 被攻击的星球, ans 输出答案
for (int i = 1; i <= k; i ++ )
{
int q;
scanf("%d", &q);
st[q] = false, pk.push(q);
// 标记为摧毁, 算法核心是将摧毁转换成 修建 , 所以逆时间顺序压入栈中
}
m = 2 * m, res = n - k;
// 无向边边数 * 2, res 储存连通块数量
for (int i = 1; i <= m; i ++ ) // 先找所有完好的边构成的连通块数量
{
int S = find(s[i]), E = find(e[i]);
// 先找到这条边的入点和出点
if(st[e[i]] && st[s[i]] && S != E) // 如果这两条边都没有被摧毁, 且不在同一个连通块中
{
res --; // 连通块的数量就可以减一了
fa[E] = S; // 加入同一个连通块
}
}
ans.push(res); // 逆时间的初始答案, k 个点都被摧毁 或 理解为没有新增的点
while(pk.size()) // 只要不为空
{
int tot = pk.top(); // 取出栈顶
pk.pop();
res ++; // 多了一个星球,连通块数量加一
st[tot] = true; // 当前星球已被修建
for (int i = h[tot]; i != -1; i = ne[i]) // 遍历这个点的所有出边
{
int j = e[i]; // 边的终点
int S = find(tot), E = find(e[i]); // 找到起点和终点的祖先
if (st[j] && S != E) // 终点没有被摧毁, 不是一个祖先也就是不在一个连通块中
{
res --; // 连通块数量减一
fa[E] = S; // 加入一个连通块
}
}
ans.push(res); // 压入当前修建好这个星球的答案
}
while (ans.size()) // 逆输出答案
{
res = ans.top();
ans.pop();
printf("%d\n", res);
}
return 0;
}
逆向过程建图。
J - 货车运输:
#include<bits/stdc++.h>
const int MAXN =10005 ;
const long long INF =999999999;
using namespace std;
struct Edge1{
int x,y,dis;
}edge1[50005];
struct Edge2{
int to,next,w;
}edge2[100005];
int cnt,n,m,head[MAXN],deep[MAXN],f[MAXN],fa[MAXN][21],w[MAXN][21];
bool vis[MAXN];
void addedge(int from, int to, int w){
edge2[++cnt].next=head[from];
edge2[cnt].to=to;
edge2[cnt].w=w;
head[from]=cnt;
return ;
}
bool CMP(Edge1 x, Edge1 y){
return x.dis>y.dis;
}
int find(int x){
if(f[x]!=x) f[x]=find(f[x]);
return f[x];
}
void kruskal(){
sort(edge1+1, edge1+m+1, CMP);
for(int i=1; i<=n; i++)
f[i]=i;
for(int i=1; i<=m; i++)
if(find(edge1[i].x)!=find(edge1[i].y)){
f[find(edge1[i].x)]=find(edge1[i].y);
addedge(edge1[i].x, edge1[i].y, edge1[i].dis);
addedge(edge1[i].y, edge1[i].x, edge1[i].dis);
}
return ;
}
void dfs(int node){
vis[node]=true;
for(int i=head[node]; i; i=edge2[i].next){
int to=edge2[i].to;
if(vis[to]) continue;
deep[to]=deep[node]+1;
fa[to][0]=node;
w[to][0]=edge2[i].w;
dfs(to);
}
return ;
}
int lca(int x, int y)
{
if(find(x)!=find(y)) return -1;
int ans=INF;
if(deep[x]>deep[y]) swap(x,y);
for(int i=20; i>=0; i--)
if(deep[fa[y][i]]>=deep[x]){
ans=min(ans, w[y][i]);
y=fa[y][i];
}
if(x==y) return ans;
for(int i=20; i>=0; i--)
if(fa[x][i]!=fa[y][i]){
ans=min(ans, min(w[x][i], w[y][i]));
x=fa[x][i];
y=fa[y][i];
}
ans=min(ans, min(w[x][0], w[y][0]));
return ans;
}
int main()
{
int x,y,z,q;
scanf("%d%d",&n,&m);
for(int i=1; i<=m; i++){
scanf("%d%d%d",&x,&y,&z);
edge1[i].x=x;
edge1[i].y=y;
edge1[i].dis=z;
}
kruskal();
for(int i=1; i<=n; i++)
if(!vis[i]){
deep[i]=1;
dfs(i);
fa[i][0]=i;
w[i][0]=INF;
}
for(int i=1; i<=20; i++)
for(int j=1; j<=n; j++){
fa[j][i]=fa[fa[j][i-1]][i-1];
w[j][i]=min(w[j][i-1], w[fa[j][i-1]][i-1]);
}
scanf("%d",&q);
for(int i=1; i<=q; i++){
scanf("%d%d",&x,&y);
printf("%d\n",lca(x,y));
}
return 0;
}
最大生成树+lca倍增。
K - 程序自动分析:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
const int N = 200010;
int n, m;
int p[N];
unordered_map<int, int> S;
struct Query
{
int x, y, e;
}query[N];
int get(int x)
{
if (S.count(x) == 0) S[x] = ++ n;
return S[x];
}
int find(int x)
{
if (p[x] != x) p[x] = find(p[x]);
return p[x];
}
int main()
{
int T;
scanf("%d", &T);
while (T -- )
{
n = 0;
S.clear();
scanf("%d", &m);
for (int i = 0; i < m; i ++ )
{
int x, y, e;
scanf("%d%d%d", &x, &y, &e);
query[i] = {get(x), get(y), e};
}
for (int i = 1; i <= n; i ++ ) p[i] = i;
// 合并所有相等约束条件
for (int i = 0; i < m; i ++ )
if (query[i].e == 1)
{
int pa = find(query[i].x), pb = find(query[i].y);
p[pa] = pb;
}
// 检查所有不等条件
bool has_conflict = false;
for (int i = 0; i < m; i ++ )
if (query[i].e == 0)
{
int pa = find(query[i].x), pb = find(query[i].y);
if (pa == pb)
{
has_conflict = true;
break;
}
}
if (has_conflict) puts("NO");
else puts("YES");
}
return 0;
}
见注释。
L - 蔬菜:(待补)
这个真不会捏。
后记:
里面有些题难度已经很高了,我连题解都看不懂,以后再补吧!