8.1集训
上午
考试
第一题
一句话题意:给你几堆火,给你几个罐子,这几个罐子分别对火可能会产生不同的影响,每次使用罐子的时候都必须对全部的火进行操作,\(1\)表示浇灭,\(0\)表示
无影响,\(-1\) 表示点燃
问最少用几次罐子可以把火浇灭,否则输出“-1”
一眼搜索
#include <bits/stdc++.h>
#define LL long long
#define debug
using namespace std;
const int N = 1e3+250;
queue<int>q;
map<LL, int> vis, dis;
int n, m;
int zuikaishideyangzi, moweideyangzi;
int a[250][250];
inline void shuruyijichushihua() {
cin >> n >> m;
for (int i = 1; i <= n; ++ i) zuikaishideyangzi = zuikaishideyangzi * 10 + 1;
dis[zuikaishideyangzi] = 0;
vis[zuikaishideyangzi] = 1;
for (int i = 1; i <= m; ++ i) {
for (int j = 1; j <= n; ++ j) {
cin >> a[i][j];
}
}
q.push(zuikaishideyangzi);
return;
}
inline void jiejuewenti() {
while (q.size()) {
int now = q.front(); q.pop();
if (now == moweideyangzi) return;
int tmp = n, shu[66], yuan[66];
int xianzaideyangzi = now;
while (tmp != 0) {
yuan[tmp] = shu[tmp] = xianzaideyangzi%10;
-- tmp;
xianzaideyangzi /= 10;
}
for (int i = 1; i <= m; ++ i) {
for (int j = 1; j <= n; ++ j) {
if (a[i][j] == 1) shu[j] = 0;
if (a[i][j] == -1) shu[j] = 1;
}
LL houlainayiwei = 0;
for (int j = 1; j <= n; ++ j) {
houlainayiwei = houlainayiwei*10 + shu[j];
shu[j] = yuan[j];
}
if (!vis.count(houlainayiwei)) {
vis[houlainayiwei] = 1;
dis[houlainayiwei] = dis[now] + 1;
q.push(houlainayiwei);
}
}
}
}
inline void shuchudaan() {
if (vis[moweideyangzi]) cout << dis[moweideyangzi];
else puts ("-1");
return;
}
inline int thestars() {
shuruyijichushihua();
jiejuewenti();
shuchudaan();
return 0;
}
int youngore = thestars();
signed main() {;}
第二题
一眼贪心,我们观察样例以及样例解释
Code
3 1
2 2
4 3
样例解释:
如果没人躺过来,需要27的面积。
我们只要让第1个人躺过来,就只需要21的面积!
开始揣测,为什么一定要让第一个人的\(w\)与\(h\)翻转?为什么不是别人?如果别人翻转会是什么样子的?
画图发现
在第一个人的\(w\)与\(h\)翻转前后,他们的\(maxh\)一直都是3,只是他们的\(sumw\)变小了,所以整体的体积变小了
在经过一段时间的臆想,我决定给他们按某种方式的排序,或许会好做一些
考虑:按\(h\)的高度大小排序?显然不合适,没有考虑全体,缺点太多
考虑:按\(w\)的大小排序?显然要被否定
于是继续臆想,最终决定按一个数的\(h\)与\(w\)的差值来从大到小来排序(他们的绝对值)
于是原来的样例变成这样:
Code
3 1(3-1=2)
4 3(4-3=1)
2 2(2-2=0)
我们肯定优先是对那些\(h\)与\(w\)差值大的组合进行翻转,因为只有这样才有可能对答案产生有效或者更大的贡献
这时候再来考虑:
\(h\)与\(w\)大小的关系
- \(w > h\)
- \(w < h\)
- \(w = h\)
显然对于第三种情况我们无论是否调换\(w\)\(h\)都是无影响的
分情况讨论第一种与第二种情况:
当\(w>h\)的时候,考虑\(w\)是否影响\(maxh\)
1.\(w <maxh\),此时交换\(w\)与\(h\)显然是优的,因为我们在\(maxh\)没有变动的情况下,是的\(sumw\)尽量的小
2.\(w>maxh\),这时候直接交换不一定是优的,因此我们需要计算一下交换之后的面积
如果\(面积_{当前算出来的} >面积_{以前算出来的}\) 显然不优,我们不去交换\(w\)与\(h\),反之则交换,并注意更新\(maxh\)与一些数组
当\(w < h\)的时候,考虑\(maxh\)与\(h\)的关系
1.\(h\neq maxh\),显然,我交换之后不影响你\(h\)的最大值,并且交换之后,我\(sumw\)还变大了,面积也会变大,显然不优,我们不去交换
2.\(h = maxh\),还要继续分析:
整体序列是否只有这么一个\(maxh\),那我们需要计算交换之后的面积,然后……(上文提过,此处省略)
如果不止一个\(maxh\),那我们交换之后显然吃亏,因为我们交换之后的\(h\)没有影响到\(maxh\)并且我的\(sumw\)也变大了
算法整体就结束了,不过仍然有一个细节要注意,因为题目规定,要求不超过\(\dfrac n 2\)的修改个数,所以每一次交换之后,都需要累加计数器,如果发现计数器大于\(\dfrac n 2\)就退出
底下是90分代码,还需要经过我再D一下
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#include <cmath>
//#define debug
using namespace std;
const int N = 1e5+66;
struct node {int h, w, cha;}a[N];
inline int cmp(node x, node y) {return x.cha > y.cha;}
int n, m, sum;
int sumw[N], maxh = -N;
int main () {
scanf ("%d", &n);
m = n>>1;
int dodododo = 0;
for (int i = 1; i <= n; ++ i) {
scanf ("%d%d", &a[i].w, &a[i].h);
a[i].cha = abs(a[i].w-a[i].h);
dodododo += a[i].w;
maxh = (maxh>a[i].h)?maxh:a[i].h;
}
sum = dodododo*maxh;
#ifdef debug
cout << "dodododo-->" << dodododo << ' ' << "maxh-->" << maxh << '\n';
cout << "sum-->" << sum << '\n';
#endif
sort (a+1, a+n+1, cmp);
#ifdef debug
for (int i = 1; i <= n; ++ i) cout << a[i].w << ' ' << a[i].h << '\n';
#endif
for (int i = 1; i <= n; ++ i) sumw[i] = sumw[i - 1] + a[i].w;
for (int i = 1; i <= n; ++ i) {
if (m == 0) break;
if (a[i].w > a[i].h) {
if (a[i].w <= maxh) {
sum = maxh*(sumw[i - 1]+a[i].h+sumw[n]-sumw[i]);
swap(a[i].w, a[i].h);
for (int j = i; j <= n; ++ j) sumw[j] = sumw[j - 1] + a[j].w;
-- m;
} else {
int ans = a[i].w*(sumw[i-1]+a[i].h+sumw[n]-sumw[i]);
if (ans < sum) {
sum = ans;
for (int j = i; j <= n; ++ j) maxh = a[i].w;
-- m;
} else continue;
}
}
if (a[i].w == a[i].h) {continue;}
if (a[i].w < a[i].h) {
if (maxh != a[i].h) {continue;}
if (maxh == a[i].h) {
int flag = 0;
for (int j = 1; j <= n; ++ j)
if (a[j].h == maxh) ++ flag;
flag -= 1;
if (flag) {continue;}
else {
int one = -N;
for (int j = 1; j < i; ++ j) one =(one>a[j].h)?one:a[j].h;
for (int j = i+1; j <= n; ++ j) one =(one>a[j].h)?one:a[j].h;
one = (one>a[i].w)?one:a[i].w;
int ans = one*(sumw[i-1]+a[i].h+sumw[n]-sumw[i]);
if (ans < sum) {
sum = ans;
maxh = one;
swap(a[i].w, a[i].h);
for (int j = i; j <= n; ++ j)sumw[j]=sumw[j - 1] + a[i].w;
} else {continue;}
}
}
}
}
cout << sum;
return 0;
}
第三题
一眼枚举
关于吃东西这道题,我们首先来分析一下题意
给你\(ABCD\)四堆数,告诉每堆数里面你必须选一个,再给你一个判定器n,问你有多少种方案选数?
最暴力的做法,四重for,可以拿到三十分
然后考虑优化,最简单的优化是知道前三个数,然后利用判定器减去前三数的和就可以得到第四个数
在这个题中,我们如果知道了\(A+B\)的和,就可以很容易的推出\(C+D\)的具体范围,
具体做法:枚举出\(A+B\)的所有组合,再枚举\(C+D\)的所有组合,
我们定义\(C1,C2\)数组
其中\(C1[i]=x\)就表示\(A+B\)所能凑出来的和的第\(i\)种方案,其价值为\(x\),同理
\(C2[i] = x\)表示\(C+D\)所能凑出来的和的第\(i\)种方案,其价值为\(x\)
在\(C2\)数组中找到能与\(C1\)数组匹配的数的下标,然后就可以累加答案
更具体的分析在代码中
下面是自己的代码:
#include <bits/stdc++.h>
#define int long long
#define LL long long
#define debug
using namespace std;
const int N = 1e8+66, M = 5e3+66, P = 2e7+2;
int n, cnt1, cnt2, maxn, res;
int A, B, C, D;
int a[M], b[M], c[M], d[M];
int c1[P], c2[N], f[N];
inline int thestars() {
cin >> n >> A >> B >> C >> D;
for (int i = 1; i <= A; ++ i) cin >> a[i];
for (int i = 1; i <= B; ++ i) cin >> b[i];
for (int i = 1; i <= C; ++ i) cin >> c[i];
for (int i = 1; i <= D; ++ i) cin >> d[i];
for (int i = 1; i <= A; ++ i) {
for (int j = 1; j <= B; ++ j) {
if (a[i] + b[j] <= n) {
++ f[a[i] + b[j]];
maxn = max (maxn, a[i] + b[j]);
}
}
}
for (int i = 0; i <= maxn; ++ i) {
while (f[i]) {
-- f[i];
c1[++ cnt1] = i;
}
}
maxn = 0;
for (int i = 1; i <= C; ++ i) {
for (int j = 1; j <= D; ++ j) {
if (c[i] + d[j] <= n) {
++ f[c[i] + d[j]];
maxn = max (maxn, c[i] + d[j]);
}
}
}
for (int i = 0; i <= maxn; ++ i) {
while (f[i]) {
-- f[i];
c2[++ cnt2] = i;
}
}
int cur;
for (cur = cnt2; cur >= 1; -- cur)
if (c1[1] + c2[cur] <= n)
break;
//我们在C2数组中寻找最后一个能与C1[1]相匹配的数的下标
//何为匹配?我加上你可以在判定器的范围之内
//所以应该倒序枚举,保证前边的一定可以匹配
for (int i = 1; i <= cnt1; ++ i) {
res += cur;
//cur表示的是在C2数组中最后一个能与当前的C1[i]匹配的下标
while (cur && c1[i + 1] + c2[cur] > n)
-- cur;
//cur这个计数器需要不断往前更新,一直在找,能与C1数组中匹配的下标
}
cout << res << '\n';
return 0;
}
int youngore = thestars();
signed main() {;}