Educational Codeforces Round 137 (Rated for Div. 2)
D - Problem with Random Tests
prob. :
给一个01串s,找到s的两个子串,使得这两个串在右对齐的情况下按位或的值最大,输出去除前导零的或运算后的最大值
数据随机生成,n 1e6
一些刚开始看成异或害搁那造tire树我真是服了自己
ideas:
贪心一下,其中一个串肯定是从左往右第一个1开始的后缀,它可以贡献最高位的1
另一个串首先需要把从左往右第一个0给变成1,也就是说它最高位是1,同时他的长度要足够能够到这个0的位置
记原串中第一个1的位置为pos1,第一个0的位置为pos0,
第二个串的选取因此会变成在pos0与pos1之间
因为数据随机生成这样的串不多,直接暴力
btw,如果整1e6次 string tmp = tmp + ‘0’
这种会T(
code:
#include "bits/stdc++.h"
using namespace std;
const int N = 1e6 + 10;
char s[N];
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
int n;
cin >> n;
cin >> (s + 1);
int pos1 = -1, pos0 = -1;
for (int i = 1; i <= n; ++i) {
if (pos1 == -1 && s[i] == '1') pos1 = i;
if (pos1 != -1 && pos0 == -1 && s[i] == '0') pos0 = i;
}
if (pos1 == -1) {
cout << 0;
return 0;
}
if (pos0 == -1) {
for (int i = pos1; i <= n; ++i) cout << s[i];
return 0;
}
int len = n - pos0 + 1;
vector<char> res(len);
for (int i = pos0; i <= n; ++i) res[i - pos0] = s[i];
for (int i = 1; i + len - 1 <= n; ++i) {
if (s[i] == '0') continue;
bool flag = false;
for (int j = 0; j < len; ++j) {
char ch;
if (s[pos0 + j] == '1' || s[i + j] == '1') ch = '1';
else ch = '0';
if (flag) {
res[j] = ch;
continue;
}
if (ch < res[j]) break;
if (ch > res[j]) {
flag = true;
res[j] = ch;
}
}
}
for (int i = pos1; i < pos0; ++i) cout << s[i];
for (auto x : res) cout << x;
}
E - FTL
prob. :
有两个技能,每个技能都有一个伤害值 p i p_i pi和一个冷却时间 t i t_i ti,目标有一个血量 h h h和一个防御值 s s s,每次可以选择一个技能发射会造成 p i − s p_i-s pi−s的伤害,或者两个技能一起发射造成 p 0 + p 1 − s p_0+p_1-s p0+p1−s的伤害,问最少多少时间能将目标击败
p 5000, t 1e12 ,h 5000,s min{p0,p1}
ideas:
刚开始尝试贪心然后发现大分讨而且贪不出来
考虑dp
状态描述,由于t很大无法描述状态,又发现伤害和技能释放次数都是5000的量级
观察题目发现在一些时间两个技能可以看作独立,但不可能完全独立,会有一些时候是一起释放的,从这里入手,使得所有记录的状态两个技能都是刚释放完
一起释放技能的最优的情况也很难讨论清楚。设从上一次一起释放到下一次一起释放的时间T,在T之前两个技能都是各自释放,然后一个技能等待另一个技能冷却。容易想到T是t0或t1的倍数。考虑在已知T的条件下怎么完善这个问题,先放一放。
击败目标的过程是在某次一起释放后,两个技能独立不停地释放
最后这段独立释放技能的时间T‘也是t0或t1的倍数
d p [ i ] dp[i] dp[i] 表示最后一次是一起释放技能时,造成i点伤害的最少时间
转移的时候考虑枚举T(最多是h倍的t0或t1)
实际上,以0号技能为主时,每次枚举之前两个技能一起释放后已造成的伤害i,0号技能的释放次数j,可以得出从i之后两个技能都是独立不停地释放会造成的伤害(用来更新最终答案),以及如果此时可以构成两个技能一起释放的条件,更新下一个dp值
讲得可能还是有点乱,不明白可以结合官方的英文题解或code食用
code:
#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
const ll N = 5010;
const ll inf = 0x3f3f3f3f3f3f3f3f;
ll p[2], t[2];
ll h, s;
ll dp[N];
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
cin >> p[0] >> t[0] >> p[1] >> t[1] >> h >> s;
memset(dp, 0x3f, sizeof dp);
dp[0] = 0;
ll ans = inf;
for (ll i = 0; i <= h; ++i) {
for (ll j = 1; j + i <= h; ++j) {
for (ll k = 0; k < 2; ++k) {
ll tot = i + j * (p[k] - s) + (j * t[k] / t[1 - k]) * (p[1 - k] - s);
if (tot >= h) {
ans = min(ans, dp[i] + j * t[k]);
}
if (j * t[k] >= t[1 - k]) {
ll tmp = i + (j - 1) * (p[k] - s) + (j * t[k] / t[1 - k] - 1) * (p[1 - k] - s)
+ (p[0] + p[1] - s);
if (tmp > h) tmp = h;
dp[tmp] = min(dp[tmp], dp[i] + j * t[k]);
}
}
}
}
ans = min(ans, dp[h]);
cout << ans << endl;
}
F - Intersection and Union
prob:
给n个区间,每个区间相当于一个包含[l, r] 的数的集合,给出三种运算
∪,集合求并;⊕,集合的并减去集合的交;∩,集合求交;
求 ∑ ∣ ( ( ( S 1 o p 1 S 2 ) o p 2 S 3 ) o p 3 S 4 ) … o p n − 1 S n ∣ \sum|(((S_1 \,op_1\,S2)\,op_2 \,S3) \, op3\, S4)\dots op_{n-1}S_n| ∑∣(((S1op1S2)op2S3)op3S4)…opn−1Sn∣, 其中每个op是上述三种之一,求的是一共 3 n − 1 3^{n - 1} 3n−1种可能结果的和
ideas1:
组合数+并查集
这个想法挺妙的自己再记录一下
参考知乎某题解:https://zhuanlan.zhihu.com/p/574586694
观察发现这个集合运算过程不满足结合律和交换律
考虑拆开计算贡献
考虑对于每个数字x在最后答案里的贡献是多少
对于每个数字x,如果被第i个区间包含,在i个区间之前的运算结果里如果有x,为了在该区间参与运算后保留x,op可以是∪或∩;如果在之前的结果里没有x,op可以是∪或⊕,都是3种里面取2种
将每个运算符和后面的区间放在一起
对于上述i我们只要取x最后一次出现的区间位置lst,对于lst之前op随意,即每个位置3种情况;到lst的时候根据情况选择2种能保留x的运算;对于lst之后的op取能保留x的2种运算。以此可以列出组合数式子
对于找对于x的lst,知乎里提到了一个经典模型,即区间覆盖染色,每个位置最后的颜色,倒着并查集(不懂的可以去看看ACWing3115题解 : https://www.acwing.com/solution/content/60830/
pi 为第 i 个元素后面第一个 未被覆盖 的区间的 左端点
更新/并查集操作为 p[nw] = find(nw + 1), nw = find(nw);
借用下图:
code1:
#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
const ll N = 3e5 + 10;
const ll inf = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;
ll l[N], r[N], p[N];
ll find(ll x) {
return (p[x] == x ? x : p[x] = find(p[x]));
}
ll qpow(ll x, ll n) {
if (n <= 0) return 1;
ll res = 1;
while (n) {
if (n & 1) res = res * x % mod;
x = x * x % mod;
n >>= 1;
}
return res;
}
signed main() {
ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
ll n;
cin >> n;
for (ll i = 1; i <= n; ++i) cin >> l[i] >> r[i];
for (ll i = 0; i <= 3e5 + 5; ++i) p[i] = i;
ll ans = 0;
for (ll i = n; i >= 1; --i) {
ll nw = find(l[i]);
while (nw <= r[i]) {
if (i != 1) ans = (ans + qpow(3, i - 2) * qpow(2, n - i + 1) % mod) % mod;
else ans = (ans + qpow(2, n - 1)) % mod;
p[nw] = find(nw + 1);
nw = find(nw);
}
}
cout << ans << endl;
}