Codeforces Round #783 (Div. 2)
A. Direction Change
[Link](Problem - A - Codeforces)
题意
给你一个网格,从 ( 1 , 1 ) (1,1) (1,1)走到 ( n , m ) (n,m) (n,m),不能向同一个方向连续走两次,问你最少多少步走到 ( n , m ) (n,m) (n,m)。
思路
- 贪心
贪心来想每次向右或下走最优,由于有限制,因此走到不能走就要往回走,这个时候形成了循环节,判断是否多一截就行。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define int long long
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
signed main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n >> m;
if (min(n, m) == 1 && max(n, m) > 2) cout << -1 << '\n';
else {
n --, m --;
int res = min(n, m) * 2;
if (abs(n - m)) {
res += abs(n - m) / 2 * 4;
if (abs(n - m) % 2) res += 1;
}
cout << res << '\n';
}
}
return 0;
}
B. Social Distance
[Link](Problem - B - Codeforces)
题意
有 m m m把椅子围成一圈, n n n个人,第 i i i给人要求他的左右 a [ i ] a[i] a[i]个椅子都是空的,问是否能将 n n n个人分配好。
思路
- 贪心
将所有人的限制按照 a [ i ] a[i] a[i]从小到大排序这样围成一圈最优,因为可以最大程度的将 a a a数组附带消除掉,然后判断是否超过 m m m个椅子即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> a[i];
sort(a + 1, a + 1 + n);
LL sum = 0;
for (int i = 2; i <= n; i ++) sum += a[i];
sum += a[n] + n;
if (sum > m) cout << "NO\n";
else cout << "YES\n";
}
return 0;
}
C. Make it Increasing
[Link](Problem - C - Codeforces)
题意
给你两个数组 a , b a,b a,b,开始 b b b数组均为 0 0 0,每次操作可以选择一个 i i i,让 b i = b i + a i b_i=b_i+a_i bi=bi+ai或者 b i = b i − a i b_i=b_i-a_i bi=bi−ai,问你最少多少次操作可以让 b b b数组单调递增。
思路
- 贪心,枚举
我们发现固定了起点以后,后面每个数具体到哪也是不确定的,因此要换一个角度来看,继续观察发现最优解里一定存在某个位置等于 0 0 0,假设不存在 0 0 0,由于最终是单调递增,我们可以找到第一个大于 0 0 0的位置,使之不操作,会使总操作减少。
当固定零以后,假设 i i i为 0 0 0第 i i i个位置后面均大于 0 0 0且递增,贪心来看我们让每个 b j b_j bj变成最小的大于前一个即可,同理 i i i前面的让其从零开始递减,等价于让 i i i前面变成 [ i − 1 , i − 2 , . . . , 1 ] [i-1,i-2,...,1] [i−1,i−2,...,1]这样递增的。
因此枚举每一个 i i i为 0 0 0,然后暴力跑一遍即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
LL a[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
cin >> n;
for (int i = 1; i <= n; i ++) cin >> a[i];
LL res = 1e18;
for (int i = 1; i <= n; i ++) {
LL t = 0;
LL s = 0;
for (int j = i + 1; j <= n; j ++) {
LL tt = (s + a[j] - 1) / a[j];
if (a[j] * tt == s) tt ++;
t += tt;
s = a[j] * tt;
}
s = 0;
for (int j = i - 1; j; j --) {
LL tt = (s + a[j] - 1) / a[j];
if (a[j] * tt == s) tt ++;
t += tt;
s = a[j] * tt;
}
res = min(res, t);
}
cout << res << '\n';
return 0;
}
D. Optimal Partition
[Link](Problem - D - Codeforces)
题意
给你一个数组 a a a,设 s s s为其前缀和,你可以将 a a a分成任意多的非空子数组,对于 a [ l , r ] a_{[l,r]} a[l,r]这个子数组,权值为:
-
s r − s l − 1 > 0 s_r-s_{l-1}>0 sr−sl−1>0 权值为 r − l + 1 r-l+1 r−l+1
-
s r − s l − 1 < 0 s_r-s_{l-1}<0 sr−sl−1<0 权值为 − ( r − l + 1 ) -(r-l+1) −(r−l+1)
-
s r − s l − 1 = 0 s_r-s_{l-1}=0 sr−sl−1=0 权值为 0 0 0
问你划分的子数组的权值和最大值为多少。
思路
- d p dp dp,优化
线段树
对于每一个 a i a_i ai一定是接到前面某一个段后面,因此可以写一个暴力的 n 2 n^2 n2的 d p dp dp:
for (int i = 1; i <= n; i ++) {
f[i] = -1e9;
for (int j = 0; j < i; j ++) {
if (a[i] > a[j]) f[i] = max(f[i], f[j] + i - j);
else if (a[i] < a[j]) f[i] = max(f[i], f[j] + j - i);
else f[i] = max(f[i], f[j]);
}
}
观察上面转移,发现对于 i i i前面小于 s i s_i si的数我们需要找 f j − j f_j-j fj−j的最大值,对于 i i i前面大于 s i s_i si的数我们需要找 f j + j f_j+j fj+j的最大值,对于等于 s i s_i si的数我们要找 f j f_j fj的最大值。相当于我们要查某个区间的最大值,对于区间我们可以开一个值域线段树,分别来维护 f j − j , f j + j , f j f_j-j,f_j+j,f_j fj−j,fj+j,fj对应 s i s_i si的最大值,由于 s i s_i si太大,并且我们只关注他们的相对大小,因此对 s i s_i si离散化一下,然后 d p dp dp即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 5e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
LL a[N], b[N];
int f[N];
struct Node {
int l, r;
int mx1, mx2, mx3;
}tr[N << 2];
void build(int u, int l, int r) {
tr[u] = {l, r, -INF, -INF, -INF};
if (l == r) return ;
int mid = l + r >> 1;
build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}
void push(int u) {
tr[u].mx1 = max(tr[u << 1].mx1, tr[u << 1 | 1].mx1);
tr[u].mx2 = max(tr[u << 1].mx2, tr[u << 1 | 1].mx2);
tr[u].mx3 = max(tr[u << 1].mx3, tr[u << 1 | 1].mx3);
}
void push(Node& rt, Node& ls, Node& rs) {
rt.mx1 = max(ls.mx1, rs.mx1);
rt.mx2 = max(ls.mx2, rs.mx2);
rt.mx3 = max(ls.mx3, rs.mx3);
}
void modify(int u, int x, int v, int p) {
if (tr[u].l == tr[u].r) {
tr[u].mx1 = max(tr[u].mx1, v - p);
tr[u].mx2 = max(tr[u].mx2, v + p);
tr[u].mx3 = max(tr[u].mx3, v);
return ;
}
int mid = tr[u].l + tr[u].r >> 1;
if (x <= mid) modify(u << 1, x, v, p);
else modify(u << 1 | 1, x, v, p);
push(u);
}
Node query(int u, int l, int r) {
if (l <= tr[u].l && tr[u].r <= r) return tr[u];
int mid = tr[u].l + tr[u].r >> 1;
if (r <= mid) return query(u << 1, l, r);
if (l > mid) return query(u << 1 | 1, l, r);
Node ls = query(u << 1, l, r), rs = query(u << 1 | 1, l, r);
Node rt;
push(rt, ls, rs);
return rt;
}
vector<LL> ve;
int find(LL x) {
return lower_bound(ve.begin(), ve.end(), x) - ve.begin() + 1;
}
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n;
a[0] = 0;
ve.clear();
for (int i = 1; i <= n; i ++) cin >> a[i], a[i] += a[i - 1], ve.push_back(a[i]);
ve.push_back(0);
sort(ve.begin(), ve.end());
ve.erase(unique(ve.begin(), ve.end()), ve.end());
m = ve.size();
for (int i = 0; i <= n; i ++) a[i] = find(a[i]);
build(1, 1, m);
modify(1, a[0], 0, 0);
for (int i = 1; i <= n; i ++) {
f[i] = -INF;
Node t;
if (a[i] > 1) {
t = query(1, 1, a[i] - 1);
f[i] = max(f[i], t.mx1 + i);
}
if (a[i] < m) {
t = query(1, a[i] + 1, m);
f[i] = max(f[i], t.mx2 - i);
}
t = query(1, a[i], a[i]);
f[i] = max(f[i], t.mx3);
modify(1, a[i], f[i], i);
}
cout << f[n] << '\n';
}
return 0;
}
树状数组
换一个方式 d p dp dp,对于每个数有两种选择要不就是和前面某个区间数续上,要不就是新开独自一个,如果和前面某个区间续上一定要 s j < s i s_j<s_i sj<si,如果 s j > s i s_j>s_i sj>si这个新区间的贡献就是负得没有意义。对于新开一个直接看它的正负,对于前面续上 f [ i ] = m a x ( f [ i ] , f [ j ] − j + i ) f[i]=max(f[i],f[j]-j + i) f[i]=max(f[i],f[j]−j+i),和上面类似搞一个值域树状数组,维护一下每个点的 f j − j f_j-j fj−j的最大值,然后 d p dp dp即可。
Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 5e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int n, m, k;
int a[N], tr[N];
LL s[N];
vector<LL> ve;
int find(LL x) {
return lower_bound(ve.begin(), ve.end(), x) - ve.begin() + 1;
}
void add(int x, int v) {
for (; x <= m; x += (x & -x)) tr[x] = max(tr[x], v);
}
int query(int x) {
int res = -INF;
for (; x; x -= (x & -x)) res = max(res, tr[x]);
return res;
}
int f[N];
int main() {
ios::sync_with_stdio(false), cin.tie(0);
int T;
cin >> T;
while (T -- ) {
cin >> n;
ve.clear(), s[0] = 0;
for (int i = 1; i <= n; i ++) cin >> a[i], s[i] = s[i - 1] + a[i], ve.push_back(s[i]);
ve.push_back(0);
sort(ve.begin(), ve.end());
ve.erase(unique(ve.begin(), ve.end()), ve.end());
for (int i = 0; i <= n; i ++) s[i] = find(s[i]);
m = ve.size();
for (int i = 1; i <= m; i ++) tr[i] = -INF;
for (int i = 1; i <= n; i ++) {
add(s[i - 1], f[i - 1] - (i - 1));
f[i] = f[i - 1];
if (a[i] > 0) f[i] ++;
else if (a[i] < 0) f[i] --;
f[i] = max(f[i], i + query(s[i] - 1));
}
cout << f[n] << '\n';
}
return 0;
}