做题记录2
CF2144C Non-Descending Arrays
思路
考虑 dp 。
对于每个位置,都有换或者不换两种状态,所以设 \(f_{i, 0/1}\) 为考虑前 \(i\) 个位置,并且第 \(i\) 个位置交换或者不交换累计的收益。接下来枚举每种情况:
-
对于 \(f_{i, 0}\) ,显然可以通过 \(f_{i - 1, 0}\) 直接转移,接下来判断一下能否从 \(f_{i - 1, 1}\) 转移;当交换会带来收益,也就是 \(a_i > b_{i - 1} \text{并且} b_i > a_{i - 1}\) 时 ,也可以通过 \(f_{i - 1, 1}\) 转移。
-
对于 $f_{1, 1},转移的逻辑是一样的,但是由于 \(i\) 号位置要进行交换,所以判断 \(i - 1\) 时要反过来判断。
最后考虑边界,有 \(f_{1, 0} = f_{1, 1} = 1\) 。
代码
void solve(void) {
int n; std::cin >> n;
std::vector<int> a(n + 1);
std::vector<int> b(n + 1);
for(int i = 1; i <= n; i++) std::cin >> a[i];
for(int i = 1; i <= n; i++) std::cin >> b[i];
i64 f[110][2];
memset(f, 0, sizeof(f));
f[1][1] = f[1][0] = 1;
for(int i = 2; i <= n; i++) {
f[i][0] = f[i][1] = 0;
for(int s = 0; s < 2; s++) {
int prea = (s == 0 ? a[i - 1] : b[i - 1]);
int preb = (s == 0 ? b[i - 1] : a[i - 1]);
if(a[i] >= prea && b[i] >= preb) {
f[i][0] = (f[i][0] + f[i - 1][s]) % P;
}
}
for(int s = 0; s < 2; s++) {
int prea = (s == 0 ? a[i - 1] : b[i - 1]);
int preb = (s == 0 ? b[i - 1] : a[i - 1]);
if(b[i] >= prea && a[i] >= preb) {
f[i][1] = (f[i][1] + f[i - 1][s]) % P;
}
}
}
i64 ans = (f[n][0] + f[n][1]) % P;
std::cout << ans << '\n';
}
CF2144D Price Tags
思路
首先注意到,\(x \in [2, \max({c_1, c_2, \dots, c_n})]\) ,物品价格的上限是 \(\lceil \frac{\max(c)}{x} \rceil\) 。但是依次枚举时间复杂度是 \(O(n^2)\) 的,怎么优化呢?
可以按照物品的最终售价 \(p\) 分桶,设 \(C = \max(c)\) ,建立数组 \(cnt[1..C]\) 记录每种价格有多少种物品,再维护前缀和 \(s[1..C]\) ,此时对于任何区间 \([L, R]\) ,原价在这个区间中的商品数量就为 \(s_R - s_{L - 1}\) 。
枚举 \(x\) ,那么商品的售价范围就在 \(p = 1, 2, \dots, \lceil \frac{C}{x} \rceil\) ,对于每个售价,原价在 \([(p - 1) \times x , p \times x]\) 的商品价格一定会分到 \(p\) 上,这些商品的数量就为 $s_{p \times x} - s_{(p - 1) \times x - 1} $ ,记作 \(cnt\_new\) ,它们带来的收益就为 \(cnt\_new \times p\) 。接着计算修改价格后能使用的旧标签的数量,就为 \(\min(cnt\_new, cnt_{p})\) 。之后就可以计算答案了。
时间复杂度为 \(O(\sum^{C}_{x = 2}\frac{C}{x})\) ,是调和级数级别的,也就是 \(O(n\log n)\) 。
代码
void solve(void) {
int n, y;
std::cin >> n >> y;
std::vector<int> c(n + 1);
for(int i = 1; i <= n; i++) {
std::cin >> c[i];
}
int Cmax = *std::max_element(c.begin() + 1, c.end());
std::vector<int> cnt(Cmax + 1, 0);
for(int i = 1; i <= n; i++) cnt[c[i]]++;
std::vector<int> s(Cmax + 1, 0);
for(int i = 1; i <= Cmax; i++) {
s[i] = s[i - 1] + cnt[i];
}
i64 ans = LLONG_MIN;
for(int i = 2; i <= Cmax + 1; i++) {
i64 sum = 0;
i64 used = 0;
int maxn = (Cmax + i - 1) / i;
for(int p = 1; p <= maxn; p++) {
int L = (p - 1) * i + 1;
int R = std::min(p * i, Cmax);
if(L > Cmax) break;
int cnt_new = s[R] - s[L - 1];
int cnt_old = (p <= Cmax ? cnt[p] : 0);
sum += (i64)p * cnt_new;
used += std::min(cnt_new, cnt_old);
}
i64 get = sum - (i64)(y * (n - used));
ans = std::max(ans, get);
}
std::cout << ans << '\n';
}
CF2148E Split
思路
记数字 \(i\) 出现的次数是 \(cnt_i\) 。如果能够把一个数组分成 \(k\) 个完全相同的部分,说明这个数组中每个数字出现的次数一定是 \(k\) 的倍数,分开后每个数组在一组中出现的次数是 \(\frac{cnt_{i}}{k}\) 。所以统计一下每个数字出现的次数,如果某个数字出现的次数不是 \(k\) 的倍数,直接输出 \(0\) 就可以了。之后顺便让 \(cnt_i /= k\) 。记子数组中每种元素出现的次数为 \(c_i\) ,此时问题就转化为计算满足所有的 \(c_i \leq cnt_i\) 的子数组的数量。
可以用双指针维护。
代码
void solve(void) {
int n, k; std::cin >> n >> k;
std::vector<int> a(n + 1), cnt(n + 1, 0);
for(int i = 1; i <= n; i++) {
std::cin >> a[i];
cnt[a[i]]++;
}
for(int i = 1; i <= n; i++) {
if(cnt[i] % k) {
std::cout << 0 << '\n';
return;
}
cnt[i] /= k;
}
i64 ans = 0;
int l = 1, r = 1;
std::vector<int> b(n + 1);
while(l <= r && r <= n) {
b[a[r]]++;
while(b[a[r]] > cnt[a[r]]) {
b[a[l]]--;
l++;
}
ans += (r - l + 1);
r++;
}
std::cout << ans << '\n';
}
CF2143C Max Tree
思路
神似 ICPC 2023 Nanjing R F题 等价重写
直接考虑最好情况,要想让结果最大,对于每条边,一定要选择 \(x\) 和 \(y\) 更大的一个,因此当 \(x > y\) 时,连 \(u \rightarrow v\) 的有向边,反之连 \(v \rightarrow\) ,对这个图拓扑排序,然后按照顺序从大到小分配点就好了,题目已经说了一定有解,所以这个做法是没有问题的。
代码
std::vector<int> G[N];
void solve(void) {
int n; std::cin >> n;
for(int i = 1; i <= n; i++) G[i].clear();
std::vector<int> in(n + 1);
for(int i = 1; i < n; i++) {
int u, v, x, y;
std::cin >> u >> v >> x >> y;
if(x > y) {
G[u].push_back(v);
in[v]++;
} else {
G[v].push_back(u);
in[u]++;
}
}
std::vector<int> L;
std::queue<int> q;
for(int i = 1; i <= n; i++) if(in[i] == 0) q.push(i);
while(q.size()) {
auto u = q.front();
q.pop();
L.push_back(u);
for(auto v : G[u]) {
if(--in[v] == 0) q.push(v);
}
}
//std::reverse(L.begin(), L.end());
std::vector<int> p(n + 1);
int num = n;
for(auto i : L) {
p[i] = num;
num--;
}
for(int i = 1; i <= n; i++) std::cout << p[i] << ' ';
std::cout << '\n';
}
Inversion Graph Coloring (Easy Version)
思路
要想让每对逆序对的颜色都互不相同,说明这个序列被拆成了两条链,一条是红的,一条是蓝的。由于是逆序对,所以两条链是不降的。可以贪心的维护拆链的过程,如果当前的元素可以跟在第一条链后面就跟在第一条后面;不然能跟第二条后面就跟第二条后面。
设 \(f_{i, j}\) 是红链以 \(i\) 结尾,蓝链以 \(j\) 结尾的方案数,有边界 \(f_{0, 0} = 1\) 。由于 \(n \leq 200\) ,所以直接写一个 \(O(n^3)\) 的暴力就好了。
代码
void solve(void) {
int n; std::cin >> n;
std::vector<int> a(n + 1);
for(int i = 1; i <= n; i++) std::cin >> a[i];
int f[310][310], nf[310][310];
memset(f, 0, sizeof(f));
memset(nf, 0, sizeof(nf));
f[0][0] = 1;
for(int i = 1; i <= n; i++) {
for(int x = 0; x <= n; x++) {
for(int y = 0; y <= n; y++) {
nf[x][y] = f[x][y];
}
}
for(int x = 0; x <= n; x++) {
for(int y = 0; y <= n; y++) {
int t = f[x][y];
if(t == 0) continue;
if(a[i] >= x) {
nf[a[i]][y] = (nf[a[i]][y] + t) % P;
} else if(a[i] >= y) {
nf[x][a[i]] = (nf[x][a[i]] + t) % P;
}
}
}
for(int x = 0; x <= n; x++) {
for(int y = 0; y <= n; y++) {
f[x][y] = nf[x][y];
}
}
}
i64 sum = 0;
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n; j++) {
sum = (sum + f[i][j]) % P;
}
}
std::cout << sum % P << '\n';
}

浙公网安备 33010602011771号