武理校赛A题
ljw的剥削(思维 + map应用)
题意:
给定 a[],b[] 两个长度同为 n 的数组,经过一系列操作后,
使
p
=
∑
i
=
1
n
m
a
x
(
(
a
i
−
b
i
)
,
0
)
p=\sum_{i=1}^nmax((a_i-b_i),0)
p=∑i=1nmax((ai−bi),0) 最小。 (0 < i <= n)
有两种操作:
1:选择一个值 x,所有 b 数组中等于 x 的值全部变为原来的两倍
if (b[i] == x) b[i] * =2
2:选择一个值 x,所有 b 数组中等于 x 的值全部加上前一个值
if (b[i] == x) b[i] += b[i - 1], 记b[0] = 0
共有10次操作机会,不同的操作不能选择相同的 x,(即若在操作1中读入 x 0 x_0 x0,在操作2中就不允许读入 x 0 x_0 x0)
同一种操作可以选择相同的 x,但是不论选了几次 x,都只执行一次
注意输出格式 ( :巨麻烦不想写了直接复制 )
输出10行,每行两个数字,两个值之间用空格隔开,第一个数字为1或者2,1表示释放“我爱ljw”技能,2表示释放“ljw爱上我”技能,第二个数字表示该技能选择的非负整数。10行输出按照第二个数字从小到大的顺序输出。如果有多种满足条件的结果,请输出选择的10个非负整数的和最小的方案,若有多种满足条件的结果中10个非负整数的和相同,但选择的技能不同的方案,则输出10个技能的数字和(即第一个数字的和)最小的方案。
思路:
(x 为选择的值)
维护两个map,记录当 x == b[i] 时,选择操作1(或2)能够使整个式子的值减小多少。
map[key] = value 代表当选择 key 作为 x 时,能使整个式子的值减小 value 。
然后将 mp1 和 mp2 的合并排序,取出前10个 key 和标签再输出即可(若不足10个,只需先输出若干行 1 0)
排序按照 map 的 value 大的优先,若value相等,则mp1的值排在mp2前面。
注意:取出的十个 key 中不能有相同的值(即 mp1 和 mp2 中存在相同的key,只取排在前面的那一个 key)
细节:
遍历a[i],当a[i] > b[i]时,让两个map进行如下操作:
mp1[b[i]] += min(a[i] - b[i], b[i]);//原本受到a[i] - b[i]点伤害; 当a[i] > 2 * b[i] 时,减少b[i]点伤害,否则减少a[i] - b[i]点伤害(即不受伤)
mp2[b[i]] += min(a[i] - b[i], b[i - 1]);//同理
注意到数据较大(1e9),所以可以将 vis 数组改为 map<int, int> vis ,记录这个值是否选择过
完整代码:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<map>
#include<vector>
#include<math.h>
#include<queue>
using namespace std;
typedef long long ll;
typedef double dd;
typedef pair<int, int> pii;
typedef pair<dd, dd> pdd;
const int MAXN = 200010;
const int inf = 1e9 + 7;
int n, cnt;
int a[MAXN], b[MAXN];
map<int, int>vis; //开成数组结果 RE 了七八次,选择开成map
struct TY {
int num, val, id;//num: 操作 1 或 2,val: 使整个式子减小多少,id: 选择的 x 的值
bool operator<(const TY& a) {
if (val == a.val) return num < a.num;
return val > a.val;
}
}e[MAXN << 1], g[MAXN << 1];
map<int, int>mp1, mp2;
bool cmp(TY a, TY b)
{
return a.id < b.id;
}
int main()
{
//freopen("D:\\C\\acm\\2021\\ljwzbj0.in", "r", stdin);
//输入
int n;
scanf("%d", &n);
for (int i = 1;i <= n;i++) scanf("%d", &a[i]);
for (int i = 1;i <= n;i++) scanf("%d", &b[i]);
//遍历,操作
for (int i = 1;i <= n;i++)
{
if (a[i] <= b[i]) continue;
mp1[b[i]] += min(a[i] - b[i], b[i]);
mp2[b[i]] += min(a[i] - b[i], b[i - 1]);
}
//合并两个map
int cnt = 0;
for (auto& p : mp1) e[++cnt] = { 1,p.second,p.first };
for (auto& p : mp2) e[++cnt] = { 2,p.second,p.first };
sort(e + 1, e + 1 + cnt);
//去掉重复选择的值,g[]存最终答案
int cnt2 = 0;
for (int i = 1;i <= cnt;i++)
{
if (vis[e[i].id] == 1) continue;
g[++cnt2] = e[i];
vis[e[i].id] = 1;
}
//小于10个的话输出若干个 1 0
if (cnt2 < 10)
{
sort(g + 1, g + 1 + cnt2, cmp);//排序,符合输出格式
for (int i = 1;i + cnt2 <= 10;i++)
{
printf("1 0\n");
}
for (int i = 1;i <= cnt2;i++)
{
printf("%d %d\n", g[i].num, g[i].id);
}
}
//大于10个输出前十个
else
{
sort(g + 1, g + 11, cmp);
for (int i = 1;i <= 10;i++)
{
printf("%d %d\n", g[i].num, g[i].id);
}
}
}
总结:
比赛时看到此题题面长,表述复杂,队友也说是个毒瘤题,导致没有过多关注,赛后来看此题,思路不难,主要复杂在读懂题意,输出格式。
码完之后 RE 了七八发,最后发现是一开始 vis 开成了数组 vis[200010],改成 map 之后过了。
总的来说,比赛没做出来有点可惜。看来还是要加强阅读理解能力