CF Round #625 Div1
我的第 100
场参与排名的 Codeforces
记录一下
A. Journey Planning
给一个长度为 \(n\) 的序列 \(b\),求子序列中满足原下标差与序列两项差相等的最大和
就是说有个序列 \(b\) ,求出一个子序列 \(x\) ,它在原序列中的下标依次为 \(c\),则对每个 \(i\) 都有
\(c_{i+1}-c_i = b_{c_{i+1}}-b_{c_i}\)
求 \(\sum x\) 的最大值
显然对于 \(i-b_i\) 相同的值都是可以放在同一个子序列中的,贪心丢进去就行了,图方便没有去判断正负直接用 \(map\) 储存差值即可
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/3/1
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
template <class T>
inline bool scan(T& ret) {
char c;
int sgn;
if (c = getchar(), c == EOF) return 0; // EOF
while (c != '-' && (c < '0' || c > '9')) c = getchar();
sgn = (c == '-') ? -1 : 1;
ret = (c == '-') ? 0 : (c - '0');
while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0');
ret *= sgn;
return 1;
}
//template <class T>
//inline void out(T x) {
// if (x > 9) out(x / 10);
// putchar(x % 10 + '0');
//}
int n;
map<int, ll> mp;
int main(int argc, char* argv[]) {
scanf("%d", &n);
vector<int> a(n);
for (int i = 0; i < n; ++i) scanf("%d", &a[i]);
for (int i = 0; i < n; ++i) {
mp[a[i] - i] += a[i];
}
ll res = 0;
for (auto i : mp) {
if (i.second > res) res = i.second;
}
printf("%lld\n", res);
return 0;
}
B. Navigation System
给一张有向图,边权全 1
,给出行走路径,已知所有信息,求最少和最多需要改变多少次最短路径导航(也就是每次在一个点给出到终点的导航,但是实际路径没有按导航走,到下个点时换了另一条路)
Q:为什么会有多种情况?A:多条最短路的时候可能选择其中任何一条,需要分别求最小和最大值
数据范围:点、边、行走路径长度最多 \(2\cdot 10^5\)
首先考虑到换路问题需要当前点到终点的最短路,所以反向建图对终点求一次单源 dij
假设现在在 \(x\) 点,下一个点是 \(y\),如果 \(dist[x]=dist[y]+1\) 说明 \(x\to y\) 是 \(x\to last\) 的最短路(可能是之一)
然后遍历 \(x\) 所有的后继节点(正向图)发现没有其他满足与 \(y\) 一样条件的点的话就是唯一最短路,那么两个答案都不变。如果有多条最短路只把最多次数 +1 即可。如果不是最短路就是两个答案都 +1
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/3/1
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5 + 50;
const int mod = 1e9 + 7;
ll qp(ll a, ll n) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
template<class T>
inline bool scan(T &ret) {
char c;
int sgn;
if (c = getchar(), c == EOF) return 0; // EOF
while (c != '-' && (c < '0' || c > '9')) c = getchar();
sgn = (c == '-') ? -1 : 1;
ret = (c == '-') ? 0 : (c - '0');
while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0');
ret *= sgn;
return 1;
}
//template <class T>
//inline void out(T x) {
// if (x > 9) out(x / 10);
// putchar(x % 10 + '0');
//}
int n, m, k;
int dis[maxn];
vector<int> edge[maxn], inv[maxn];
void dijkstra(int s) {
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > que;
memset(dis, 0x3f, sizeof dis);
que.push({0, s});
dis[s] = 0;
while (!que.empty()) {
auto f = que.top();
que.pop();
int u = f.second, d = f.first;
if (d != dis[u]) continue;
for (auto v : edge[u]) {
if (dis[u] + 1 < dis[v]) {
dis[v] = dis[u] + 1;
que.push({dis[v], v});
}
}
}
}
int main(int argc, char *argv[]) {
scanf("%d%d", &n, &m);
for (int i = 0, u, v; i < m; ++i) {
scanf("%d%d", &u, &v);
edge[v].push_back(u);
inv[u].push_back(v);
}
scanf("%d", &k);
vector<int> path(k);
for (int i = 0; i < k; ++i) {
scanf("%d", &path[i]);
}
dijkstra(path.back());
int mnres = 0, mxres = 0;
for (int i = 0; i < k - 1; ++i) {
int mn = 0x3f3f3f3f, count = 0;
for (auto v : inv[path[i]]) {
if (dis[v] == mn) {
count ++;
} else if (dis[v] < mn) {
mn = dis[v];
count = 1;
}
}
if (mn == dis[path[i + 1]] && count == 1) ;
else if (mn == dis[path[i + 1]] && count > 1) mxres ++;
else if (mn < dis[path[i + 1]]) mnres ++, mxres ++;
// cerr << mn << " " << dis[path[i + 1]] << endl;
}
printf("%d %d\n", mnres, mxres);
return 0;
}
C. World of Darkraft: Battle for Azathoth
\(n\) 个武器 \(m\)个防具(weapons
and armor
总感觉有点熟悉??某游戏mod制作者发出了鸽子的声音)
武器有攻击力,防具有防御力,每件装备都有它的花费
\(p\) 个怪物,每个都有其攻击力、防御力、价值
现在需要挑一件武器和一件防具,对于 \(p\) 个怪物,如果你选择的武器攻击力大于它的防御力、防具大于它的攻击力,则可以无损地获得它的价值。求最大价值 - 花费
\(1 \leq n, m, p \leq 2 \cdot 10^5\)
看到这种数据范围和两个能力值就知道是二维点对问题了
很套路的扫描线
假设一个二维空间 \(xoy\),\(x\) 轴表示攻击力轴,\(y\) 轴表示防御力轴(当然也可以反过来),每个怪物是离散的二维点,那么对于一组武器、防具的选择则有左下方的所有点都可以击败,也就是一个二维带权区间最值,总之对防具(\(y\) 轴)排序,对武器(\(x\) 轴)建立线段树求区间和,扫描线往上增加怪物的价值点求最大值即可。
具体来说,每个点的权值为:首先第一次将线段树上的所有点减去武器的花费,然后枚举每一个防具,最大值 - 它的花费就是答案。所有防具枚举完取最大值就是最后的答案。实际上每个怪物对大于它防御的武器都是有贡献的所以需要区间加、区间求最值。
我好啰嗦
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/3/1
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5+50;
const int mod = 1e9+7;
int n, m, p;
int val[maxn << 2], lazy[maxn << 2];
int a[maxn], ca[maxn];
int vala[maxn];
inline void build(int rt, int l, int r) {
if (l == r) {
val[rt] = -vala[l];
return;
}
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
val[rt] = max(val[rt << 1], val[rt << 1 | 1]);
}
inline void pushup(int rt) {
val[rt] = max(val[rt << 1], val[rt << 1 | 1]);
}
inline void pushdown(int rt) {
if (lazy[rt]) {
lazy[rt << 1] += lazy[rt];
lazy[rt << 1 | 1] += lazy[rt];
val[rt << 1] += lazy[rt];
val[rt << 1 | 1] += lazy[rt];
lazy[rt] = 0;
}
}
int le, k;
inline void update(int rt, int l, int r) {
if (le <= l) {
val[rt] += k;
lazy[rt] += k;
return;
}
int mid = l + r >> 1;
pushdown(rt);
if (le <= mid) update(rt << 1, l, mid);
update(rt << 1 | 1, mid + 1, r);
pushup(rt);
}
struct node {
int x, y, z;
bool operator < (const node& oth) const {
return y < oth.y;
}
};
vector<int> xpos;
int main(int argc, char* argv[]) {
scanf("%d%d%d", &n, &m, &p);
xpos.resize(n);
for (int i = 1; i <= n; ++i) {
scanf("%d%d", &a[i], &ca[i]);
xpos[i - 1] = a[i];
}
sort(xpos.begin(), xpos.end());
xpos.erase(unique(xpos.begin(), xpos.end()), xpos.end());
for (int i = 1; i <= n; ++i) vala[i] = 0x3f3f3f3f;
for (int i = 1; i <= n; ++i) {
int pos = lower_bound(xpos.begin(), xpos.end(), a[i]) - xpos.begin() + 1;
vala[pos] = min(vala[pos], ca[i]);
}
build(1, 1, n);
vector<pair<int, int> > b(m);
for (int i = 0; i < m; ++i) {
scanf("%d%d", &b[i].first, &b[i].second);
}
sort(b.begin(), b.end());
vector<node> x(p);
for (int i = 0; i < p; ++i) {
scanf("%d%d%d", &x[i].x, &x[i].y, &x[i].z);
}
sort(x.begin(), x.end());
int now = 0;
ll res = -0x3f3f3f3f3f3f3f3f;
for (int i = 0; i < m; ++i) {
ll tmp = -b[i].second;
while (now < p) {
if (x[now].y < b[i].first) {
le = upper_bound(xpos.begin(), xpos.end(), x[now].x) - xpos.begin() + 1;
k = x[now].z;
if (le <= n) update(1, 1, n);
now ++;
} else break;
}
tmp += val[1];
// cerr << tmp << " " << i << endl;
res = max(res, tmp);
}
printf("%lld\n", res);
return 0;
}
D. Reachable Strings
给一个串,长度不超过 \(2 \cdot 10^5\) ,\(2 \cdot 10^5\) 次询问两个等长的子串 \(s1,s2\)
是否可达
所谓 \(s,t\) 可达
即是指对于串 \(s\) 任意次将子串 011
替换成 110
或者 110
替换成 011
能变为 \(t\)
首先,显然两个串的 0 的数量相等、1 的数量相等
其次,显然两个串的独立 1 相对位置不变,that's to say 010
can't be 100
or 001
那么就可以认定,在去掉所有相邻的一对 1 之后如果串相同则认为两个串可达
赛中其实是想到了结论的,然而写的实在是太丑了
线段树维护所有不相邻 1 的 hash 即可
很久没写过这么复杂的线段树了,合并属实难写,以至于我想一句句的说明它每句的意思
但还是算了,实在太麻烦了,总之维护区间左值(pflag)、右值(sflag)、已经删除了连续 1 的对数(del)、删除之后的长度(len)、删除之后的 hash 值(hs)即可
注意此处有可能需要删掉 hash 值的最后一个值和开头一个值,稍作分析就知道一定会产生除法,所以必须取模使用逆元。另外一提此题有其他简单写法,不过我没有看懂别人按奇偶分类的 hash 写法
/*================================================================
*
* 创 建 者: badcw
* 创建日期: 2020/3/4
*
================================================================*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 2e5+50;
const int mod = 1e9+7;
ll qp(ll a, ll n) {
ll res = 1;
while (n > 0) {
if (n & 1) res = res * a % mod;
a = a * a % mod;
n >>= 1;
}
return res;
}
template <class T>
inline bool scan(T& ret) {
char c;
int sgn;
if (c = getchar(), c == EOF) return 0; // EOF
while (c != '-' && (c < '0' || c > '9')) c = getchar();
sgn = (c == '-') ? -1 : 1;
ret = (c == '-') ? 0 : (c - '0');
while (c = getchar(), c >= '0' && c <= '9') ret = ret * 10 + (c - '0');
ret *= sgn;
return 1;
}
//template <class T>
//inline void out(T x) {
// if (x > 9) out(x / 10);
// putchar(x % 10 + '0');
//}
int n, q;
char s[maxn];
const int prim = 997;
const int invs = qp(prim, mod - 2);
ll base[maxn] = {1};
struct node {
int len;
int del;
ll hs;
bool pflag, sflag;
bool operator == (const node& oth) const {
return len == oth.len && del == oth.del && hs == oth.hs && pflag == oth.pflag && sflag == oth.sflag;
}
}p[maxn << 2];
node comb(node a, node b) {
if (!a.len) {
b.del += a.del;
return b;
}
if (!b.len) {
a.del += b.del;
return a;
}
node ret;
ret.del = a.del + b.del;
if (a.sflag && b.pflag) {
ret.del ++;
a.hs = (a.hs - 1 + mod) * invs % mod;
a.len --;
a.sflag = 0;
if (a.len == 0) a.pflag = 0;
b.hs = (b.hs - base[b.len - 1] + mod) % mod;
b.len --;
b.pflag = 0;
if (b.len == 0) b.sflag = 0;
ret.len = a.len + b.len;
ret.pflag = (a.len == 0 ? b.pflag : a.pflag);
ret.sflag = (b.len == 0 ? a.sflag : b.sflag);
ret.hs = (a.hs * base[b.len] % mod + b.hs) % mod;
} else {
ret.len = a.len + b.len;
ret.hs = (a.hs * base[b.len] % mod + b.hs) % mod;
ret.pflag = (a.len == 0 ? b.pflag : a.pflag);
ret.sflag = (b.len == 0 ? a.sflag : b.sflag);
}
return ret;
}
void build(int rt, int l, int r) {
if (l == r) {
p[rt].len = 1;
p[rt].del = 0;
p[rt].hs = s[l] - '0';
p[rt].sflag = p[rt].pflag = s[l] - '0';
return;
}
int mid = l + r >> 1;
build(rt << 1, l, mid);
build(rt << 1 | 1, mid + 1, r);
p[rt] = comb(p[rt << 1], p[rt << 1 | 1]);
}
int le, re;
node query(int rt, int l, int r) {
if (le <= l && r <= re) {
return p[rt];
}
int mid = l + r >> 1;
node tmp{0, 0, 0, 0, 0};
if (le <= mid) {
tmp = comb(tmp, query(rt << 1, l, mid));
}
if (re > mid) {
tmp = comb(tmp, query(rt << 1 | 1, mid + 1, r));
}
return tmp;
}
int main(int argc, char* argv[]) {
for (int i = 1; i < maxn; ++i) base[i] = 1ll * base[i - 1] * prim % mod;
scanf("%d%s%d", &n, s + 1, &q);
build(1, 1, n);
for (int i = 0; i < q; ++i) {
int l1, l2, len;
scanf("%d%d%d", &l1, &l2, &len);
le = l1, re = l1 + len - 1;
node p1 = query(1, 1, n);
le = l2, re = l2 + len - 1;
node p2 = query(1, 1, n);
puts(p1 == p2 ? "YES" : "NO");
}
return 0;
}