【题意】

平面上有\(n(n<=1000)\)个点,你的任务是让所有n个点联通。为此,你可以新建一些边,费用等于两个端点的欧几里得距离平方。另外还有\(q(q<=8)\)个套餐可以购买,如果你购买了第\(i\)个套餐,该套餐中的所有结点将变得相互连接。第\(i\)个套餐的花费为\(C_i\)。

【算法】

\(Kruskal\)

【分析】

最容易想到的算法是:先枚举购买哪些套餐,把套餐中包含的权值设为\(0\),然后求最小生成树。由于枚举量为\(O(2^q)\),给边排序的时间复杂度为\(O(n^2logn)\),而排序之后每次\(kruskal\)算法的时间复杂度为\(O(n^2)\),因此总时间复杂度为\(O(2^qn^2+n^2logn)\),对于题目来说的规模太大了。

只需一个小小的优化即可降低时间复杂度:先求一次原图 (不购买任何套餐) 的最小生成树,得到\(n-1\)条边,其余的边就没用了。然后枚举买哪些套餐(这里可以用状态压缩的思想),则枚举套餐后再求最小生成树时,图上的边已经寥寥无几。

为什么可以这样呢? 大部分题解都没有证明。这里给出证明过程

首先回顾一下,在\(kruskal\)算法中,哪些边不会进入最小生成树。答案是:两端已经属于同一个集合的边。买了套餐后,相当于一些边的边权变成了\(0\),而对于不在套餐中的每条边\(e\),排序在\(e\)之前的边一个都没少,反而还多了一些权值为\(0\)的边,所以在原图\(kruskal\)时被“扔掉”的边,在后面的枚举套餐中也一样会被扔掉。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1000+10;
const int MAXM=MAXN*MAXN;
int n,q,T,ans=0x3f3f3f3f;
int s[10][MAXN];
int c[10];
struct Node2
{
int x,y;
}city[MAXN];
struct Node
{
int u,v,w;
}edge[MAXM],g[MAXM];
int cnt,m;
int fa[MAXN];
int save[MAXN];
inline int read()
{
int tot=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
tot=tot*10+c-'0';
c=getchar();
}
return tot;
}
inline bool cmp(Node x,Node y)
{
return x.w<y.w;
}
inline int find(int k)
{
if(fa[k]==k)return k;
else return fa[k]=find(fa[k]);
}
inline int init_kruskal()
{
int tot=0,cc=0;
for(int i=1;i<=n;i++)
fa[i]=i;
for(int i=1;i<=cnt;i++)
{
if(fa[find(edge[i].u)]!=fa[find(edge[i].v)])
{
fa[find(edge[i].u)]=find(edge[i].v);
tot++;
cc+=edge[i].w;
save[tot]=i;//记录边
}
if(tot==n-1)break;
}
return cc;
}
inline int kruskal(int tot)
{
int cc=0,t=tot;
for(int i=1;i<n;i++)
{
if(find(g[i].u)!=find(g[i].v))
{
fa[find(g[i].u)]=find(g[i].v);
t++;
cc+=g[i].w;
}
if(t==n-1)break;
}
return cc;
}
inline void solve()
{
for(int ss=0;ss<(1<<q);ss++)//状压思想,用二进制来表示选还是不选
{
for(int i=1;i<=n;i++)
fa[i]=i;//初始化并查集
int tot=0;//选中套餐中被连接的点数
int cc=0;//套餐的钱
for(int k=1;k<=q;k++)
{
if(ss&(1<<(k-1)))//如该套餐被选中
{
//cout<<k<<" ";
cc+=c[k];
for(int i=1;i<=s[k][0];i++)
{
for(int j=i+1;j<=s[k][0];j++)
{
//cout<<s[k][0]<<" "<<k<<" "<<s[k][i]<<" "<<s[k][j]<<endl;
if(find(s[k][i])!=find(s[k][j]))
{
fa[find(s[k][i])]=find(s[k][j]);
tot++;
}
}
}
}
}
//cout<<endl;
//cout<<cc<<endl;
//cout<<tot<<" "<<kruskal(tot)<<" "<<cc<<endl;
ans=min(ans,kruskal(tot)+cc);//更新最小值
}
}
int main()
{
T=read();
while(T--)
{
cnt=0;
n=read();q=read();
for(int i=1;i<=q;i++)
{
s[i][0]=read();c[i]=read();
for(int j=1;j<=s[i][0];j++)
s[i][j]=read();//读入套餐
}
for(int i=1;i<=n;i++)
city[i].x=read(),city[i].y=read();
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
edge[++cnt].u=i;
edge[cnt].v=j;
edge[cnt].w=(city[i].x-city[j].x)*(city[i].x-city[j].x)+(city[i].y-city[j].y)*(city[i].y-city[j].y);
}
}
sort(edge+1,edge+1+cnt,cmp);
ans=init_kruskal();//原始图的最小生成树
//cout<<ans<<endl;
for(int i=1;i<n;i++)
{
g[i].u=edge[save[i]].u;
g[i].v=edge[save[i]].v;
g[i].w=edge[save[i]].w;
}//建一个新图
/*for(int i=1;i<n;i++)
{
cout<<g[i].u<<" "<<g[i].v<<" "<<g[i].w<<endl;
}
cout<<endl;*/
solve();//准备枚举
cout<<ans<<endl;
if(T)cout<<endl;//UVA不会省略最后的换行符
}
return 0;
}

洛谷 题解 UVA1151 【买还是建 Buy or Build】的更多相关文章

  1. 洛谷 题解 UVA572 【油田 Oil Deposits】

    这是我在洛谷上的第一篇题解!!!!!!!! 这个其实很简单的 我是一只卡在了结束条件这里所以一直听取WA声一片,详细解释代码里见 #include<iostream> #include&l ...

  2. 洛谷比赛 U5442 买(最长链)

    U5442 买 题目提供者bqsgwys 标签 树形结构 树的遍历 洛谷原创 题目背景 小E是个可爱的电路编码员. 题目描述 一天小E又要准备做电路了,他准备了一个电路板,上面有很多个电路元器件要安装 ...

  3. 洛谷 P1629 邮递员送信-反向建边

    洛谷 P1629 邮递员送信 题目描述: 有一个邮递员要送东西,邮局在节点 11.他总共要送 n-1n−1 样东西,其目的地分别是节点 22 到节点 nn.由于这个城市的交通比较繁忙,因此所有的道路都 ...

  4. 洛谷 题解 P1600 【天天爱跑步】 (NOIP2016)

    必须得说,这是一道难题(尤其对于我这样普及组205分的蒟蒻) 提交结果(NOIP2016 天天爱跑步): OJ名 编号 题目 状态 分数 总时间 内存 代码 / 答案文件 提交者 提交时间 Libre ...

  5. 洛谷题解P4314CPU监控--线段树

    题目链接 https://www.luogu.org/problemnew/show/P4314 https://www.lydsy.com/JudgeOnline/problem.php?id=30 ...

  6. 洛谷 P2918 [USACO08NOV]买干草Buying Hay 题解

    P2918 [USACO08NOV]买干草Buying Hay 题目描述 Farmer John is running out of supplies and needs to purchase H ...

  7. 洛谷 题解 P1196 【[NOI2002]银河英雄传说】

    并查集大难题. 看了题解之后才有思路,调了很久很久才AC,当然要写一篇题解来纪念一下. 先来分析一下这些指令的特点,很容易发现对于每个M指令,只可能一次移动整个队列,并且是把两个队列首尾相接合并成一个 ...

  8. 洛谷 P1194 【买礼物】

    这道题其实就是转化一个模型就可以了. 买了一个另外一个又优惠,其实就相当于在优惠的时候连一条边,因为不可能多买,所以就是建一棵最小生成树.最后因为肯定买了一件物品,要加上最初的单价. 代码: #inc ...

  9. 洛谷题解 CF777A 【Shell Game】

    同步题解 题目翻译(可能有童鞋没读懂题面上的翻译) 给你三张牌0,1,2. 最初选一张,然后依次进行n次交换,交换规则为:中间一张和左边的一张,中间一张和右边一张,中间一张和左边一张...... 最后 ...

随机推荐

  1. 十九.部署LNMP环境、构建LNMP平台、地址重写

    proxy client web1 web2   1.部署LNMP环境 1.1 部署nginx(前面已部署过) 1.2 部署mariadb ]# yum -y install mariadb mari ...

  2. CF707D Persistent Bookcase 可持久化线段树

    维护一个二维零一矩阵(n,m<=1000),支持四种操作(不超过10^5次): 将(i,j)置一 将(i,j)置零 将第i行零一反转yu 回到第K次操作前的状态 每次操作后输出全局一共有多少个一 ...

  3. splay树 序列终结者

    /* 4655 序列终结者  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 大师 Master 题解       题目描述 Description 网上有许多题,就是给定一个序 ...

  4. 理解 java 使用 异或 交换两数

    网上看了一些使用异或交换两数,不是很好理解.现在写一下自己的理解. 首先是 异或原则,对于任意 x: x ^ x == 0; x ^ 0 == x; 思路: 根据原则,可以得到两个公式: 求a:    ...

  5. mac 启动mysql

    sudo /usr/local/mysql/support-files/mysql.server stop sudo /usr/local/mysql/support-files/mysql.serv ...

  6. C# 下划线转驼峰

    /// <summary> /// 转换 /// </summary> /// <param name="sender"></param& ...

  7. centos7使用yum提示有事物未完成的解决办法:

    错误提示: There are unfinished transactions remaining. You might consider running yum-complete-transacti ...

  8. fixedFluxPressure边界条件【转载】

    转载自:http://blog.sina.com.cn/s/blog_e256415d0102vikh.html fixedFluxPressure是OpenFOAM较新的一个边界条件,表示边界处压力 ...

  9. LUA upvalues

    1 upvalue概念 upvalue:嵌套函数的外部函数的局部变量 function func(a) <== 这个函数返回值是一个函数 return function () a = a + 1 ...

  10. php如何生成 uuid(总结)

    php如何生成 uuid(总结) 一.总结 一句话总结: UUID的全拼为“Universally Unique Identifier”,可以译为“通用唯一识别码”. UUID是指在一台机器上生成的数 ...