【bzoj1827】[Usaco2010 Mar]gather 奶牛大集会

题目描述

         Bessie正在计划一年一度的奶牛大集会,来自全国各地的奶牛将来参加这一次集会。当然,她会选择最方便的地点来举办这次集会。每个奶牛居住在 N(1<=N<=100,000) 个农场中的一个,这些农场由N-1条道路连接,并且从任意一个农场都能够到达另外一个农场。道路i连接农场A_i和B_i(1 <= A_i <=N; 1 <= B_i <= N),长度为L_i(1 <= L_i <= 1,000)。集会可以在N个农场中的任意一个举行。另外,每个牛棚中居住着C_i(0 <= C_i <= 1,000)只奶牛。在选择集会的地点的时候,Bessie希望最大化方便的程度(也就是最小化不方便程度)。比如选择第X个农场作为集会地点,它的不方便程度是其它牛棚中每只奶牛去参加集会所走的路程之和,(比如,农场i到达农场X的距离是20,那么总路程就是C_i*20)。帮助Bessie找出最方便的地点来举行大集会。  

输入

第一行:一个整数N

第二到N+1行:第i+1行有一个整数C_i

第N+2行到2*N行,第i+N+1行为3个整数:A_i,B_i和L_i。

输出

一个数字表示答案

样例

Input

Output

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

15

对于

20%数据n<20

50%数据 n<2000

100%数据n<100,000

首先想到的就是用链表。。

然后想到将每个点为终点的答案算出来。在算的时候发现找终点这一过程是O(n²)

先附上50%AC暴力代码:

#include<cstdio>
#include<iostream>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<queue>
using namespace std;
queue<int>q;
int dis[2005][2005];
int c[4005];
int head[4005],next[4005],tov[4005];
bool b[4005];
int tot;
long long minx=5000000000LL,ans;
int n;
void go(int x,int y,int z)
{
  tot++;
  tov[tot]=y;
  next[tot]=head[x];
  head[x]=tot;
}
int main()
{
  memset(dis,-1,sizeof(dis));
  freopen("A.in","r",stdin);
  freopen("A.out","w",stdout);
  scanf("%d",&n);//cin>>n;
  for(int i=1;i<=n;i++)
  scanf("%d",&c[i]);//cin>>c[i];
  for(int i=1;i<n;i++)
  {
    int x,y,z;
    scanf("%d %d %d",&x,&y,&z);//cin>>x>>y>>z;
    dis[x][y]=dis[y][x]=z;
    go(x,y,z);
    go(y,x,z);
  }
    for(int i=1;i<=n;i++)
    {
      memset(b,false,sizeof(b));
      q.push(i);
      while(!q.empty())
      {
        int u=q.front();
        b[u]=true;
        q.pop();
        int v=head[u];
        while(v)
        {
          if(i==u)
          q.push(tov[v]);
          else if(i!=u&&b[tov[v]]==false)
          {
            q.push(tov[v]);
            dis[i][tov[v]]=dis[tov[v]][i]=dis[i][u]+dis[u][tov[v]];
            b[tov[v]]=true;
          }
            v=next[v];
        }
      }
  }
    for(int i=1;i<=n;i++)
    {
      ans=0;
      for(int j=1;j<=n;j++)
      {
        if(i!=j&&c[j]!=0)
        ans+=dis[i][j]*c[j];
      }
        if(ans<minx)minx=ans;
    }
      printf("%d",minx);//cout<<minx;
      exit(0);
      return 0;
}

后来发现2遍dfs即可

输入时建立链表,第一次dfs找其它点奶牛到1号点总共的花费(用sum[i]逐加实现),第二次dfs找其它点,

方程为ans[x]=ans[fa]-num[x]*cost+(SUM-num[x])*cost;意思是减去当前节点到父亲的花费,再加上其它点多走的花费。

代码:

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int head[100005];
int next[200005],tov[200005],w[200005];
int tot;long long SUM;
int c[100005];
int n;
long long ans[100005];
long long sum[100005],num[100005];
long long minx=10000000000000LL;
void go(int x,int y,int z)
{
  tov[++tot]=y;
  next[tot]=head[x];
  head[x]=tot;
  w[tot]=z;
}
void dp1(int x,int fa)
{
  num[x]=c[x];
  sum[x]=0;
  for(int i=head[x];i;i=next[i])
  {
    if(tov[i]==fa) continue;
    dp1(tov[i],x);
    num[x]+=num[tov[i]];
    sum[x]+=sum[tov[i]]+num[tov[i]]*w[i];
  }
}
void dp2(int x,int fa,int cost)
{
  if(!fa)
  ans[x]=sum[x];//第一次
  else ans[x]=ans[fa]-num[x]*cost+(SUM-num[x])*cost;//倒
  for(int i=head[x];i;i=next[i])
  {
    if(fa==tov[i]) continue;
    dp2(tov[i],x,w[i]);
  }
}
int main()
{
ios::sync_with_stdio(false);
freopen("A.in","r",stdin);
freopen("A.out","w",stdout);
cin>>n;
for(int i=1;i<=n;i++)
{
  cin>>c[i];
  SUM+=c[i];
}
for(int i=1;i<n;i++)
{
  int x,y,z;
  cin>>x>>y>>z;
  go(x,y,z);
  go(y,x,z);
}
  dp1(1,0);//算出ans[1]
  dp2(1,0,0);
  for(int i=1;i<=n;i++)
  {
    if(minx>ans[i])
    minx=ans[i];
  }
    cout<<minx;
    return 0;
}

原文地址:https://www.cnblogs.com/937337156Zhang/p/5812384.html