这次创新班,我们做了这套题,最后做对了三题,感觉还不错。下面给出题解。
volume(时限0.1秒)
题目描述:
在一条数轴上有N头牛在不同的位置上,每头牛都计算到其它各头牛的距离。求这n*(n-1)个距离的总和。
数据范围
1<= N <= 10000。每头牛所在位置是一个范围在0到1,000,000,000之内的整数。
输入文件 volume.in
第一行:N
后面N行,每行一个整数,表示一头牛所有位置。
输出文件 volume.out
一个整数。
样例
输入 |
5 |
(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必须再进行优化。

根据上面的分析,我们可以得出如下算法:
【算法思路】
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 | 解释: Time 1 2 3 4 5 6 7 8 9 10 |
输出 | 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 | 样例说明: 删除3号或8号 节点,则分枝 最多有5个节点
|
输出 | 3 |
|
【解题思路】
很明显,这是一道水题,只要统计去掉那个点之后的所有连通块有没有大于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 |
|
输出 |
5 |
|
【题意分析】
这一题是本套题的难题,正解有两个:一个是贪心,一个是树形DP。
先上贪心的解:
【解题思路(贪心)】
其实贪心是很容易理解的,只要每次找出剩下的“高台”中花费的费用最少的那一段,把其需要的费用加到ans里面,只需要找高台的个数-k就可以了。
然后,整个思路就很清楚了。不过贪心使要注意细节,慢慢来。
【程序】
#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……