您的位置:首页 > 编程语言 > Go语言

【USACO 2011 Dec Gold 】简化农场

2013-12-09 16:54 375 查看
Description
农夫约翰在一所夜校学习算法课程,他刚刚学会了最小生成树。现在约翰意识到他的农场设计得不够高效,他想简化农场的布局。 

约翰的农场可以看做一个图,农田代表图中顶点,田间小路代表图中的边,每条边有一定的长度。约翰注意到,农场中最多有三条小路有着相同的长度。约翰想删除一些小路使得农场成为一棵树,使得两块农田间只有一条路径。但是约翰想把农场设计成最小生成树,也就是农场道路的总长度最短。 

请帮助约翰找出最小生成树的总长度,同时请计算出总共有多少种最小生成树?
Input
第一行,两个整数N 和 M (1 <= N <= 40,000; 1 <= M <= 100,000),分别表示农田数目和小路的数目 

接下来M行,每行三个整数a_i, b_i 和 n_i (1 <= a_i, b_i <= N; 1<= n_i <= 1,000,000)表示一条小路的起点、终点和长度。相同的n_i最多出现三次。 

Output
一行,两个整数,分别表示最小生成树的总长度和不同最小生成树的数量(mod 1,000,000,007)
Sample Input

4 5
1 2 1
3 4 1
1 3 2
1 4 2
2 3 2


Sample Output

4 3

【分析】

第一问就不说了。

对于第二问,我们可以敏锐地发现,相同的n_i只出现了最多3次。于是我们将相同的边分为一个阶段,分阶段处理。可以分阶段处理的原因是,如果用kruskal来做生成树的话,我们一定是先选用最小的边使图尽可能地连通后,才选用下一种长度的边。所以很容易证明,不同的最小生成树中每一种长度的边数量是一定的。所以我们只需要求得每个阶段的方案总数,累乘起来即可。

对于每个阶段,我们先用一般的kruskal算出第一问的答案,顺便用一个变量tot记录一下用了多少条边来使其连通。然后我们DFS,枚举这个阶段边的方案(由于每个阶段最多3条边,所以复杂度比较低),判断其是否等效。这里等效的定义是,连通情况与kruskal计算的连通情况一致。由上面的叙述,我们知道,每次枚举最多枚举tot条边。

至此,本题完美解决,时间复杂度O(MlogM+M*k)  

O(MlogM)为排序的复杂度,而O(k)表示DFS的复杂度,k是个较小的常数。

【代码】

/*
ID:Ciocio
LANG:C++
DATE:2013-12-08
TASK:Simplify
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#include <queue>

using namespace std;

#define MAXN 40005
#define MAXE 100005
#define MODER 1000000007

int N,M,R,ret;
int fa[MAXN],pr[MAXN];
bool vis[MAXE];
struct edge{
int a,b,c;
friend bool operator<(const edge &A,const edge &B)
{return A.c<B.c;}
}E[MAXE];

void _read(int &x)
{
char tt=getchar();
while(tt<'0'||'9'<tt) tt=getchar();
for(x=0;'0'<=tt&&tt<='9';x=x*10+tt-'0',tt=getchar());
}

int _getfather(int x,int *fa)
{
return (x==fa[x])?fa[x]:fa[x]=_getfather(fa[x],fa);
}

void _init()
{
_read(N);_read(M);
for(int i=1;i<=N;i++) pr[i]=fa[i]=i;
for(int i=1;i<=M;i++)
{
_read(E[i].a);
_read(E[i].b);
_read(E[i].c);
}
sort(E+1,E+M+1);
}

void _Dfs(int k,int a) //传递形参a,是为了生成一组组合,而不是排列
{
if(!k) {ret++;return;} //找到一组方案
if(R+1-a<k) return; //可行性剪枝
for(int i=a;i<=R;i++)
if(!vis[i])
{
vis[i]=true;
int fx=E[i].a,fy=E[i].b;
while(fx!=pr[fx]) fx=pr[fx]; //这里的并查集不要路径压缩
while(fy!=pr[fy]) fy=pr[fy];
if(fx!=fy) //如果这条边有用,我们使用它
{
pr[fx]=fy; //不路径压缩的好处是可以状态还原
_Dfs(k-1,i+1);
pr[fx]=fx;
}
vis[i]=false;
}
}

typedef long long LL;
void _solve()
{
LL ans1=0,ans2=1;
E[M+1].c=-1;
for(int i=1,j=1;i<=M;i=j+1,j=i)
{
for(;E[j].c==E[j+1].c;j++); //找出这一阶段,阶段范围[i,j]
int fx,fy;
int tot=0;
for(int k=i;k<=j;k++) //kruskal求第一问
{
fx=_getfather(E[k].a,fa);
fy=_getfather(E[k].b,fa);
if(fx!=fy)
{
tot++; //记下tot
fa[fx]=fy;
ans1+=E[k].c;
}
}
if(!tot) continue; //
R=j;ret=0;
_Dfs(tot,i);
ans2=(ans2*ret)%MODER;
for(int k=i;k<=j;k++) //同步pr,便于下一次Dfs
{
fx=_getfather(E[k].a,pr);
fy=_getfather(E[k].b,pr);
if(fx!=fy)
pr[fx]=fy;
}
}
cout<<ans1<<" "<<ans2<<endl;
}

int main()
{
_init();
_solve();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: