目录
hihocoder 编程训练赛76
题目1 : 切割木棒
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
小Hi有N根木棒,其中第i根的长度是Li。
现在小Hi会进行多轮切割,每轮行动中小Hi会选出其中最短的木棒,不妨设其长度为X。然后将所有长度为X的木棒移除。再将剩余的木棒都切掉X长度。如此反复直到木棒一根也不剩。
例如一开始木棒的长度是[2, 1, 3, 1],则第一轮之后1被移除,余下的又被切短为:[1, 2];第二轮之后余下的是[1];第三轮之后就一根也不剩了。
给定N根木棒,请你计算每轮切割过后,剩余木棒的长度之和是多少。
输入
第一行包含一个整数N。
第二行包含N个整数,L1, L2, L3, ... LN。
对于50%的数据,1 ≤ N ≤ 1000
对于100%的数据,1 ≤ N ≤ 100000 1 ≤ Li ≤ 1000000
输出
输出每轮之后剩余木棒的长度
样例输入
4
2 1 3 1
样例输出
3
1
0
题意分析:
1.题是什么?
n个长度分别为ai的木棒,现在我们每次操作将所有木棒剪短当前最短的木棒的长度,剪为0视为这根木棒已经没有了,
也就是如果现有木棒4根分别为4,4,6,8;
第一次剪短将每根木棒剪短4,剩余两根木棒长度为2,4,
第二次剪短2,剩余一根木棒2,
第三次剪短2,剩余0根木棒,
我们要输出的答案就是每次被剪短之后剩余的所有木棒的总长度.
2.思路
最近刚做了一个计数排序的博客,因为每次剪都是剪最短的并且木棒最长1000000,刚好能开数组做计数,故而我首先想到把这所有木棒用一个1000000大小的数组做计数存储,然后从0到1000000的遍历,每遍历到一个存在值的点就进行一遍剪操作,而最后的答案到底该加多少则由逆向思维处理,先把所有木棒总长度叠加处理,然后每次操作剪掉了多少就减去多少,剩下的就是本次剪完的总长度了;
ac代码
#include <stdio.h>
typedef long long ll;
const int maxm=1000001;
int count[maxm];
int main(){
ll n;
scanf("%lld",&n);
for(int i=0;i<maxm;i++) count[i]=0;
ll t,sum=0;//sum表示当前木棒总长度
for(int i=0;i<n;i++){
scanf("%lld",&t);
count[t]++;
sum+=t;
}
ll num=n; //当前总共多少个木棒
ll ans[n],ansnum=0,nowreduce=0; //nowreduce表示这些木棒曾被削断多少
for(int i=0;i<maxm;i++){
if(count[i]){
sum-=(i-nowreduce)*num;
ans[ansnum++]=sum;
nowreduce+=(i-nowreduce);
num-=count[i];
}
}
for(int i=0;i<ansnum;i++) printf("%lld\n",ans[i]);
return 0;
}
题目2 : 取卡片游戏
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
小Hi和小Ho在玩一种游戏。他们面前有N张卡片,每张卡片上都写有一个整数,依次是A1, A2, ... AN。
他们可以轮流从中取卡片。每次可以取出1、2或3张卡片(不能不取),取走的卡片不能被再次拿取,直到最后一张卡片被取走。
这时小Hi和小Ho各自取走的卡片上的整数之和就是他们的得分。
假设小Hi和小Ho都足够聪明,都会选择使自己得分最高的策略。请你计算先手的小Hi最多能得几分。
输入
第一行包含一个整数N。
第二行包含N个整数A1, A2, A3, ... AN。
1 ≤ N ≤ 100000
-1000000 ≤ Ai ≤ 1000000
输出
输出小Hi最多的得分
样例输入
3
-1 1 2
样例输出
3
题意分析:
1.题是什么
相当于给你和他n个数字,取值范围为-1000000到1000000,现在你们每次可以拿走三个或两个或一个数字,随便拿哪个,你先拿,问你你最大能拿到和为多少.
2.思路
一个明显的博弈论问题,首先就该想到dp(虽然我当时脑袋抽了没想到),想到dp自然该确定dp[i]的意义以及递推公式,而我设计的这个dp中dp[i]存的是在抽取完前n张卡牌之后先手抽取的人所能达到的最大分值,先对这n个数字排序是自然的,然后
递推公式为:dp[i]=std::max(sum[i-1]-dp[i-1]+a[i],std::max(sum[i-2]-dp[i-2]+a[i]+a[i-1],sum[i-3]-dp[i-3]+a[i]+a[i-1]+a[i-2]));
因为是两个人轮流拿,故而我做了一个sum数组,sum[i]表示前i张包括第i张的和为多少,这样sum[i-1]-dp[i-1]就能知道那一次选择我最优能拿到多少了,毕竟dp[i-1]存的是另一个人的最优选择.故而dp[i]自然就是拿一张两张和三张之中的最优答案,取最大值就好.
ac代码
#include <iostream>
#include <algorithm>
#include <stdio.h>
using namespace std;
typedef long long ll;
const int maxn=100001;
const int maxm=1000001;
ll a[maxn];
ll dp[maxn]; //dp[i]存的是在抽取完前n张卡牌之后先手抽取的人所能达到的最大分值
ll sum[maxn];
int main(){
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
sort(a+1,a+n+1);
for(int i=0;i<=n;i++) dp[i]=0;
sum[0]=0;
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
dp[0]=0;
dp[1]=a[1];
dp[2]=a[2];
dp[3]=std::max(a[2]+a[3],a[1]+a[2]+a[3]);
for(int i=4;i<=n;i++){
dp[i]=std::max(sum[i-1]-dp[i-1]+a[i],std::max(sum[i-2]-dp[i-2]+a[i]+a[i-1],sum[i-3]-dp[i-3]+a[i]+a[i-1]+a[i-2]));
}
printf("%lld\n",dp[n]);
return 0;
}