Educational Codeforces Round 85 (Rated for Div. 2)
A. Level Statistics
签到。
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/10 22:41:10
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 100 + 5;
int n;
int p[N], c[N];
void run() {
cin >> n;
for(int i = 1; i <= n; i++) cin >> p[i] >> c[i];
for(int i = 1; i <= n; i++) {
if(p[i] < p[i - 1] || c[i] < c[i - 1]) {
cout << "NO" << '\n';
return;
}
if(p[i] < c[i]) {
cout << "NO" << '\n';
return;
}
if(p[i] - p[i - 1] < c[i] - c[i - 1]) {
cout << "NO" << '\n';
return;
}
}
cout << "YES" << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
B. Middle Class
贪心。排序后求出前缀和直接搞即可。
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/10 22:48:39
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
int n, x;
int a[N];
ll sum[N];
void run() {
cin >> n >> x;
for(int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + n + 1);
reverse(a + 1, a + n + 1);
for(int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i];
int ans = 0;
for(int i = 1; i <= n; i++) {
if(1ll * x * i <= sum[i]) ans = i;
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
C. Circle of Monsters
题意:
现有\(n\)只怪兽围成环,每个怪兽有两个属性\(a_i,b_i\),\(a_i\)为其生命值,\(b_i\)为怪兽死亡后对下一个怪兽造成的伤害。
现在你每次可以打一发子弹使得一个怪兽生命减\(1\),问最少需要打多少子弹,使得所有怪兽都死亡。
思路:
如果是一条链的话,那直接从第一个怪兽开始打,所需要的最少子弹数都是不变的。
但题目中是一个环,我们可以直接枚举起点,然后就会在某个地方断开变为一条链。因为链的情况答案不会发生改变,提前通过前缀和计算即可。
详见代码:
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/11 0:04:38
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 6e5 + 5;
int n;
ll a[N], b[N], sum[N];
void run() {
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i] >> b[i];
a[i + n] = a[i], b[i + n] = b[i];
}
for(int i = 1; i <= 2 * n; i++) sum[i] = sum[i - 1] + max(0ll, a[i] - b[i - 1]);
ll ans = 1e18;
for(int i = 1; i <= n; i++) {
ll res = a[i] + sum[i + n - 1] - sum[i];
ans = min(ans, res);
}
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
D. Minimum Euler Cycle
就类似于这样走就行:
\(1,2,1,3,\cdots,1,n,2,3,2,4,\cdots,2,n,3,4,\cdots,n-1,n,1\)
即可。
随便求一下就行。
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/15 8:39:21
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5;
ll n, l, r;
ll f[N];
void init() {
for(int i = 1; i <= n; i++) {
f[i] = f[i - 1] + 2 * (n - i);
}
}
int calc(ll i) {
if(i == f[n] + 1) return 1;
int t = lower_bound(f + 1, f + n + 1, i) - f - 1;
i -= f[t];
if(i & 1) return t + 1;
else return i / 2 + t + 1;
}
void run() {
cin >> n >> l >> r;
init();
for(ll i = l; i <= r; i++) {
cout << calc(i) << " \n"[i == r];
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
int T; cin >> T;
while(T--) run();
return 0;
}
E. Divisor Paths
题意:
给出整数\(D,D\leq 10^{16}\),现在由\(D\)的所有因子按照如下规则构造一张图:
- 对于两个因子\(u,v\),若\(u|v且v/u=p,p\)为一个素数,那么\(u,v\)之间存在一条边;
- 边\((u,v)\)的边权为整除\(u\)但不整除\(v\)的因子个数。
类似于下图:
现在给出多组询问,对于每组询问\((u,v)\),回答从\(u\)到\(v\)最短路径有多少条。
思路:
这种数学题,我们一点一点来剖析其性质。
- 对于一条边\((u,v),u<v\),其边权为\(d(v)-d(u)\),其中\(d(i)\)为\(i\)的因子个数。
那么对于一条路径而言,长度只于起点和终点两点有关。
我们现在要从\(x\)走到\(y,x>y\),路径长度为\(d(x)-d(y)\)。
- 我们可以将该图形抽象为\(k\)维网格图,每次只能在某一维度走一步,每一维代表一个素因子。
据此我们可以得出从\(x\)到\(y\)的最优的走法:\(x\rightarrow gcd(x,y)\rightarrow y\)或者\(x\rightarrow lcm(x,y)\rightarrow y\)。
接下来就证明第一种走法比第二种走法更优。
我们记\(g=gcd(x,y),l=lcm(x,y)\),那么两种的贡献分别为:\(d(x)+d(y)-d(g)-d(g),d(l)+d(l)-d(x)-d(y)\),我们将两者相减:
这里我们应该还是比较容易发现等式是小于等于\(0\)的,感觉就是证明\(d(a)+d(b)-1\leq d(ab)=d(a)\cdot d(b),a,b\)互质。
所以我们就证明了最优走法。
也有另外一种证明方法:因为是网格图,假设为二维网格图,我们要从\(xy\rightarrow (x-1)(y+1)\),那么有两种走法,通过简单比较就会发现肯定先往小的走再往大的走更优,然后就没了。
我们事先在\(O(\sqrt{n})\)的时间复杂度内找到所有\(D\)的素因子,然后对于每个询问直接搞就行。
代码如下:
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/15 18:59:20
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#include <numeric>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 1e5 + 5, MOD = 998244353;
int qpow(ll a, ll b) {
ll res = 1;
while(b) {
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
void run() {
ll D; cin >> D;
vector <ll> primes;
for(ll i = 2; i * i <= D; i++) if(D % i == 0) {
primes.push_back(i);
while(D % i == 0) D /= i;
}
if(D > 1) primes.push_back(D);
vector <ll> fac(100), inv(100);
fac[0] = 1;
for(int i = 1; i < 100; i++) fac[i] = fac[i - 1] * i % MOD;
inv[99] = qpow(fac[99], MOD - 2);
for(int i = 98; i >= 0; i--) inv[i] = inv[i + 1] * (i + 1) % MOD;
auto calc = [&](ll x, ll y) {
vector <ll> v;
for(auto it : primes) if(x % it == 0) {
int cnt = 0;
while(x % it == 0) {
if(y % it == 0) {
x /= it, y /= it;
} else {
x /= it;
++cnt;
}
}
v.push_back(cnt);
}
ll res = fac[accumulate(all(v), 0)];
for(auto it : v) res = res * inv[it] % MOD;
return res;
};
int q; cin >> q;
while(q--) {
ll a, b; cin >> a >> b;
ll g = __gcd(a, b);
ll ans = calc(a, g) * calc(b, g) % MOD;
cout << ans << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
F. Strange Function
题意:
给出序列\(a_{1,2,...,n}\),现在定义\(f(a)\)表示选择所有\(i\),满足\(a_i>max\{a_j\},j<i\)的\(a_i\)出来,得到一个递增的序列\(b\)。
现在可以删除某些位置上的数花费\(p_i\)的代价(可以为负)。
现在给出\(b\),问最少要多少代价,使得\(f(a)=b\)。
思路:
定义\(dp_{i,j}\)表示现在位于\(a\)序列第\(i\)位,\(b\)序列第\(j\)位的最小代价,我们考虑下一个位置\(a_{i+1}:\)
- 假设\(a_{i+1}\leq b_j\),我们可以删除或者不删除,此时\(dp_{i+1,j}=dp_{i,j}+min(0,p_{i+1})\);
- 假设\(a_{i+1}>b_j\),此时又分两种情况:
- 若\(a_{i+1}=b_{j+1}\),那么此时可选可不选,所以就有\(dp_{i+1,j}=dp_{i,j}+p_{i+1},dp_{i+1,j+1}=dp_{i,j}\);
- 若\(a_{i+1}\not ={b_{j+1}}\),此时我们必须删除,那么\(dp_{i+1,j}=dp_{i,j}+p_{i+1}\)。
我们相当于有三种转移,因为\(b\)数组是连续的,并且我们观察到转移时只有一种情况是\(j\)向\(j+1\)转移的。
我们可以用线段树维护所有\(dp_{i-1,j}\)的值,然后我们将\(b\)分为几个区间,分别按照转移方程进行区间/单点修改即可。
很巧妙的一个题,主要利用了转移方程\(j\)不变以及\(b\)单调的性质,因此我们可以沿用之前的状态并且分区间进行区间修改。
细节见代码:
Code
/*
* Author: heyuhhh
* Created Time: 2020/4/15 20:23:48
*/
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <vector>
#include <cmath>
#include <set>
#include <map>
#include <queue>
#include <iomanip>
#include <assert.h>
#define MP make_pair
#define fi first
#define se second
#define pb push_back
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define INF 0x3f3f3f3f
#define Local
#ifdef Local
#define dbg(args...) do { cout << #args << " -> "; err(args); } while (0)
void err() { std::cout << std::endl; }
template<typename T, typename...Args>
void err(T a, Args...args) { std::cout << a << ' '; err(args...); }
template <template<typename...> class T, typename t, typename... A>
void err(const T <t> &arg, const A&... args) {
for (auto &v : arg) std::cout << v << ' '; err(args...); }
#else
#define dbg(...)
#endif
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
//head
const int N = 5e5 + 5;
int n, m;
int a[N], p[N], b[N];
ll minv[N << 2], lz[N << 2];
void tag(int o, ll v) {
minv[o] += v, lz[o] += v;
}
void push_up(int o) {
minv[o] = min(minv[o << 1], minv[o << 1|1]);
}
void push_down(int o, int l, int r) {
if(lz[o] != 0) {
int mid = (l + r) >> 1;
tag(o << 1, lz[o]);
tag(o << 1|1, lz[o]);
lz[o] = 0;
}
}
void build(int o, int l, int r) {
lz[o] = 0;
if(l == r) {
if(l == 0) minv[o] = 0;
else minv[o] = 1e18;
return;
}
int mid = (l + r) >> 1;
build(o << 1, l, mid), build(o << 1|1, mid + 1, r);
push_up(o);
}
void update(int o, int l, int r, int L, int R, ll v) {
if(L > R) return;
if(L <= l && r <= R) {
tag(o, v); return;
}
push_down(o, l, r);
int mid = (l + r) >> 1;
if(L <= mid) update(o << 1, l, mid, L, R, v);
if(R > mid) update(o << 1|1, mid + 1, r, L, R, v);
push_up(o);
}
void modify(int o, int l, int r, int p, ll v) {
if(l == r) {
minv[o] = min(minv[o], v);
return;
}
push_down(o, l, r);
int mid = (l + r) >> 1;
if(p <= mid) modify(o << 1, l, mid, p, v);
else modify(o << 1|1, mid + 1, r, p, v);
push_up(o);
}
ll query(int o, int l, int r, int L, int R) {
if(L <= l && r <= R) {
return minv[o];
}
push_down(o, l, r);
int mid = (l + r) >> 1;
ll res = 1e18;
if(L <= mid) res = query(o << 1, l, mid, L, R);
if(R > mid) res = min(res, query(o << 1|1, mid + 1, r, L, R));
return res;
}
void run() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) cin >> p[i];
cin >> m;
for (int i = 1; i <= m; i++) cin >> b[i];
for (int i = 1, j = 1; i <= n; i++) {
if (a[i] == b[j]) ++j;
if(i == n && j <= m) {
cout << "NO" << '\n';
return;
}
}
build(1, 0, m);
for (int i = 0; i < n; i++) {
int t = lower_bound(b + 1, b + m + 1, a[i + 1]) - b;
if (p[i + 1] < 0) update(1, 0, m, t, m, p[i + 1]);
if (b[t] == a[i + 1]) {
ll v = query(1, 0, m, t - 1, t - 1);
modify(1, 0, m, t, v);
}
update(1, 0, m, 0, t - 1, p[i + 1]);
}
ll ans = query(1, 0, m, m, m);
cout << "YES" << '\n';
cout << ans << '\n';
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cout << fixed << setprecision(20);
run();
return 0;
}
G. Substring Search
题意:
给出\(s\)串和\(t\)串,现在问对于\(s\)的每个长度为\(m\)的子串,是否有\(s'=t\)。字符串相等定义为:
- 对于\(1\leq i\leq len,s_i=t_i或p_{s_i}=t_i\);
其中\(p\)为\(1\)到\(26\)的排列。
对所有的\(1\leq i\leq n-m+1\)都要输出答案。
思路:
将字符串相等形式化即可表示为:
那么稍微变一下即为:
后面这一部分我们直接将其打开是一个关于\(t_j\)的四次多项式,系数是关于\(i+j\)位置的一个表达式。
那么我们直接将多项式拆开算,提前算出来系数,然后对于每一项求卷积即可。
关键在于形式化的表述,这一步挺巧妙的,平方的话是使得其结果非负,那么如果为\(0\)的话,就说明这个等式即保证了每一项都至少有一个成立。
代码略。
重要的是自信,一旦有了自信,人就会赢得一切。