今日小结:昨天晚上看了一晚上二分,然后今天整理了二分博客,做了五六道二分题目,还做了一两道DP题目。

一. 今日完成的题目:

洛谷P1163,洛谷P1168,洛谷P1571,洛谷P1678,洛谷P1918,洛谷P4771,洛谷P1412,洛谷P3918。

二.

1. 当天完成题目数:8道。

2. 未完成6个题目的原因:

3. 复习的知识点:二分,DP。

4. 不会的题目:洛谷P2757。

整理的二分博客:

二分查找

三.

1. 洛谷P1163 银行贷款

题目描述

当一个人从银行贷款后,在一段时间内他(她)将不得不每月偿还固定的分期付款。这个问题要求计算出贷款者向银行支付的利率。假设利率按月累计。

输入输出格式

输入格式:

三个用空格隔开的正整数。

第一个整数表示贷款的原值,第二个整数表示每月支付的分期付款金额,第三个整数表示分期付款还清贷款所需的总月数。

输出格式:

一个实数,表示该贷款的月利率(用百分数表示),四舍五入精确到\(0.1\%\)

输入输出样例

输入样例#1:

1000 100 12

输出样例#1:

2.9

思路:二分答案,因为题目要求求的是利率,并且有一个坑点就是这里的利率不一定小于\(1\),那么,我们可以从\(0\)\(10\)开始二分,每次取中间值,如果这个中间利率是合法的,那么继续去合法的区间里面找,即左区间,否则去右区间。

代码:

#include<cstdio>
#define dl double
using namespace std;
dl a,b;
int c;
inline bool check(dl x) {
  dl lv=x/100,k=a;
  for(int i=1;i<=c;++i) {
    k+=lv*k;
    k-=b;
  }
  if(k<0) return 1;
  else return 0;
}
int main() {
  scanf("%lf%lf%d",&a,&b,&c);
  dl l=0,r=1000;
  while(r-l>=1e-5) {
    dl mid=(l+r)/2;
    if(check(mid)) l=mid;
    else r=mid;
  }
  printf("%0.1lf\n",l);
  return 0;
}

2. 洛谷P1168 中位数

题目描述

给出一个长度为\(N\)的非负整数序列\(A_i\)​,对于所有\(1 \leq k \leq (N + 1) / 2\),输出\(A_1, A_3, …, A_{2k - 1}\)的中位数。即前\(1,3,5,…\)个数的中位数。

输入输出格式

输入格式:

\(1\)行为一个正整数\(N\),表示了序列长度。

\(2\)行包含\(N\)个非负整数\(A_i (A_i ≤ 10^9)\)

输出格式:

\((N + 1) / 2\)行,第\(i\)行为\(A_1, A_3, …, A_{2k - 1}\)​的中位数。

输入输出样例

输入样例#1:

7
1 3 5 7 9 11 6

输出样例#1:

1
3
5
6

说明

对于\(20\%\)的数据,\(N \leq 100\)

对于\(40\%\)的数据,\(N \leq 3000\)

对于\(100\%\)的数据,\(N \leq 100000\)

思路:开一个小根堆和一个大根堆,然后把第一个数存到大根堆里,之后枚举\(i\)\(2\)\(n\),如果\(a[i]\)比大根堆堆顶元素大,就把\(a[i]\)加入小根堆,否则加入大根堆,这样就保证了所有小根堆里面的数都比大根堆里的大,在添加过程中,如果两个堆的元素个数差大于\(1\)了,那么就把元素数量大的堆的堆顶\(pop\)出来,放到另外一个堆里面,然后如果\(i\)是奇数,就输出元素多的堆的堆顶即可,即所求中位数。

代码:

#include<vector>
#include<cstdio>
#include<queue>
#include<iostream>
#define maxn 100001
using namespace std;
int n,a[maxn];
priority_queue<int>q1;
priority_queue<int,vector<int>,greater<int> >q2;
inline int abs(int x) {
  return x>=0?x:-x;
}
int main() {
  scanf("%d",&n);
  for(int i=1;i<=n;++i) scanf("%d",&a[i]);
  q1.push(a[1]),cout<<a[1]<<'\n';
  for(int i=2;i<=n;++i) {
    if(a[i]>q1.top()) q2.push(a[i]);
    else q1.push(a[i]);
    if(abs(q1.size()-q2.size())>1) {
        if(q1.size()>q2.size()) q2.push(q1.top()),q1.pop();
        else q1.push(q2.top()),q2.pop();
      }
    if(i&1) cout<<((q1.size()>q2.size())?q1.top():q2.top())<<'\n';
  }
  return 0;
}

3. 洛谷P1571 眼红的Medusa

题目描述

虽然\(Miss Medusa\)到了北京,领了科技创新奖,但是他还是觉得不满意。原因是,他发现很多人都和他一样获了科技创新奖,特别是其中的某些人,还获得了另一个奖项——特殊贡献奖。而越多的人获得了两个奖项,\(Miss Medusa\)就会越眼红。于是她决定统计有哪些人获得了两个奖项,来知道自己有多眼红。

输入输出格式

输入格式:

输入第一行,有两个数\(n,m\),表示有\(n\)个人获得科技创新奖,\(m\)个人获得特殊贡献奖。

第二行,\(n\)个正整数,表示获得科技创新奖的人的编号

第三行,\(m\)个正整数,表示获得特殊贡献奖的人的编号

输出格式:

输出一行,为获得两个奖项的人的编号,按在科技创新奖获奖名单中的先后次序输出。

输入输出样例

输入样例#1:

4 3
2 15 6 8
8 9 2

输出样例#1:

2 8

说明

对于\(60\%\)的数据,\(n \leq 1000,m \leq 1000\)

对于\(100\%\)的数据,\(n \leq 100000,m \leq 100000\),获得奖项的人的编号在\(2*10^9\)以内

输入数据保证第二行任意两个数不同,第三行任意两个数不同。

思路:这道题目思路比较容易想,如果要是用二分的话就用\(b\)去映射\(a\),即把b数组排好序之后去看看\(b\)数组中有的数值在\(a\)中是否也有,当然这道题也可以用\(map\)来做,毕竟\(map\)用来映射是真的好用,只是时间复杂度有点高,但是我是来练二分的,就把二分的也一起写了。

代码(map):

#include<cstdio>
#include<map>
#include<iostream>
#define maxn 100001
using namespace std;
int n,m,a[maxn],b[maxn];
map<int,bool>mp;
int main() {
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;++i) scanf("%d",&a[i]);
  for(int i=1;i<=m;++i) {scanf("%d",&b[i]);mp[b[i]]=1;}
  for(int i=1;i<=n;++i) {if(mp[a[i]]) cout<<a[i]<<" ";} 
  return 0;
}

代码(二分):

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 100001
using namespace std;
int n,m,a[maxn],b[maxn];
inline bool check(int x) {
  int l=1,r=m;
  while(l<=r) {
    int mid=(l+r)>>1;
    if(b[mid]==a[x]) return 1;
    if(b[mid]>a[x]) r=mid-1;
    else l=mid+1;
  }
  return 0;
}
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    for(int i=1;i<=m;++i) scanf("%d",&b[i]);
    sort(b+1,b+1+m);
    for(int i=1;i<=n;++i) if(check(i)) cout<<a[i]<<" ";
    return 0; 
}

4. 洛谷P1678 烦恼的高考志愿

题目背景

计算机竞赛小组的神牛\(V\)神终于结束了万恶的高考,然而作为班长的他还不能闲下来,班主任老\(t\)给了他一个艰巨的任务:帮同学找出最合理的大学填报方案。可是\(V\)神太忙了,身后还有一群小姑娘等着和他约会,于是他想到了同为计算机竞赛小组的你,请你帮他完成这个艰巨的任务。

题目描述

根据\(n\)位学生的估分情况,分别给每位学生推荐一所学校,要求学校的预计分数线和学生的估分相差最小(可高可低,毕竟是估分嘛),这个最小值为不满意度。求所有学生不满意度和的最小值。

输入输出格式

输入格式:

读入数据有三行,第一行读入两个整数\(m,n\)\(m\)表示学校数,\(n\)表示学生数。第二行共有\(m\)个数,表示\(m\)个学校的预计录取分数。第三行有\(n\)个数,表示\(n\)个学生的估分成绩。

输出格式:

输出数据有一行,为最小的不满度之和。

输入输出样例

输入样例#1:

4 3
513 598 567 689
500 600 550

输出样例#1:

32

说明

数据范围:对于\(30\%\)的数据,\(m,n \leq 1000\),估分和录取线 \(\leq10000\);对于\(100\%\)的数据,\(n,m \leq 100,000\),录取线 \(\leq1000000\)

思路:这道题坑我一小时……题意阐述不是特别详细,并且除了二分查找之外还要判断比所有录取分都小和比所有录取分都大的情况,还是……这道题如果用\(STL\)里的\(lower\)_\(bound\)函数的话就比较好做了,但是我还是坚持写了二分。

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 100001
using namespace std;
int ans,m,n,a[maxn],maxx,b[maxn],minn;
inline int abs(int x) {
  return x>=0?x:-x;
}
inline int find(int x) {
  int l=1,r=m;
  while(l<=r) {
    int mid=(l+r)>>1;
    if(a[mid]>=b[x]) r=mid-1;
    else l=mid+1; 
  }
  return l;
}
int main() {
  scanf("%d%d",&m,&n);
  for(int i=1;i<=m;++i) scanf("%d",&a[i]);
  for(int i=1;i<=n;++i) scanf("%d",&b[i]);
  sort(a+1,a+1+m);
  for(int i=1;i<=n;++i)
  if(a[find(i)]==0) ans+=b[i]-a[m];
  else {
    if(find(i)==1) ans+=a[1]-b[i];
    else ans+=min(abs(b[i]-a[find(i)]),abs(b[i]-a[find(i)-1]));
  }
  cout<<ans<<'\n';
  return 0;
}

5. 洛谷P1918 保龄球

题目描述

\(DL\) 算缘分算得很烦闷,所以常常到体育馆去打保龄球解闷。因为他保龄球已经打了几十年了,所以技术上不成问题,于是他就想玩点新花招。

\(DL\) 的视力真的很不错,竟然能够数清楚在他前方十米左右每个位置的瓶子的数量。他突然发现这是一个炫耀自己好视力的借口\(——\)他看清远方瓶子的个数后从某个位置发球,这样就能打倒一定数量的瓶子。

1 OOO

2 OOOO

3 O

4 OO

如上图,每个\(“O”\)代表一个瓶子。如果 \(DL\) 想要打倒 \(3\) 个瓶子就在 \(1\) 位置发球,想要打倒 \(4\) 个瓶子就在 \(2\) 位置发球。

现在他想要打倒 \(m\) 个瓶子。他告诉你每个位置的瓶子数,请你给他一个发球位置。

输入输出格式

输入格式:

输入文件名为 \(bowling.in\)

第一行包含一个正整数 \(n\),表示位置数。

第二行包含 \(n\) 个正整数,第 \(i\) 个数。表示第 \(i\) 个位置的瓶子数,保证各个位置的瓶子数不同。

第三行包含一个正整数 \(Q\),表示 \(DL\) 发球的次数。

第四行至文件末尾,每行包含一个正整数 \(m\),表示 \(DL\) 需要打倒 \(m\) 个瓶子。

输出格式:

输出文件名为 \(bowling.out\)

\(Q\) 行。每行包含一个整数,第 \(i\) 行的整数表示 \(DL\)\(i\) 次的发球位置。若无解,则输出 \(0\)

输入输出样例

输入样例#1:

5
1 2 4 3 5
2
4
7

输出样例#1:

3
0

说明

【数据范围】

对于 \(50\%\)的数据,\(1 \leq n,Q \leq 1000\)\(1 \leq ai,M \leq 10^5\)

对于 \(100\%\)的数据,\(1 \leq n,Q ≤ 100000\)\(1 \leq ai,M \leq 10^9\)

思路:自己写了题解

代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 100001
using namespace std;
int n,t,x,a[maxn];
struct node {
    int id,a;
}e[maxn];
inline int find(int x) {
  int l=1,r=n;
  while(l<=r) {
    int mid=(l+r)>>1;
    if(e[mid].a==x) return mid;
    if(e[mid].a>x) r=mid-1;
    else l=mid+1;
  }
  return 0;
}
inline bool cmp(node x,node y) {
  return x.a<y.a;
}
int main() {
  scanf("%d",&n);
  for(int i=1;i<=n;++i) scanf("%d",&e[i].a),e[i].id=i;
  sort(e+1,e+1+n,cmp);
  scanf("%d",&t);
  while(t--) {
    scanf("%d",&x);
    cout<<e[find(x)].id<<'\n';
  }
  return 0;
} 

6. 洛谷P4771 八百标兵奔北坡

题目背景

\(baingbaboom\)正在往北边跑\(!!!\)

题目描述

现在在一张\(N*M\)的地图上有\(K\)\(babingbaboom!!!\)对于一张地图上的点都有一个 \(h_{i,j}\) 来表示这个地方的高度。现在这些\(babingbaboom\)都想要跑到北边的一个山坡上。求出离每一个\(babingbaboom\)最近的靠北的山。

补充定义:

山:

山的周围没有比它更高的地方。(四联通)

在北边:

\(Babingbaboom\)的坐标为\(A(a,b)\),山的坐标为\(B(x,y)\),山在\(Babingbaboom\)的北边当且仅当\(dis_{A,B}==a-x\)

切比雪夫距离:

\(A(x1​,y1​)\) \(B(x2​,y2​)\)\(dis_{A,B}​=max(∣x1​−x2​∣,∣y1​−y2​∣)\)

输入输出格式

输入格式:

第1行三个正整数\(N,M,K\)。 第\(2-N+1\)行每行有M个正整数 \(h_{i,j}\) 。 第\(N+2-N+K+1\)行每行有两个正整数\(X_i,Y_i\)表示每一个\(babingbaboom\)的坐标。

输出格式:

\(K\)行。如果对于每一个\(babingbaboom\)存在这样的最近的山(切比雪夫距离),就输出这个\(babingbaboom\)到山的切比雪夫距离;否则输出\(“Pool Babingbaboom!”\)(不要引号!(我知道可怜的是\(Poor\),但是我就爱写\(Pool\)))

输入输出样例

输入样例#1:

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

输出样例#1:

Pool Babingbaboom!
Pool Babingbaboom!
1
2
0

说明

\(1 \leq N,M \leq 10^3\)\(1 \leq K \leq 10^5\)\(1 \leq h_{i,j} \leq 10^9\)

数据有梯度!

样例图片(星代表一个\(babingbaboom\),红色代表一个山):
(竖的是x,横的是y。画的时候没注意,很抱歉。)

思路:考虑一种\(DP\)的策略,用\(f[i][j]\)表示离\((i,j)\)点最近的山的距离,那么显然有山的地方,\(f\)数组值为\(0\),通过切比雪夫距离的定义,并根据题目条件,我们可以知道如果有离他距离为\(1\)的山的话,那一定在\((i-1,j-1),(i-1,j),(i-1,j+1)\)。我们又可以看出,当离它距离为\(2\)时,一定能包括其所有情况。 公式便可以很简单的推出
\(f[i][j]=min(f[i-1][j-1],f[i-1][j],f[i-1][j+1])+1\)

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#define maxn 1007
#define ll long long
using namespace std;
int n,m,k,map[maxn][maxn];
ll f[maxn][maxn];
int main() {
  scanf("%d%d%d",&n,&m,&k);
  for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) scanf("%d",&map[i][j]);
  memset(f,0x3f,sizeof(f));
  for(int i=1;i<=n;++i) for(int j=1;j<=m;++j) 
  if(map[i][j]>=map[i-1][j]&&map[i][j]>=map[i+1][j]&&map[i][j]>=map[i][j+1]&&map[i][j]>=map[i][j-1]) f[i][j]=0;
  else f[i][j]=min(f[i-1][j],min(f[i-1][j-1],f[i-1][j+1]))+1;
  for(int i=1,x,y;i<=k;++i) {
    scanf("%d%d",&x,&y);
    if(f[x][y]>1e9) printf("Pool Babingbaboom!\n");
    else printf("%lld\n",f[x][y]);
  }
  return 0;
}

7. 洛谷P1412 经营与开发

题目描述

\(4X\)概念体系,是指在\(PC\)战略游戏中一种相当普及和成熟的系统概念,得名自\(4\)个同样以\(“EX”\)为开头的英语单词。

\(eXplore\)(探索)

\(eXpand\)(拓张与发展)

\(eXploit\)(经营与开发)

\(eXterminate\)(征服)

——维基百科

今次我们着重考虑\(exploit\)部分,并将其模型简化:

你驾驶着一台带有钻头(初始能力值\(w\))的飞船,按既定路线依次飞过\(n\)个星球。

星球笼统的分为\(2\)类:资源型和维修型。(\(p\)为钻头当前能力值)

1.资源型:含矿物质量\(a[i]\),若选择开采,则得到\(a[i]*p\)的金钱,之后钻头损耗\(k\%\),即\(p=p*(1-0.01k)\)

2.维修型:维护费用\(b[i]\),若选择维修,则支付\(b[i]*p\)的金钱,之后钻头修复\(c\%\),即\(p=p*(1+0.01c)\)

注:维修后钻头的能力值可以超过初始值(你可以认为是翻修+升级)

金钱可以透支。

请作为舰长的你仔细抉择以最大化收入。

输入输出格式

输入格式:

第一行4个整数\(n,k,c,w\)

以下\(n\)行,每行2个整数\(type,x\)

\(type\)\(1\)则代表其为资源型星球,\(x\)为其矿物质含量\(a[i]\)

\(type\)\(2\)则代表其为维修型星球,\(x\)为其维护费用\(b[i]\)

输出格式:

一个实数(保留\(2\)位小数),表示最大的收入。

输入输出样例

输入样例#1:

5 50 50 10
1 10
1 20
2 10
2 20
1 30

输出样例#1:

375.00

说明

【数据范围】

对于\(30\%\)的数据 \(n \leq 100\)

另有\(20\%\)的数据 \(n \leq 1000\)\(k=100\)

对于\(100\%\)的数据 \(n \leq 100000\); \(0 \leq k,c,w,a[i],b[i] \leq 100\);保证答案不超过\(10^9\)

思路:由题意可知,当前的状态会影响到后面的最大值,即答案具有后效性,并且当前钻头的能力值和得到的金钱成正比,当前初始位置的能力值我们是知道的,那么我们干脆倒序枚举,更新f数组,最后f[1]*p即为答案。

代码:

#include<iostream>
#include<cstdio>
#define dl double
#define maxn 100007
using namespace std;
dl f[maxn],c,k,w[maxn],maxx;
int n,id[maxn],p;
int main() {
  scanf("%d%lf%lf%d",&n,&k,&c,&p);
  c=1+(c/100),k=1-(k/100);
  for(int i=1;i<=n;++i) scanf("%d%lf",&id[i],&w[i]);
  for(int i=n;i>=1;--i) {
    if(id[i]==1) f[i]=max(f[i+1],f[i+1]*k+w[i]);
    else f[i]=max(f[i+1],f[i+1]*c-w[i]);
  }
  printf("%0.2lf",p*f[1]);
  return 0;
}

8. 洛谷P3918 [国家集训队]特技飞行

题目背景

\(1.wqs\)爱好模拟飞行。

\(2.clj\)开了一家神犇航空,由于clj还要玩游戏,所以公司的事务由你来打理。

注意:题目中只是用了这样一个背景,并不与真实/模拟飞行相符

题目描述

神犇航空开展了一项载客特技飞行业务。每次飞行长\(N\)个单位时间,每个单位时间可以进行一项特技动作,可选的动作有K种,每种动作有一个刺激程度\(Ci\)。如果连续进行相同的动作,乘客会感到厌倦,所以定义某次动作的价值为(距上次该动作的时间)*\(Ci\),若为第一次进行该动作,价值为\(0\)。安排一种方案,使得总价值最大。

输入输出格式

输入格式:

第一行,两个数,\(N\)\(K\),如上所述;

第二行,\(K\)个正整数,表示\(K\)种动作的\(Ci\)值。

输出格式:

仅一行,一个整数,表示最大总价值。

输入输出样例

输入样例#1:

5 2
2 2

输出样例#1:

12

说明

对于\(10\%\)的测试数据,\(N \leq 20,K \leq 3\)

对于全部的测试数据,\(1 \leq N \leq 1000\)\(1 \leq K \leq 300\)\(0 \leq Ci \leq 1000\)

思路:考虑贪心的策略,因为某次动作的价值为(距上次该动作的时间)*\(Ci\),而\(Ci\)又是一个定值,所以当距离时间最长时这个动作的价值也就最大,那么题目要求求最大值,我们就要让拥有大的\(Ci\)的动作发挥大的价值,那么就先排一边序,然后枚举这k个动作,定义一个区间为1到n,每次缩小这个区间,乘以\(Ci\),此时这个动作发挥了它的最大作用,然后就做完了。

代码:

#include<cstdio>
#include<algorithm>
#define maxn 1001
using namespace std;
int ans,n,k,c[maxn];
int main() {
  scanf("%d%d",&n,&k);
  for(int i=1;i<=k;++i) scanf("%d",&c[i]);
  int l=1,r=n;
  sort(c+1,c+1+k);
  for(int i=k;i;--i) {
    ans+=(r-l)*c[i];
    l++,r--;
    if(l>=r) {
      printf("%d",ans);
      return 0; 
    }
  }
  printf("%d\n",ans);
  return 0;
}