2009南海区集训队初中选拔题1的解题报告

本文针对四道算法竞赛题目提供了解题思路与代码实现,包括计算牛群间距离总和、安排产奶牛的房间、分割无根树以及平整路面减少高台数量等问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这次创新班,我们做了这套题,最后做对了三题,感觉还不错。下面给出题解。

 

volume(时限0.1秒)

题目描述:

  在一条数轴上有N头牛在不同的位置上,每头牛都计算到其它各头牛的距离。求这n*(n-1)个距离的总和。

数据范围

   1<= N <= 10000。每头牛所在位置是一个范围在0到1,000,000,000之内的整数。

输入文件 volume.in

  第一行:N

  后面N行,每行一个整数,表示一头牛所有位置。

输出文件 volume.out

 一个整数。

样例

输入

5
1
5
3
2
4   


说明:

(1+2+3+4)+(4+3+2+1)

+(2+1+1+2)+(1+1+2+3)

+(3+2+1+1) = 40

输出

40

    

 

题目比较容易理解,就不多说了吧。我们可以用一个O(n^2)的方法,把全部的距离都统计一下,再输出就行了。但考虑到时限只有0.1秒,所以AC必须再进行优化。

2009南海区集训队初中选拔题1的解题报告 - 区庆亮 - oql
 
 为了方便进行前缀和,所以我们要先进行排序。

 根据上面的分析,我们可以得出如下算法:

【算法思路】

1,排序(拍完序后才能计算前缀和)

2,计算前缀和(区间[1,i]的和)和倒前缀和(区间[i,n]的和)

3,计算左右边的距离和,并累加。

4,输出

    按照上面的算法,就只用O(n)的时间来算出答案了。注意;要使用long long,不然会爆int范围。

 【程序】

#include<cstdio>
#include<algorithm>
using namespace std;
long long a[10010];
long long b[10010];
long long sum1[10010];
long long sum2[10010];
int n;
long long ans=0;
int main()
{
 freopen("volume.in","r",stdin);
 freopen("volume.out","w",stdout);
 
 scanf("%d",&n);
 for(int i=1;i<=n;i++) scanf("%I64d",&a[i]);
 
 sort(a+1,a+n+1);//排序
 
 for(int i=1;i<n;i++) b[i]=a[i+1]-a[i];//计算相邻两数的差
 
 sum1[1]=0;
 for(int i=2;i<=n;i++) sum1[i]=sum1[i-1]+b[i-1]*(i-1);//计算前缀和
 
 sum2[n]=0;
 for(int i=n-1;i>=1;i--) sum2[i]=sum2[i+1]+b[i]*(n-i);//计算前缀和
 
 for(int i=1;i<=n;i++) ans+=sum1[i]+sum2[i];//累加
 
 printf("%I64d",ans);
 return 0;
}

 
  
题目描述:
  有N头挑剔、害羞的奶牛,当它产奶时要在特定的时间段[A,B],并要求在单独的房间内。请帮助农夫FJ设计一个方案:
  1、最少的房间数。
  2、各头牛分配到哪个房间中。
数据范围
1<= N <= 50,000. 1<= A <= B <= 1,000,000。
输入文件 reserve.in
第一行:一个整数 N
下面N行:每行二个整数,表示一头牛的信息。
输出文件 reserve.out
  第一行:最少的房间数。
  下面有N行:表示对应输入的牛所分配的房间号。
样例

输入

5
1 10
2 4
3 6
5 8
4 7

解释:

Time    1  2 3  4   5 6   7 8  9 10
Stall 1 c1>>>>>>>>>>>>>>>>>>>>>>>>>>>
Stall 2 .. c2>>>>>> c4>>>>>>>>> .. ..
Stall 3 .. .. c3>>>>>>>>> .. .. .. ..
Stall 4 .. .. .. c5>>>>>>>>> .. .. ..

输出

4
1
2
3
2
4

 注:答案不唯一,只要输入一种即可。

 
题目的意思也很简单,直接进入正题。
我们可以把奶牛需要在这个时候开始产奶的时间A表示为在这个时间内,需要房间的个数比这个A-1时需要的个数+1(因为要多增一个房间);同理,结束产奶的时间B表示为在这个时间内,需要房间的个数比这个B-1时需要的个数-1(因为空了一个房间)。所以只要把时间不断增加,并用max来储存需要房间的最大值。最后输出max。
 【算法思路】

1,排序,把输入的数据全部从小到大排序,如相同则A比B前(因为这时一只奶牛还没有产玩奶,而另一只奶牛就已经开始产奶了,需要多增加一个房间)。O(n logn)

2,用循环增加计时器,统计max。 O(n)

3,输出max。 O(1)

 
【程序】

#include<cstdio>
#include<algorithm>
using namespace std;
struct data{
 int left,num,x;
}f[100010];

int sa,sb;
int n,fp;
int ans,tail,head;
int b[100010],c[100010];

int cmp(data f1,data f2)
{
 if(f1.num<f2.num) return 1;
 if(f1.num==f2.num && f1.left>f2.left) return 1;
 return 0;
}
int main()
{
 
 freopen("reserve.in","r",stdin);
 freopen("reserve.out","w",stdout);
 
 scanf("%d",&n);
 for(int i=1;i<=n;i++)
 {
  scanf("%d%d",&sa,&sb);
  fp++; f[fp].left=1; f[fp].num=sa;
  fp++; f[fp].left=0; f[fp].num=sb;
  f[fp-1].x=f[fp].x=i;
  
 }
 
 sort(f+1,f+fp+1,cmp);//排序
 
 for(int i=1;i<=fp;i++)
 {
  if(f[i].left)
  {
   if(tail==head) { ans++; c[f[i].x]=ans;  }//需要多加房间
   else
   {
    c[ f[i].x ]=b[head]; head++;
   }
  }
  else
  {
   b[tail]=c[f[i].x];
   tail++;
  }
 }
 
 printf("%d\n",ans);
 for(int i=1;i<=n;i++) printf("%d\n",c[i]);
 return 0;
}

 

  

treecut(时限0.2秒)

题目描述:

  有一个N个节点的无根树,各节点编号为1..N,现在要求你删除其中的一个点,使分割开的连通块中节点个数都不超过原来的一半多。 

数据范围

   1 <= N <= 10,000

输入文件 treecut.in

  第一行:一个整数N。

  后面有N-1行:每行两个整数 X 和 Y,表示一个边连接的两个节点号。

输出文件 treecut.out

   输出所有可能选择的点。如果有多个节点,按编号从小到大输出,每个一行。如果找不到这样的点,输出一行:"NONE".

样例

输入

10
1 2
2 3
3 4
4 5
6 7
7 8
8 9
9 10
3 8

样例说明:

删除3号或8号

节点,则分枝

最多有5个节点

 

输出

3
8         

    

 


 【解题思路】

很明显,这是一道水题,只要统计去掉那个点之后的所有连通块有没有大于n/2就可以了。统计时用DFS来做就行了。最后再把符合题目的节点排序并输出就行了。

【程序】

 

#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>

using namespace std;

vector <int> c[10010];//必 须 用 vector 拉 链,否 则 会 爆 空 间 。
int f[10010];
int t,n,fp;
int a,b;
int ans[10010];

int DFS(int x)
{
 f[x]=1;
 int sum=0,s=0;
 for(int i=0;i<c[x].size();i++)
 if(f[ c[x][i] ]==0)
 {
  
  t=DFS(c[x][i]);
  s=max(s,t);
  sum+=t;
  
 }
 
 if(max(s,n-sum-1)<=n/2) //是否符合要求 。
 {
  ans[fp]=x;
  fp++;
 }
 return sum+1;
 
}

int main()
{
 
 freopen("treecut.in","r",stdin);
 freopen("treecut.out","w",stdout);
 
 scanf("%d",&n);
 
 for(int i=1;i<n;i++)
 {
  scanf("%d%d",&a,&b);
  c[a].push_back(b);
  c[b].push_back(a);
 }
 
 DFS(1);//DFS
 
 sort(ans,ans+fp);//排序节点
 
 if(fp==0) printf("NONE");//没有一个节点符合要求 。
 else
 {
  for(int i=0;i<fp;i++) printf("%d\n",ans[i]);

  
 }
 return 0;
 
}



  

peaks(时限0.2秒)

题目描述:

  农夫John的农场有条路不平坦,特别是一些突高处让奶牛们很不愿走。于是John准备把路修整一下。

  路可以看成 N 段等长的小段连接在一起,每段的高度用一整数 H 表示,比如:

 

1 2 3 3 3 2 1 3 2 2 1 2

用形象的表示的话,就是下面所示:

 

 

* * *     *
  * * * * *   * * *   *
* * * * * * * * * * * *
1 2 3 3 3 2 1 3 2 2 1 2

  如果有高度相等的连续的一段(或一个),它的两边高度都比它低或是边界外,则可称为是一个“高台”,上面的样例数据中就有3个高台。

  现在John想用最少的代价使平台数不超过 K 个。注意,修理过程中路只能降低不能增高。每小段降低一个高度,则花费代价为 1 .

  如上例中,如果 K=1 的话,则最少花费代价为5. 表示如下:

 

 

 

* * *     -
  * * * * *  - - -   -
* * * * * * * * * * * *
1 2 3 3 3 2 1 1 1 1 1 1


数据范围

1 <= N <= 1000 ; 1 <= Hi <= 1,000,000 ; 1 <= K <= 25

输入文件 peaks.in

第一行:两个整数 N 和 K 

下面N行:第行一个整数 Hi ,表示连续的各小段路的高度数据。

输出文件 peaks.out

只一个整数,表示最小花费(即可看成最少要移走多少个"*")。

样例

输入

12 1
1
2
3
3
3
2
1
3
2
2
1
2

 

输出

5

 

 

【题意分析】

        这一题是本套题的难题,正解有两个:一个是贪心,一个是树形DP。

先上贪心的解:

【解题思路(贪心)】

        其实贪心是很容易理解的,只要每次找出剩下的“高台”中花费的费用最少的那一段,把其需要的费用加到ans里面,只需要找高台的个数-k就可以了。

 

2009南海区集训队初中选拔题1的解题报告 - 区庆亮 - oql

 

 然后,整个思路就很清楚了。不过贪心使要注意细节,慢慢来。

【程序】

 

#include<cstdio>
#include<algorithm>
#include<iostream>

using namespace std;

int i,m,k,s,l,sum,x,y,z,small,p;
int ans;
int n,a[1000011];

int main()
{
 freopen("peaks.in","r",stdin);
 freopen("peaks.out","w",stdout);
 
 scanf("%d%d",&n,&k);
 
 for(int l=1;l<=n;l++) scanf("%d",&a[l]);
 
 i=1;
 while(i<=n)//统计有多少个“高台”
 {
  s++;
  while(i<=n&&a[i+1]>=a[i]) i++;
  while(i<=n&&a[i+1]<=a[i]) i++;
 }
 
 
 while(s>k)//截掉(s-k)个 “高台”
 {
  s--;
  small=0x0fffffff;
  x=y=z=0;
  i=0;
  
  while(i<=n)//找到需要费用最少的“高台”
  {
   
   sum=0;
  
   l=i;
  
   while(i<=n&&a[i+1]>=a[i]) i++;
   while(i<=n&&a[i+1]<=a[i]) i++;
  
   p=max(a[i],a[l]);
  
   for(int k=l+1;k<i;k++)//统计这个高台的费用
   {
    if(a[k]>p) sum+=a[k]-p;
   }
  
   if(sum<small)//计算最小值
   {
    small=sum; x=l; y=i; z=p;
   }
  
  }
  
  ans+=small;//累加需要的费用
  
  for(int k=x+1;k<y;k++) //截掉高台
  {
   if(a[k]>z) a[k]=z;
  }
 }
 
 
 printf("%d",ans);
 return 0;
}


 

 

树形DP……

转载于:https://www.cnblogs.com/ouqingliang/p/9245329.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值