注意到选定的操作数可以少于 \(m\),因此所有对乘积有负贡献的操作直接扔掉(在本题中,只有满足 \(b_i < a_i\) 的赋值操作对乘积是负贡献的)。
假设我们框定了选择的操作集合,如何决定顺序?
先做所有赋值操作,再做所有加操作,再做所有乘操作是最优的,而每种类型操作内部的顺序无所谓。
对每个数而言,只进行一次赋值操作就行了。那么对每个数只需要保留对其赋的值最大的那个操作。这样以来,对每个数的赋值都是相对于原数而言的,那么可以直接转换成对第 \(i\) 个数加 \(b_j - a_i\) 的加法操作(假设 \(j\) 是这个赋值操作的编号)。
现在变成只有加和乘的操作了。
假设我们给第 \(x\) 个数分配了 \(p\) 个加法操作,我们一定是取对 \(x\) 的所有加法操作中的前 \(p\) 大。因此考虑对每个数的加法操作降序排列,我们一定优先选择对 \(x\) 加最大的那个操作,再选择对 \(x\) 加第二大的那个操作……
这样以来,对每个数的加法操作,也是相对于对这个原数再进行了比这个加法操作加的更多的所有加法操作后的数(不妨设为 \(s\))而言的,于是进行这个操作之前这个数的具体值就确定是 \(s\) 了。假设这个加操作编号为 \(j\),则加上 \(b_j\) 可以直接转化为乘上 \(\dfrac{s + b_j}{s}\) 的乘法操作。
最后只有乘操作,选择乘操作的前 \(m\) 大即可。当然,最开始扔掉负贡献操作后剩余非负贡献操作数量可能不超过 \(m\),这个时候全选即可。
注意到加法转乘法中可能会出现分数。观察 \(\dfrac{s + b_j}{s}\) 这个分式,\(s\) 为 \(n \times b\) 量级,最多达到 \(10^{11}\)。所以可以直接按照浮点数记录比较大小(精度 \(10^{-11}\) 可以接受)。
或者按照 \(\dfrac{a}{b} < \dfrac{c}{d} \iff ad < bc\) 比较,这样计算中间值达到 \(10^{22}\),无法接受。可以考虑给每个比例减个 \(1\),这样每个加转乘的比例式变成 \(\dfrac{b_j}s\),分子不超过 \(10^6\),分母不超过 \(10^{11}\),计算中间值达到 \(10^{17}\),是可以接受的。注意原来那些乘法操作的比例也要减 \(1\)。
为什么赋值不能直接转乘法?
刚刚已经推得一定存在一个最优解使得每个变量都是被按照先赋值,再加,最后乘来操作的。赋值转加法,就会变成先加,再加,最后乘,很明显前两步可以合并;而赋值转乘法会变成先乘,再加,最后乘,这就没法处理了。
/*
* @Author: crab-in-the-northeast
* @Date: 2023-04-30 20:08:14
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2023-04-30 20:27:31
*/
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool f = true;
char ch = getchar();
for (; !isdigit(ch); ch = getchar())
if (ch == '-')
f = false;
for (; isdigit(ch); ch = getchar())
x = (x << 1) + (x << 3) + ch - '0';
return f ? x : (~(x - 1));
}
const int maxk = (int)1e5 + 5;
const int maxn = (int)1e5 + 5;
typedef std :: pair <int, int> pii;
int a[maxk], tp[maxn];
pii asi[maxk];
struct node {
int id, x, p, q;
bool operator < (node b) {
return p * b.q > b.p * q;
}
};
signed main() {
int k = read(), n = read(), m = read();
std :: vector <node> add, mul;
for (int i = 1; i <= k; ++i)
a[i] = read();
for (int i = 1; i <= n; ++i) {
int t = read(), x = read(), v = read();
tp[i] = t;
if (t == 1)
asi[x] = std :: max(asi[x], std :: make_pair(v, i));
else if (t == 2)
add.push_back({i, x, v, 1});
else
mul.push_back({i, x, v - 1, 1});
}
for (int i = 1; i <= k; ++i)
if (asi[i].first > a[i])
add.push_back({asi[i].second, i, asi[i].first - a[i], 1});
std :: sort(add.begin(), add.end());
for (node o : add) {
int id = o.id, x = o.x, p = o.p;
mul.push_back({id, x, p, a[x]});
a[x] += p;
}
std :: sort(mul.begin(), mul.end());
m = std :: min(m, (int)mul.size());
std :: sort(mul.begin(), mul.begin() + m, [](node a, node b) {
return tp[a.id] < tp[b.id];
});
printf("%lld\n", m);
for (int i = 0; i < m; ++i)
printf("%lld ", mul[i].id);
puts("");
return 0;
}
标签:Shop,int,mul,加法,操作,赋值,id,CF521D
From: https://www.cnblogs.com/crab-in-the-northeast/p/cf521d.html