- 小麦亩产一千八
- 休息
- 军训
T1: 小麦亩产一千八
题目描述
“有了金坷垃,肥料一袋能顶两袋撒,小麦亩产一千八,吸收两米下的氮磷钾……”,话说HYSBZ(Hengyang School for Boys & Zy)学识渊博孩纸们一讲到粮食,都会想起印度那个著名的故事:国王要在第一个格子里放入一粒小麦,接下来的格子放入前面一个格子的两倍的小麦。这样所需小麦总数是巨大的,哪是不用金坷垃就能完成的任务?不过为了减轻国王的任务,那个下棋获胜的宰相换了一个要求:“我只需要你在棋盘外放一粒小麦,可以将其理解为第0 个格子,然后你需要在第一个格子里放入 p 粒小麦,之后==每一个格子放入前两个格子的小麦数之和的小麦,并且要满足第 a 个格子放 x 粒小麦,第 b 个格子放……”说到这,宰相突然发现自己说的满足第a 个格子放x 粒小麦的情况可能不存在……欺君可是大罪啊!国王看到宰相迟迟不说,自己也烦了!我自己来算!于是国王拜托你,让你算出第b 个格子应该放几粒小麦。当然,就算答案不存在,你也是要告诉国王的。
输入
该题有多组数据,请读到文件末结束。
对于每一组数据仅一行,3 个正整数 a , x , b ,分别表示第 a 个格子放了 x 粒小麦,以及你所需要计算的是第 b 个格子的小麦数量。
输出
对于每一次询问,仅1 个整数,为第 b 个格子的小麦数量,若宰相说的情况不存在,那么请输出-1。
样例
输入
1 1 2
3 5 4
3 4 6
12 17801 19
输出
2
8
-1
516847
样例解释
对于样例二,f[1]=2 时,能够满足f[3]=5,因此宰相没有撒谎,此时第5 个格子的小麦数应为f[4]=f[2]+f[3]=3+5=8.
数据范围
对于50%的数据:如果答案存在,那么p<=50
对于100%的数据:1<=数据组数<=10000,1<=a,b<=20, 数据保证如果答案存在,那么1<=p<=1000000.
思路
f[n] = f[n - 1] + f[n - 2] ,这熟悉的式子,当然首先应该想到斐波那契数列啦。然后,再试一试手推,然后就能得到小麦个数的通项公式:
g(x) = p * f[a] + f[a - 1]
(g(x) 为第 x 格的小麦数量, p 为第 1 格的小麦数量,f数组为斐波那契数列)
(在本题中,f[0] = 0, f[1] = 1)
因为a,b 都在1~20之间,所以可以先算出斐波那契数列前20项的值
f[1] = 1;
for (int i = 2; i <= 21; i ++)
f[i]=f[i - 1] + f[i - 2];
然后,就要开始验证了
if ((x - f[a - 1]) % f[a])
{
printf("-1\n");
continue;
}
p = (x - f[a - 1]) / f[a];
printf("%lld\n",f[b] * p + f[b - 1]);
p 一定是个整数,所以不能整除肯定不对,能整除的话,就把 p 算出来然后再用公式推 b 格的麦子数
附
因为有多组数据,所以可以用一个 while 一直读到文章末尾
while ((scanf("%lld %lld %lld", &a, &x, &b)) != EOF)
EOF :End Of File
另附
十年OI一场空,不开long long 见祖宗
T2: 休息
题目描述
休息的时候,可以放松放松浑身的肌肉,打扫打扫卫生,感觉很舒服。在某一天,某LMZ 开始整理他那书架。已知他的书有n 本,从左到右按顺序排列。他想把书从矮到高排好序,而每一本书都有一个独一无二的高度Hi。他排序的方法是:每一次将所有的书划分为尽量少的连续部分,使得每一部分的书的高度都是单调下降,然后将其中所有不少于2 本书的区间全部翻转。重复执行以上操作,最后使得书的高度全部单调上升。可是毕竟是休息时间,LMZ 不想花太多时间在给书排序这种事上面。因此他划分并翻转完第一次书之后,他想计算,他一共执行了多少次翻转操作才能把所有的书排好序。LMZ 惊奇地发现,第一次排序之前,他第一次划分出来的所有区间的长度都是偶数。
输入
第一行一个正整数n, 为书的总数。
接下来一行n个数,第i个正整数Hi,为第i 本书的高度。
输出
仅一个整数,为LMZ 需要做的翻转操作的次数。
样例
输入
6
5 3 2 1 6 4
输出
3
样例解释
第一次划分之后,翻转(5,3,2,1),(6,4)。之后,书的高度为1 2 3 5 4 6,然后便是翻转(5,4)即可。
思路
先选出最长的逆序对,然后把它们翻转,翻转之后,区间内都是有序的,然后的翻转只可能发生在区间链接处,接下来的问题就变成了求逆序对的个数
szsy知名巨佬zwt说:其实就是转了一次之后的逆序对个数加上2.
一道比较显然的结论题。
先说解法。首先,找出所有的单调下降序列,然后把他们全部翻转过来,记录翻转的次数。
之后求出翻转后序列的逆序对数,答案就是逆序对数与翻转次数之和。
证明如下:
在找出所有的单调下降序列并翻转后,可以发现,刚刚找到的区间内的元素都已经变得有序。
也就是说,只有两个小区间之间的数可以进行交换。
显然,我们只会交换两个降序的数。即逆序对。
因此答案为逆序对数与第一轮翻转次数之和。
代码实现
#include<bits/stdc++.h>
using namespace std;
#define N 100010
int n,a[N],tot,b[N];
long long ans;
void merge(int l,int r)//归并排序
{
if(r - l < 1)
return;
int mid = (l + r) / 2;
merge(l,mid);
merge(mid + 1,r);
int p1 = l, p2 = mid + 1, t = l;
while((p1 <= mid) && (p2 <= r))
{
if(a[p1] > a[p2])//找逆序对
{
b[t ++] = a[p2 ++];
ans += mid - p1 + 1;
}
else b[t ++] = a[p1++];
}
while(p1 <= mid)
b[t ++] = a[p1 ++];
while(p2 <= r)
b[t ++] = a[p2 ++];
for(int i = l; i <= r; i ++)
a[i] = b[i];
}
typedef struct
{
int l, r;
}block;
block p[N];
int main()
{
int cnt = 0;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
if(a[i] > a[i - 1])
{
p[cnt].r = i - 1;
p[++cnt].l = i;
}
}
p[cnt].r = n;
for(int i = 1; i <= cnt; i++)
reverse(a + p[i].l, a + p[i].r + 1), ans++;
merge(1, n);
printf("%lld",ans);
return 0;
}
T3: 军训
题目描述
HYSBZ 开学了!今年HYSBZ 有 n 个男生来上学,学号为1…n,每个学生都必须参加军训。在这种比较堕落的学校里,每个男生都会有 G[i] 个女朋友,而且每个人都会有一个欠扁值Hi。学校为了保证军训时教官不会因为学生们都是人生赢家或者是太欠扁而发生打架事故,所以要把学生们分班,并做出了如下要求:
1.分班必须按照学号顺序来,即不能在一个班上出现学号不连续的情况。
2.每个学生必须要被分到某个班上。
3.每个班的欠扁值定义为该班中欠扁值最高的那名同学的欠扁值。所有班的欠扁值之和不得超过Limit。
4.每个班的女友指数定义为该班中所有同学的女友数量之和。在满足条件1、2、3 的情况下,分班应使得女友指数最高的那个班的女友指数最小。
请你帮HYSBZ 的教务处完成分班工作,并输出女友指数最高的班级的女友指数。
输入数据保证题目有解。
输入
第一行仅2个正整数 n , Limit,分别为学生数量,欠扁值之和的上限。
接下来n 行每行2 个正整数 H[i],G[i],分别为学号为 i 的学生的欠扁值和女友数。
输出
仅1 个正整数,表示满足分班的条件下女友指数最高的班级的女友指数。
样例
输入
4 6
4 3
3 5
2 2
2 4
输出
8
样例解释
分班按照(1,2),(3,4)进行,这时班级欠扁值之和为4+2=6<=Limit,而女友指数最高的班级为(1,2),为8。容易看出该分班方案可得到最佳答案。
(一开始并没有看懂样例 。。。)
思路
题目中要求最小值的最大值,这种求最大最小双最值问题可以很容易想到用二分答案来做。那对于判断ans的check是否能用贪心求解?显然对于某一连续段而言,加入一个学生,可以增加其女友指数,但也有可能使得欠扁值增大,并没有什么能够保证正确性和最优性的贪心,那么可以考虑DP,则可得到转移方程如下:
dp[ i ] = min{ dp[ j ] + max( H[ k ] ) }
显然转移一次复杂度为O(n),总的时间复杂度为O(n^2),显然是会超时的.。
但是实际上并非所有的转移都是有效的,有的实际上是无用的 。
将 pos (位置)和 H (欠扁值)放在二维坐标轴上,当前 i 点,可以从直线 p 以后的点转移过来( p 表示满足二分的答案距 i 最远的位置),在k和 i 之间的点转移过来的话,实际上是无用的,在这一段的最优转移是从 k 处切开, k 以后的点一直到i为止分成一个班,因为 k 以后的点, i 是最大的,那么在这之间一定是不会改变其最大值的,那么在 k 以后选取点则是最优的,同理 j 也如此.那么可以用单调队列来维护一个H单调递减的序列,便可以达到减少转移个数实现优化的目的。
(其实可以进一步优化,用小根堆维护dp[j]+max{H[k]| j< k<=i },i<=j< i)
代码实现
注释真的很良心吧
#include<bits/stdc++.h>
using namespace std;
#define INF 1 << 30
#define maxn 20001
//一个附赠的快读模板
/*
int read()
{
char ch = getchar();
int ret = 0, flag = 1;
while(ch < '0'|| ch > '9')
{
if(ch == '-')
flag = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9')
{
ret = ret * 10 + ch - '0';
ch = getchar();
}
return ret * flag;
}
*/
/*
hint数组:每个人的欠扁值
girl数组:每个人的女友数
lim:最大限制欠扁值和
n:人数
sum_girl数组:这个人加其之前所有人的女友总和
que:用于维护一个hint单调递减的序列的单调队列
状态转移方程:dp[i] = min{dp[j] + max(hint[k])}
*/
int hint[maxn], girl[maxn], lim, n;
int dp[maxn], ans,sum_girl[maxn],que[maxn];
//全局变量不赋初值默认为零
bool check(int ans)//判断ans
{
int head = 1,tail = 0, pos = 1;//pos表示能够满足ans的分段临界位置
for(int i = 1; i <= n; i ++)
{
while((sum_girl[i] - sum_girl[pos - 1]) > ans)
pos ++;//临界位置向右移动
while((head <= tail) && (que[head] < pos))
head ++;//不能被转移到的点出队
while((head <= tail) && (hint[i] >= hint[que[tail]]))
tail --;//将i入队时,为维护队列单调性,H比i小的出队
que[++tail] = i;//i入队
dp[i] = dp[pos - 1] + hint[que[head]];//考虑特殊位置临界点处
for(int j = head; j < tail; j ++)
dp[i] = min(dp[i],dp[que[j]] + hint[que[j + 1]]);//使该点的dp值尽可能小
if(dp[i] > lim)
return false;//若最小dp值超过限制,ans不存在
}
return true;
}
//二分答案:仅限答案单调时使用!!!
//如果check(mid)找到了的话,说明最大的答案就是mid了
//继续往之前找,要是能找到,那就一定是更加ok的答案,那就继续更新
int find_ans(int l,int r)//从单人最多一直找到全在一班
{
int ans = 0;
while(l <= r)
{
int mid = (l + r) / 2;
if(check(mid))//= if(check(mid) = true)
{
ans = mid;//找到了,就把ans更新为mid
r = mid - 1;//再往前找找
}
else//没找到
l = mid + 1;//那就只好往后找找啦
}
return ans;
}
int main()
{
scanf("%d %d", &n, &lim);
int l = 0, r = 0;//记得初始化!!!
for(int i = 1; i <= n; i ++)
{
scanf("%d %d", &hint[i], &girl[i]);
sum_girl[i] = sum_girl[i - 1] + girl[i];//计算总和
l = max(l,girl[i]);//l最后变为单人女友最多的数量
r += girl[i];//r最后为全部女友数
}
int ans = find_ans(l,r);//二分答案
printf("%d",ans);
return 0;
}