比赛前一天刷水题

导读

马上就要 csp 了,由于去年的 白席 丢分,好渴鹅十分心惊胆战。于是打开洛谷,打开入门与面逝,刷起了水题。刷完了水题,好渴鹅 又双叒写篇博客压压惊。

B3638 T1 三角形面积

题目背景

请尽量在 20min 之内写完题目。这是指「写代码」的时间;「读题」时间不计算在内。

题目描述

给定平面直角坐标系上的三个整点 \(A, B, C\) 的坐标,求其围成的三角形面积。

数据保证答案一定是整数。所以如果你采用了浮点数来计算,请四舍五入到整数


两点之间的距离公式: \((x_1, y_1), (x_2, y_2)\) 之间的距离是 \(\sqrt{(x_1 - x_2)^2 + (y_1 - y_2)^2}\)

海伦公式: 若三角形的边长为 \(a, b, c\),则三角形的面积是 \(\sqrt{s(s-a)(s-b)(s-c)}\),其中 \(s=\frac12(a+b+c)\).

输入格式

共三行,每行表示一个三角形上的点。
每行包含两个正整数,表示点的坐标,形式为 x y

输出格式

共一行,一个整数,表示三角形面积。

样例 #1

样例输入 #1

10 20
30 40
50 50

样例输出 #1

100

提示

样例解释

可以通过海伦公式计算面积。方法如下。

\(AB\) 距离:\(\sqrt{(30 - 10)^2 + (40 -20)^2} \approx 28.284\)
\(BC\) 距离:\(\sqrt{(50-30)^2 + (50-40)^2} \approx 22.361\)
\(AC\) 距离:\(\sqrt{(50-10)^2+(50-20)^2}\approx 50\)

应用海伦公式,\(s \approx (28.284 + 22.361 + 50) / 2 \approx 50.323\)
求出近似面积: \(\sqrt{s(s-a)(s-b)(s-c)} \approx \sqrt{10016.80} \approx 100.08\),故答案为 \(100\)

数据规模与约定

对于 \(100\%\) 的数据:每个点的 \(x, y\) 坐标值一定在 \([1, 200]\) 之内,均为整数;答案一定为正整数。

解法

过于智慧的题目,他明明都把公式写到你脸上了,你还要啥 自行车 WA?然鹅,好渴鹅不仅多次本地 WA,交上去还因为 y1 CE 了一发,说出来丢死人。 不过还是很简单的,注意四舍五入使用 round 函数,并使用 static_cast<int> 进行 int 的转换。

#include <iostream>
#include <cmath>

using namespace std;

double x1, y_, x2, y2, x3, y3, a, b, c, p;

int main() {
  cin >> x1 >> y_ >> x2 >> y2 >> x3 >> y3;
  a = sqrt((x1 - x2) * (x1 - x2) + (y_ - y2) * (y_ - y2));
  b = sqrt((x1 - x3) * (x1 - x3) + (y_ - y3) * (y_ - y3));
  c = sqrt((x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3));
  p = (a + b + c) / 2;
  cout << static_cast<int>(round(sqrt(p * (p - a) * (p - b) * (p - c)))) << '\n';
  return 0;
}

P2347 [NOIP1996 提高组] 砝码称重

题目描述

设有 \(1\mathrm{g}\)\(2\mathrm{g}\)\(3\mathrm{g}\)\(5\mathrm{g}\)\(10\mathrm{g}\)\(20\mathrm{g}\) 的砝码各若干枚(其总重$ \le 1000$),可以表示成多少种重量?

输入格式

输入方式:\(a_1 , a_2 ,a_3 , a_4 , a_5 ,a_6\)

(表示 \(1\mathrm{g}\) 砝码有 \(a_1\) 个,\(2\mathrm{g}\) 砝码有 \(a_2\) 个,…,\(20\mathrm{g}\) 砝码有 \(a_6\) 个)

输出格式

输出方式:Total=N

\(N\) 表示用这些砝码能称出的不同重量的个数,但不包括一个砝码也不用的情况)

样例 #1

样例输入 #1

1 1 0 0 0 0

样例输出 #1

Total=3

提示

【题目来源】

NOIP 1996 提高组第四题

解法

正常解法

也是灰常的简单,直接背包。设 \(dp_i\)\(dp_i\in 0,1\))表示是否能够正好表示出 \(i\) 的重量,然后对于第 \(i\) 物品,进行 \(a_i\)\(01\) 背包即可,并附上初始状态 \(dp_0=1\)。状态转移方程:\(dp_i=\max(dp_i,dp_{(i-w_i)})\)

时间复杂度:\(\mathcal O(\sum\limits_{i=1}^{n} a_i\times k)\),本题 \(k=1000\)

#include <iostream>
#include <algorithm>

using namespace std;

const int kMaxN = 7, kMaxHke = 1005;

int a[kMaxN], w[kMaxN] = {0, 1, 2, 3, 5, 10, 20};
int n = 661980 / 1e5;
bool dp[kMaxHke];

int main() {
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  dp[0] = 1;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= a[i]; j++) {
      for (int k = kMaxHke; k >= w[i]; k--) {
        dp[k] |= dp[k - w[i]];
      }
    }
  }
  cout << "Total=" << count(dp + 1, dp + 1001, 1) << '\n';
  return 0;
}

二进制优化

我们知道,\(\lceil \log_2 n \rceil\) 位数的二进制可以表示 \([1,n]\) 的值,那么我们是否可以使用二进制来表示物品的数量呢?当然可以!对于任意的一个 \(i\),我们对 \(i\) 进行 \(2^j\) 的分解,并存入数组内,记住新的重量数组需要乘上系数 \(j\)。然后对于剩下来的 \(a_i\),我们单独存入数组。这样子 \([1,a_i]\) 当中的数量都可以表示出来,最后再来一遍 01 背包就行了。

时间复杂度:\(\mathcal O(\sum\limits_{i=1}^{n}\lfloor\log_2 a_i\rfloor \times k),k=1000\)

#include <iostream>
#include <algorithm>

using namespace std;

const int kMaxN = 7, kMaxHke = 1005;

int a[kMaxN], f[kMaxN] = {0, 1, 2, 3, 5, 10, 20};
int w[kMaxHke], n = 661980 / 1e5, l;
bool dp[kMaxHke];

int main() {
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
  }
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= a[i]; j <<= 1) {
      w[++l] = f[i] * j, a[i] -= j;
    }
    a[i] && (w[++l] = f[i] * a[i]);
  }
  dp[0] = 1;
  for (int i = 1; i <= l; i++) {
    for (int j = 1000; j >= w[i]; j--) {
      dp[j] |= dp[j - w[i]];
    }
  }
  cout << "Total=" << count(dp + 1, dp + 1001, 1) << '\n';
  return 0;
}

P2837 [USACO08FEB] Dining Cows B

题目描述

为了避免餐厅过分拥挤,FJ要求奶牛们分 \(2\) 批就餐。每天晚饭前,奶牛们都会在餐厅前排队入内,按 FJ 的设想,所有第 \(2\) 批就餐的奶牛排在队尾,队伍的前半部分则由设定为第1批就餐的奶牛占据。由于奶牛们不理解FJ的安排,晚饭前的排队成了一个大麻烦。 第 \(i\) 头奶牛有一张标明她用餐批次 \(D_i(1 <= D_i <= 2)\) 的卡片。虽然所有 \(N\) 头奶牛排成了很整齐的队伍,但谁都看得出来,卡片上的号码是完全杂乱无章的。 在若干次混乱的重新排队后,FJ找到了一种简单些的方法:奶牛们不动,他沿着队伍从头到尾走一遍,把那些他认为排错队的奶牛卡片上的编号改掉,最终得到一个他想要的每个组中的奶牛都站在一起的队列,例如 112222111122 的时候,FJ会把整个队列弄得只有1组奶牛(比方说,1111222)。 你也晓得,FJ是个很懒的人。他想知道,如果他想达到目的,那么他最少得改多少头奶牛卡片上的编号。所有奶牛在FJ改卡片编号的时候,都不会挪位置。

输入格式

  • \(1\) 行: \(1\) 个整数;
  • \(N\)\(2..N+1\) 行: 第 \(i+1\) 行是 \(1\) 个整数,为第i头奶牛的用餐批次 \(D_i\)

输出格式

  • 一行: 输出 \(1\) 个整数,为 FJ 最少要改几头奶牛卡片上的编号,才能让编号变成他设想中的样子。

样例 #1

样例输入 #1

7
2
1
1
1
2
2
1

样例输出 #1

2

样例 #2

样例输入 #2

5
2
2
1
2
2

样例输出 #2

1

提示

\(1 \le N \le 30000\)

解法

洛谷说这道题的题目标签是 动态规划,dp 递推,我真 文明 服了,这明明就是一道前缀和的裸体 裸题好不好(输入法真可爱)。\(p1_i\) 就表示 \(\sum\limits_{i=1}^{n} (a_i=1)\);同理,\(p2_i\) 就表示 \(\sum\limits_{i=1}^{n}(a_i=2)\)。最后输出下面这个玩意就行了:

\[\max\limits_{i=0}^{n}(p2_i+p1_n-p1_i) \]

#include <iostream>

using namespace std;

const int kMaxN = 30005;

int a[kMaxN], p1[kMaxN], p2[kMaxN], n, ans = 114514;

int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    p1[i] = p1[i - 1] + (a[i] == 1);
    p2[i] = p2[i - 1] + (a[i] == 2);
  }
  for (int i = 0; i <= n; i++) {
    ans = min(ans, p2[i] + p1[n] - p1[i]);
  }
  cout << ans << '\n';
  return 0;
}

树状数组

既然都有区间求和了,那么,树状数组,启动!咸得没事干。

#include <iostream>

using namespace std;

const int kMaxN = 30005;

int a[kMaxN], t1[kMaxN], t2[kMaxN], n, ans = 114514;

void update(int *t, int x, int y) {
  x <= n && (update(t, x + (x & -x), y), t[x] += y);
}

int query(int *t, int x) {
  return x ? t[x] + query(t, x - (x & -x)) : 0;
}

int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i];
    update(t1, i, a[i] == 1);
    update(t2, i, a[i] == 2);
  }
  for (int i = 0; i <= n; i++) {
    ans = min(ans, query(t2, i) + query(t1, n) - query(t1, i));
  }
  cout << ans << '\n';
  return 0;
}

P8707 [蓝桥杯 2020 省 AB1] 走方格

题目描述

在平面上有一些二维的点阵。

这些点的编号就像二维数组的编号一样,从上到下依次为第 \(1\) 至第 \(n\) 行,从左到右依次为第 \(1\) 至第 \(m\) 列,每一个点可以用行号和列号来表示。

现在有个人站在第 \(1\) 行第 \(1\) 列,要走到第 \(n\) 行第 \(m\) 列。只能向右或者向下走。

注意,如果行号和列数都是偶数,不能走入这一格中。

问有多少种方案。

输入格式

输入一行包含两个整数 \(n\)\(m\)

输出格式

输出一个整数,表示答案。

样例 #1

样例输入 #1

3 4

样例输出 #1

2

提示

\(1\le n,m\le30\)

蓝桥杯 2020 第一轮省赛 A 组 G 题(B 组 H 题)。

解法

过于 可爱 的 dp 题。设 \(dp_{(i,j)}\) 表示当前走到 \((i,j)\),一共有多少方案。由于每次只能往右边或者下面走,那我们写收只因型的就得跟人家反着干,状态转移方程就是:\(dp_{(i,j)}=dp_{(i-1,j)}+dp_{(i,j-1)}(\max(j\bmod 2,i\bmod 2) =1,dp_{(i,j)}= 0)\)

#include <iostream>

using namespace std;

const int kMaxN = 35;

int dp[kMaxN][kMaxN], n, m;

int main() {
  cin >> n >> m;
  dp[1][1] = 1;
  for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= m; j++) {
      ((i & 1) != 0 || (j & 1) != 0) && !dp[i][j] && (dp[i][j] = dp[i - 1][j] + dp[i][j - 1]);
    }
  }
  cout << dp[n][m] << '\n';
  return 0;
}

P2008 大朋友的数字

题目背景

在 NOIP2013 的赛场上,常神牛华丽丽的手残了,小朋友的数字一题只得了 \(10\) 分。于是,他要恶搞一下这道题。

题目描述

有一批大朋友(年龄 \(15\) 岁以上),他们每人手上拿着一个数字,当然这个数字只有 \(1\) 位,也就是 \(0\)\(9\) 之间。每个大朋友的分数为在他之前的最长不下降子序列中所有数之和。(这个序列必须以它作为结尾!)如有多个最长不下降子序列,那么取编号字典序最小的。现在告诉你有 \(n\) 个大朋友,以及他们各自的数字,请你求出他们每个人的分数。

输入格式

第一行,\(1\) 个数 \(n\)

第二行,\(n\) 个数,分别表示每个人的数字。

输出格式

一行,\(n\) 个数,分别表示每个人的分数。

样例 #1

样例输入 #1

5
1 2 5 3 4

样例输出 #1

1 3 8 6 10

样例 #2

样例输入 #2

5
1 7 5 9 6

样例输出 #2

1 8 6 17 12

提示

【样例解释 \(1\)

五个人分数分别为 \((1),(1+2),(1+2+5),(1+2+3),(1+2+3+4)\)

【样例解释 \(2\)

五个人分数分别为 \((1),(1+7),(1+5),(1+7+9)\) (还有一个 \((1,5,9)\)),\((1+5+6)\)

【数据规模】

对于 \(50\%\) 的数据,\(1\le n\le 500\)

对于 \(80\%\) 的数据,\(1\le n\le 10^3\)

对于 \(100\%\) 的数据,\(1\le n\le 10^4\)

解法

过于简单的 dp 题。一看题目,哟,这不最长上升子序列吗?哐哐哐敲键盘,提交——WA?我 爱你!好吧这题其实是最长不下降子序列,智慧的好渴鹅过于智慧。设 \(dp_i\) 表示以 \(i\) 结尾的最长上升子序列的长度,以及这条最长上升子序列的和。对于任意一个 \(j\)\(1\le j<i\)),如果 \(a_j\le a_i\) 并且 \(dp1_j+1>dp1_i\)\(dp_i=(dp1_j+1,dp2_j+a_i)\)。对于每一个 \(i\)\(dp2_i\) 即是答案。

#include <iostream>

using namespace std;

const int kMaxN = 1e4 + 5;

int a[kMaxN], n;
pair<int, int> dp[kMaxN];

int main() {
  cin >> n;
  for (int i = 1; i <= n; i++) {
    cin >> a[i], dp[i] = {1, a[i]};
    for (int j = 1; j < i; j++) {
      a[j] <= a[i] && dp[j].first + 1 > dp[i].first && (dp[i] = {dp[j].first + 1, dp[j].second + a[i]}, 0);
    }
    cout << dp[i].second << ' ';
  }
  return 0;
}

后记

刷水题结束了,马上就要 csp 了,祝大家 rp++。希望每一个 OIer 都能自信地喊出那句话:

posted @ 2023-10-20 14:59  haokee  阅读(75)  评论(0)    收藏  举报