同步发布于我的网站

Problem

Sajin最近深入研究了最小生成树,现在他已经掌握了MST的算法。他渴望通过一系列查询来评估您对最小生成树概念的掌握程度。

您将面临一个加权无向图,该图包含没有任何自环的 \(n\) 个顶点和 \(m\) 条边。

Sajin提出 \(q\) 询问。对于每个顶点集,都给出了一个顶点集 \(S\) 。您的目标是确定 \(S\) 的诱导子图(induced subgraph)并找到其最小生成树的权重。如果 \(S\) 的诱导子图断开,则输出-1

图的诱导子图是另一个图,由图的顶点子集和原始图中的所有边组成,连接该子集中的顶点对。即,对于图 \(G=(V,E)\) ,给定 \(V^\prime\) ,则有 \(E^\prime=\{(u,v) \mid u,v\in V^\prime,(u,v)\in E\}\),诱导子图为 \(G^\prime=(V^\prime,E^\prime)\)。

\(2\le n\le 10^5,1\le m,q\le10^5\)

\(1 \le |S_i|\le n,\sum S_i\le 10^5\)

Solution

Algorithm1

由于限制了 \(\sum S_i\le 10^5\) , MST本身的时间复杂度是完全过得去的。但是瓶颈在于如何从 \(G\) 中挑出需要的边。

考虑两种找边的方法:

  1. 对于 \(S\) 中的点 \(x\),遍历 \(x\) 的所有出边,终点为 \(y\) 。如果 \(y\in S\) ,那么边 \((x,y)\in G^\prime\) 。将得到的所有边存起来并排序跑kruskal。

​ 对于一次询问,这个算法的最坏时间复杂度为 \(O(m\log(|S|))\) ,即所有边都需要被遍历一次。

  1. 用map存图 \(G\) ;双重遍历 \(x,y\in S\) ,将其中存在的边 \((x,y)\) 取出来并排序跑kruskal。

​ 整体时间复杂度是\(O(n^2+n^2\log(n))\),即双重遍历和kruskal。

取 \(base=\sqrt n\) ,

  • 当 \(|S|\le base\) 时使用第二种算法,总时间复杂度为 \(O(n\cdot base\cdot \log(n))\)
  • 当 \(|S| > base\) 时使用第一种算法,总时间复杂度为 \(O(base\cdot m\cdot \log(n))\) 。

Algorithm2

一般建立最小生成树,我们会使用上面的第一种找边的方式:对于 \(S\) 中的点 \(x\),遍历 \(x\) 的所有出边,终点为 \(y\) 。如果 \(y\in S\) ,那么边 \((x,y)\in G^\prime\) 。将得到的所有边存起来并排序跑kruskal。

问题的瓶颈在于新建导出子图 \(G^\prime\) 的时候,会被类菊花图卡掉。可能会一直询问类菊花图的核心点,而这些核心点具有很多条出边,每次询问都需要遍历核心节点的出边。

考虑类似三元环的连边方式,对于每条边,只从度小的点到度大的点连一条单向边。如果度数相同,则从编号小的连接到编号大的。

在这样构建的图中,每个点的出度不会大于 \(\sqrt m\) 。如果有某个点的出度为 \(d > \sqrt m\) ,那么在原图中,需要有 \(d\) 个度数大于 \(d\) 的节点与该节点连接,此时总边数至少 \(d\times d>m\)​,矛盾。

所以可以在这样的一张单向图中放心的找边,单次询问时间复杂度 \(O(|S| \sqrt m \log(|S|))\)​ 。

Code

Code1

#define N 100010
struct Edge
{
int x,y;
LL w;
Edge(int xx,int yy,LL ww)
{
x=xx;
y=yy;
w=ww;
}
bool operator<(const Edge & z)
{
return w<z.w;
}
}; int n,m,q; namespace algo1{
int cnt;
int head[N],nxt[N*2],ver[N*2],w[N*2];
void insert(int x,int y,LL z)
{
nxt[++cnt]=head[x];
head[x]=cnt;
ver[cnt]=y;
w[cnt]=z;
} }; namespace algo2{
map<pii,LL>mp;
void insert(int x,int y,LL z)
{
pii e=make_pair(x,y);
if(mp.find(e)==mp.end()) mp[e]=z;
else mp[e]=min(mp[e],z);
}
bool exist(pii e)
{
return mp.find(e)!=mp.end();
}
}; namespace DSU{
int vis[N],f[N],sz[N]; int getf(int x)
{
if(x==f[x]) return x;
return f[x]=getf(f[x]);
} void merge(int x,int y)
{
x=getf(x);
y=getf(y);
if(x==y) return;
if(sz[x]<sz[y]) swap(x,y);
f[y]=x;
sz[x]+=sz[y];
} bool ask(int x,int y)
{
return getf(x)==getf(y);
}
}; LL MST(vector<int>V,vector<Edge>E)
{
sort(E.begin(),E.end());
LL ans=0;
int cnt=1;
for(unsigned int i=0;i<E.size();i++)
{
if(!DSU::ask(E[i].x,E[i].y))
{
cnt++;
DSU::merge(E[i].x,E[i].y);
ans+=E[i].w;
}
}
if(cnt!=V.size()) return -1;
return ans;
} int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
// cin>>t;
while(t--)
{
cin>>n>>m>>q;
for(int i=1;i<=m;i++)
{
int x,y;
LL z;
cin>>x>>y>>z;
algo1::insert(x,y,z);
algo1::insert(y,x,z);
algo2::insert(x,y,z);
algo2::insert(y,x,z);
}
const int base=sqrt(100000);
while(q--)
{
int k;
cin>>k;
vector<int>S;
vector<Edge>E;
for(int i=0;i<k;i++)
{
int t;cin>>t;
S.push_back(t);
DSU::vis[t]=1;
DSU::sz[t]=1;
DSU::f[t]=t;
}
if(k>base)
{
//algo1
for(int i=0;i<k;i++)
{
int x=S[i];
for(int j=algo1::head[x];j;j=algo1::nxt[j])
{
int y=algo1::ver[j];
LL w=algo1::w[j];
if(DSU::vis[y])
{
E.push_back(Edge(x,y,w));
}
}
}
}
else
{
//algo2
for(int i=0;i<k;i++)
{
for(int j=i+1;j<k;j++)
{
pii e=make_pair(S[i],S[j]);
if(algo2::exist(e))
{
E.push_back(Edge(S[i],S[j],algo2::mp[e]));
}
}
}
} cout<<MST(S,E)<<endl; for(int i=0;i<k;i++)
{
DSU::vis[S[i]]=0;
}
}
}
return 0;
}

生最差劲的一件事莫过于早上写的代码但半路跑路还没做任何标记,晚上打开不知道从何续写。

人生最美好的一件事莫过于发现这份代码其实是以及写完的!?而且还能过样例!??而且直接提交直接过了!???

Code2

#define N 100010
struct Edge
{
int x,y;
LL w;
Edge(int xx,int yy,LL ww)
{
x=xx;
y=yy;
w=ww;
}
bool operator<(const Edge & z)
{
return w<z.w;
}
}; int n,m,q; namespace G1
{
map<pii,LL>mp;
int degree[N];
void insert(int x,int y,LL z)
{
pii e=make_pair(x,y);
if(mp.find(e)==mp.end()) mp[e]=z;
else mp[e]=min(mp[e],z);
degree[x]++;
}
}; namespace G2
{
int cnt;
int head[N],nxt[N*2],ver[N*2];
LL w[N];
void insert(int x,int y,LL z)
{
nxt[++cnt]=head[x];
head[x]=cnt;
ver[cnt]=y;
w[cnt]=z;
}
}; namespace DSU{
int vis[N],f[N],sz[N]; int getf(int x)
{
if(x==f[x]) return x;
return f[x]=getf(f[x]);
} void merge(int x,int y)
{
x=getf(x);
y=getf(y);
if(x==y) return;
if(sz[x]<sz[y]) swap(x,y);
f[y]=x;
sz[x]+=sz[y];
} bool ask(int x,int y)
{
return getf(x)==getf(y);
}
}; LL MST(vector<int>V,vector<Edge>E)
{
sort(E.begin(),E.end());
LL ans=0;
int cnt=1;
for(unsigned int i=0;i<E.size();i++)
{
if(!DSU::ask(E[i].x,E[i].y))
{
cnt++;
DSU::merge(E[i].x,E[i].y);
ans+=E[i].w;
}
}
if(cnt!=V.size()) return -1;
return ans;
} int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cout.precision(10);
int t=1;
// cin>>t;
while(t--)
{
cin>>n>>m>>q;
for(int i=1;i<=m;i++)
{
int x,y;
LL z;
cin>>x>>y>>z;
G1::insert(x,y,z);
G1::insert(y,x,z);
} for(auto it=G1::mp.begin();it!=G1::mp.end();it++)
{
int x=it->first.first,y=it->first.second;
LL w=it->second;
if(G1::degree[x]<G1::degree[y]||(G1::degree[x]==G1::degree[y]&&x<y)) G2::insert(x,y,w);
} while(q--)
{
int k;
cin>>k;
vector<int>S;
vector<Edge>E;
for(int i=0;i<k;i++)
{
int t;cin>>t;
S.push_back(t);
DSU::vis[t]=1;
DSU::sz[t]=1;
DSU::f[t]=t;
}
for(int i=0;i<k;i++)
{
int x=S[i];
for(int j=G2::head[x];j;j=G2::nxt[j])
{
int y=G2::ver[j];
LL w=G2::w[j];
if(DSU::vis[y])
{
E.push_back(Edge(x,y,w));
}
}
} cout<<MST(S,E)<<endl; for(int i=0;i<k;i++)
{
DSU::vis[S[i]]=0;
}
} }
return 0;
}

2024牛客多校2B MST的更多相关文章

  1. 2019牛客多校第一场 I Points Division(动态规划+线段树)

    2019牛客多校第一场 I Points Division(动态规划+线段树) 传送门:https://ac.nowcoder.com/acm/contest/881/I 题意: 给你n个点,每个点有 ...

  2. 牛客多校第一场 B Inergratiion

    牛客多校第一场 B Inergratiion 传送门:https://ac.nowcoder.com/acm/contest/881/B 题意: 给你一个 [求值为多少 题解: 根据线代的知识 我们可 ...

  3. 2019牛客多校第二场 A Eddy Walker(概率推公式)

    2019牛客多校第二场 A Eddy Walker(概率推公式) 传送门:https://ac.nowcoder.com/acm/contest/882/A 题意: 给你一个长度为n的环,标号从0~n ...

  4. 牛客多校第三场 F Planting Trees

    牛客多校第三场 F Planting Trees 题意: 求矩阵内最大值减最小值大于k的最大子矩阵的面积 题解: 矩阵压缩的技巧 因为对于我们有用的信息只有这个矩阵内的最大值和最小值 所以我们可以将一 ...

  5. 牛客多校第三场 G Removing Stones(分治+线段树)

    牛客多校第三场 G Removing Stones(分治+线段树) 题意: 给你n个数,问你有多少个长度不小于2的连续子序列,使得其中最大元素不大于所有元素和的一半 题解: 分治+线段树 线段树维护最 ...

  6. 牛客多校第四场sequence C (线段树+单调栈)

    牛客多校第四场sequence C (线段树+单调栈) 传送门:https://ac.nowcoder.com/acm/contest/884/C 题意: 求一个$\max {1 \leq l \le ...

  7. 牛客多校第3场 J 思维+树状数组+二分

    牛客多校第3场 J 思维+树状数组+二分 传送门:https://ac.nowcoder.com/acm/contest/883/J 题意: 给你q个询问,和一个队列容量f 询问有两种操作: 0.访问 ...

  8. 2019牛客多校第八场 F题 Flowers 计算几何+线段树

    2019牛客多校第八场 F题 Flowers 先枚举出三角形内部的点D. 下面所说的旋转没有指明逆时针还是顺时针则是指逆时针旋转. 固定内部点的答案的获取 anti(A)anti(A)anti(A)或 ...

  9. 2019年牛客多校第一场B题Integration 数学

    2019年牛客多校第一场B题 Integration 题意 给出一个公式,求值 思路 明显的化简公式题,公式是分母连乘形式,这个时候要想到拆分,那如何拆分母呢,自然是裂项,此时有很多项裂项,我们不妨从 ...

  10. 2020牛客多校第八场K题

    __int128(例题:2020牛客多校第八场K题) 题意: 有n道菜,第i道菜的利润为\(a_i\),且有\(b_i\)盘.你要按照下列要求给顾客上菜. 1.每位顾客至少有一道菜 2.给顾客上菜时, ...

随机推荐

  1. Windows 提权-MSSQL

    本文通过 Google 翻译 MSSQL – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充. 导航 0 前言 ...

  2. Windows 提权-内核利用_1

    本文通过 Google 翻译 Kernel Exploits Part 1 – Windows Privilege Escalation 这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校 ...

  3. kubeadm安装kubernetes

    kubeadm安装kubernetes kubeadm 是官方社区推出的一个用于快速部署 kubernetes 集群的工具. 这个工具能通过两条指令完成一个 kubernetes 集群的部署: $ k ...

  4. 使用C#创建一个MCP客户端

    前言 网上使用Python创建一个MCP客户端的教程已经有很多了,而使用C#创建一个MCP客户端的教程还很少. 为什么要创建一个MCP客户端呢? 创建了一个MCP客户端之后,你就可以使用别人写好的一些 ...

  5. ubuntu apt 安装报错:Media change: please insert the disc labeled 'Ubuntu 20.04.5 LTS Focal Fossa - Release amd64 (20220831)' in the drive '/cdrom/' and press [Enter]

    前言 如果你在 Ubuntu 上使用 apt 安装软件包时遇到 "Media change: please insert the disc labeled ..." 的错误消息,这 ...

  6. Docker容器详解

    [] 容器(Container)是一种轻量级的虚拟化技术,它通过操作系统级的虚拟化,将应用程序及其依赖环境打包在一起,确保应用程序可以在任何环境中一致运行.与虚拟机不同,容器共享宿主操作系统的内核,而 ...

  7. 代码块--java进阶day03

    1.代码块 1.局部代码块 定义在方法中的一对大括号,可以提早释放内存,走完{}里的逻辑后就会被释放,在之后的编程中无法使用 2.构造代码块 位置在类中,方法外的{},在构造方法执行的时候,构造代码块 ...

  8. AI时代:大模型开发framework之langchain和huggingface

    langchain: 提供了大模型相关应用开发的所有便利. https://python.langchain.com/docs/get_started/introduction Build your ...

  9. Text Bg ContentSizeFitter的另类控制

    using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; [RequireComponent(typeof(Co ...

  10. Web前端入门第 32 问:CSS background 元素渐变背景用法全解

    渐变背景在 CSS 里面就是一个颜色到另一个颜色渐渐变化的样子. 本文示例中,盒子基础样式: .box { margin: 20px; padding: 20px; border: 10px dash ...