Codeforces Round #783 (Div. 2)

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=biai,问你最少多少次操作可以让 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] [i1,i2,...,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 srsl1>0 权值为 r − l + 1 r-l+1 rl+1

  • s r − s l − 1 < 0 s_r-s_{l-1}<0 srsl1<0 权值为 − ( r − l + 1 ) -(r-l+1) (rl+1)

  • s r − s l − 1 = 0 s_r-s_{l-1}=0 srsl1=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 fjj的最大值,对于 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 fjj,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 fjj的最大值,然后 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值