写这篇题解是因为作者太蒻已经忘了最小生成树了。
<题面>
这个题还真是想不到最小生成树。
$80\%$算法
复杂度:$Theta(k^2 log N )$
用了二分答案(明显答案具有单调性)
然后$k^2$暴力判断是否合法。
可以得到80分。
$100\%$算法
复杂度:$Theta(k^2)$
考虑上面的暴力判断,
如何判断呢?要搜点距,$dfs$
然后我们就可以得到一些东西。
假设现在得到了答案是$ans$
我们考虑它的特性。
在每一个点上以$ans$为半径画圆
那么,一定有一条边上的两条圆是相切的。
如果$ans$变小,那么一定有一个更优解。
如果$ans$更大,那么圆一定会相交导致路径不连续。
我们再找找性质,
发现这两个圆一定在上边界到下边界的路径上,且是路径上最长的边,这也是导致上文路径不连续的原因。
对于一个点,那个圆一定出现在与它相连的最短边上,
因为如果有更长边,更长边会充当一个三角形的最长边,导致路径会受更短的一条边约束。
于是有最小生成树(我考试肯定想不出来QAQ)
在找最小生成树时,就一定可以保证找到加入树的边一定是上文的最短边。
于是直接套用$Prim$顺便维护到下边界的距离,复杂度$Theta(k^2)$
我本来想优化,于是想用堆:$Theta(k^2) Rightarrow Theta(e log k)$
于是边数$e=k^2$
$Theta(k^2) Rightarrow Theta(k^2 log k)$//当我没说
这时有一个问题,不能建图,空间复杂度不可承受。
于是需要使用欧几里得距离最小生成树。
你可能肯定会想,这又是什么玩意?
其实就是最小生成树,只是不建边而是去用点直接计算距离。
所以愉快的AC了
#include <iostream> #include <cstdio> #include <cmath> #define N 6060 #define LF double using namespace std; int pn,hei; struct x_y{ LF x,y; }ps[5*N]; LF dis[5*N],ans=0; bool is_v[N]; inline LF len(const x_y a,const x_y b){ return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)); } void prim(){ for(int i=1;i<=pn;i++){ dis[i]=hei-ps[i].y; } for(int i=1;i<=pn+1;i++){ int x=0; for(int j=1;j<=pn+1;j++){ if((!is_v[j])&&(x==0||dis[j]<dis[x])){ x=j; } } ans=max(ans,dis[x]); if(x==pn+1){//这里是必写的,因为这个意味着更新结束 return ; } is_v[x]=1; int j; for(j=1;j<=pn;j++){ { dis[j]=min(dis[j],len(ps[j],ps[x])); } } dis[pn+1]=min(dis[pn+1],ps[x].y); } } int main(){ int x,y; scanf("%*d%d%d",&hei,&pn); for(int i=1;i<=pn;i++){ scanf("%d%d",&x,&y); ps[i].x=x; ps[i].y=y; } dis[pn+1]=hei; prim(); printf("%.9lf",ans/2); }
码量也并不大。
这里顺便写一下两个最小生成树的板板。
1.Kruskal
/***** Kruskal *****/ #include<cmath> #include<cstdio> #include<iostream> #include<algorithm> using namespace std; bool b[101]; int answer,ans,n,t,f[1001]; struct node { int fr,to,ti; }a[10001]; bool cmp(node xx,node yy) { return xx.ti<yy.ti; } int find(int x) { if (f[x]==x) return x; else return f[x]=find(f[x]); } void merge(int x,int y) { int xx=find(x); int yy=find(y); if (xx<yy) f[xx]=f[yy]; else f[yy]=f[xx]; } int main() { scanf("%d",&n); for (int i=1; i<=n; ++i) f[i]=i; for (int i=1; i<=n; ++i) for (int j=1; j<=n; ++j) { int x; scanf("%d",&x); if (x!=0) {a[++t]=(node){i,j,x};} } int k=0; sort(a+1,a+t+1,cmp); for (int i=1; i<=t; ++i) { if (find(a[i].fr)!=find(a[i].to)) {//不在同一个集合中 merge(a[i].fr,a[i].to);//合并 ans+=a[i].ti;//记录最小生成值 k++;//记录边数 } if (k==n-1) break;//一棵树,n个点,n-1条边 } printf("%d",ans); }
2.Prim
/****** Prime ******/ #include<iostream> #include<cstdio> #include<cstring> using namespace std; int con[101][101],dis[101],num,a; int main(){ cin>>num; long long sum=0,min=0x7fffffff; memset(dis,0x7f,sizeof(dis)); for(int i=1;i<=num;i++){ for(int j=1;j<=num;j++){ scanf("%d",&con[i][j]); } } dis[1]=0; for(int i=1;i<=num;i++) dis[i]=con[1][i]; for(int i=1;i<=num;i++){ min=0x7f7f7f7f; for(int j=1;j<=num;j++){ if(dis[j]!=0&&dis[j]<min){ min=dis[j];//cout<<"i"<<i<<"min"<<min<<endl; a=j;//cout<<"a"<<a<<endl; } } sum+=dis[a]; dis[a]=0; for(int j=1;j<=num;j++) if(dis[j]>con[a][j]) dis[j]=con[a][j]; } printf("%d ",sum); return 0; }