[タイトル]イタリア
平面上であり、\(n(nは<= 1000 )\) ポイント、あなたのタスクは、すべてのn個の点ユニコムを取得することです。これを行うには、あなたが側の一部を作成することができ、コストは、の二つの端点に等しいユークリッド距離の二乗します。別の\(Q(Q <= 8 )\) パッケージには、あなたが最初に購入した場合、購入することができます\(私は\)パッケージを、すべてのノードでパッケージが相互に接続されているになります。最初の\(私は\)支出のパッケージです\(C_I \)を。
[アルゴリズム]
\(クラスカル\)
[分析]
アルゴリズムを考える可能性が最も高いです:最初の列挙が買う何のパッケージを、パッケージが正しい値に設定されて含まれている(0 \)\、最小スパニングツリーを。列挙の量が\(O(2 ^ Q)\) 、複雑さがソートされている時間の縁に\()^ O(N- 2logn \) 、および各ソート後\(クラスカル\)アルゴリズムの時間複雑性をある\(O(^ N-2)\)ので、総時間複雑である\(O(Qnを^ 2 + 2 ^ ^ N-2logn)\) 、被写体のサイズが大きすぎるため。
ほんの短い時間の複雑さを低減するように最適化:最初の画像検索(それ以降のパッケージなし)を与えるために、最小スパニングツリーのを\(N-1 \に)側面を、残りの側面は役に立ちません。何それから(あなたが圧縮された状態のイデオロギーを使用することができる)パッケージを購入列挙し、列挙すると、マップの端にある最小スパニングツリーの後にパッケージには、非常に少数となっているとき。
なぜこれが起こることができますか?ほとんどの説明は証明されていません。ここでの証明は、
初めて目に\(クラスカル\)最小スパニングツリーを入力しない側のアルゴリズム、。答えは:側の両端がすでに同じコレクションに属しています。パッケージを購入した後、コンティンジェンシーに対応する側縁の一部になる\(0 \) 、およびパッケージの各エッジのためされていない(E \)を\ソートない、\(E \)劣ら前側をだけでなく、いくつかのより多くの重みである(0 \)\側ので、元の\(クラスカル\)同じパッケージに列挙の背面を捨てするに、縁に「破棄」されています。
[コード]
#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;
}