hihocoder[Offer收割]编程练习赛50 题解

题目1 : 循环数组
考虑枚举从位置i断开,移到前面。那么需要确保从当前位置i开始的前缀和都大于0。记此时i到n的总和为x,那么若x大于从1开始到i-1的前缀和中的最小值,则i必然可行。

所以需要维护的东西有,从i位置开始到n的前缀和,为了支持i到i+1的数值变化,需要用一个支持区间加法的线段树。维护从1开始的前缀和的最小值可以用前缀和数组来完成,就酱。

#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double pi = acos(-1.0);

typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
    a = max(a, b);
}
void umin(int &a, int b) {
    a = min(a, b);
}

void file() {
    freopen("a.in", "r", stdin);
//    freopen("1.txt", "w", stdout);
}

namespace Solver {
    const int maxn = 110000;
    int n, A[maxn];
    LL pre[maxn], minv[maxn], suf[maxn];

    #define lc rt<<1
    #define rc rt<<1|1
    #define lson l, m, lc
    #define rson m+1, r, rc
    struct A {
        LL add, minv;
    }tr[maxn << 2];

    LL PushUp(int rt) {
        tr[rt].minv = min(tr[lc].minv, tr[rc].minv);
    }

    LL PushDown(int rt, int len) {
        if(tr[rt].add) {
            tr[lc].minv += tr[rt].add, tr[rc].minv += tr[rt].add;
            tr[lc].add += tr[rt].add, tr[rc].add += tr[rt].add;
            tr[rt].add = 0;
        }
    }

    void Add(int L, int R, LL add, int l, int r, int rt) {
        if(L > R) return ;
        if(L <= l && R >= r) {
            tr[rt].add += add;
            tr[rt].minv += add;
            return ;
        }
        PushDown(rt, r - l + 1);
        int m = (l + r) >> 1;
        if(L <= m) Add(L, R, add, lson);
        if(R > m) Add(L, R, add, rson);
        PushUp(rt);
    }

    LL Query(int L, int R, int l, int r, int rt) {
        if(L <= l && R >= r) {
            return tr[rt].minv;
        }
        PushDown(rt, r - l +1);
        int m = (l + r) >> 1;
        LL ans = 1e18;
        if(L <= m) ans = min(ans, Query(L, R, lson));
        if(R > m) ans = min(ans, Query(L, R, rson));
        return ans;
    }
    void solve() {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%d", &A[i]);
        pre[1] = A[1], minv[1] = A[1];
        for(int i = 2; i <= n; i++) {
            pre[i] = pre[i-1] + A[i];
            minv[i] = min(pre[i], minv[i-1]);
        }
        for(int i = 1; i <= n; i++)
            Add(i, i, pre[i], 1, n, 1);
        for(int i = 1; i <= n; i++) {
//            cout<<Query(n, n, 1, n, 1)<<" "<<pre[i-1]<<" "<<i<<endl;
            if(Query(i, n, 1, n, 1) <= 0) {
                Add(i+1, n, -A[i], 1, n, 1);
                continue;
            } else {
                LL v = Query(n, n, 1, n, 1);
                if(v + minv[i-1] > 0) {
                    cout<<i<<endl;
                    return ;
                }
                Add(i+1, n, -A[i], 1, n, 1);
            }
        }
        puts("-1");
    }
};

int main() {
//    file();
    Solver::solve();
    return 0;
}

题目2 : 座位问题
可以发现每次观众会找到一段连续的空位最多的线段,然后坐在中间。
区间长度为偶数就坐在靠左的位置。拿优先队列或set维护下就可以了。

#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double pi = acos(-1.0);

typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
    a = max(a, b);
}
void umin(int &a, int b) {
    a = min(a, b);
}

void file() {
    freopen("a.in", "r", stdin);
//    freopen("1.txt", "w", stdout);
}

namespace Solver {
    const int maxn = 110000;
    int n, m, k;
    int v[maxn];
    struct A {
        int x, l;
        bool operator < (const A & b) const {
            return x > b.x || (x == b.x && l < b.l);
        }
    };
    void solve() {
        scanf("%d%d%d", &n, &m, &k);
        for(int i = 1; i <= m; i++) {
            int val;
            scanf("%d", &val);
            v[val] = 1;
        }
        int p = 1, cnt = (v[1] == 0);
        set<A> st;
        for(int i = 2; i <= n; i++) {
            if(cnt == 0 && v[i] == 0) p = i;
            if(v[i] == 0) cnt++;
            else {
                st.insert({cnt, p});
                cnt = 0;
            }
        }
        if(cnt) st.insert({cnt, p});
        for(int i = 1; i <= k; i++) {
            A x = *st.begin();
            st.erase(st.begin());
            int id = (x.l + (x.x-1)/2);
            int ed = x.l + x.x - 1;
//            cout<<x.l<<" "<<id<<" "<<ed<<" denig "<<endl;
            printf("%d\n", id);
//            cout<<x.l<<" "
            st.insert({id-x.l, x.l});
            st.insert({ed - id, id+1});
        }
    }
};

int main() {
//    file();
    Solver::solve();
    return 0;
}

题目3 : 末尾有最多0的乘积
0的个数只和2还有5的幂次有关。
因此把每个数预处理,然后就是背包问题。
dp[i][j][k] 代表 对于当前决策第i个物品用不用,已经选了j个物品,其中这个乘积包含5的幂次为k的情况下最多包含2的幂次的个数。
显然k最多到1400。
然后枚举选或者不选转移即可。

#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double pi = acos(-1.0);

typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
    a = max(a, b);
}
void umin(int &a, int b) {
    a = min(a, b);
}

void file() {
    freopen("a.in", "r", stdin);
//    freopen("1.txt", "w", stdout);
}

namespace Solver {
    const int maxn = 111;
    int n, m;
    int v[maxn], v2[maxn], v5[maxn];
    int dp[2][101][1500];
    void solve() {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++) scanf("%d", &v[i]);
        for(int i = 1; i <= n; i++) {
            while(v[i]%2==0) v2[i]++, v[i]/=2;
            while(v[i]%5==0) v5[i]++, v[i]/=5;
        }
        LL sum1, sum2; sum1 = sum2 = 0;
        memset(dp, -0x3f3f3f3f, sizeof dp);
        dp[0][0][0] = 0;
        for(int i = 1; i <= n; i++) {
            sum2 += v5[i];
            int now = i & 1, pre = now ^ 1;
            for(int j = 0; j <= m; j++)
                for(int k = 0; k <= sum2; k++)
                    dp[now][j][k] = dp[pre][j][k];
            for(int j = 1; j <= i; j++)
                for(int k = v5[i]; k <= sum2; k++)
                    if(dp[pre][j-1][k-v5[i]] >= 0)
                        dp[now][j][k] = max(dp[pre][j-1][k - v5[i]] + v2[i], dp[now][j][k]);
        }
        int ans = 0;
        for(int i = 0; i < 1500; i++) {
            if(dp[n&1][m][i] >= 0)
                ans = max(ans, min(dp[n&1][m][i], i));
        }
        cout<<ans<<endl;
    }
};

int main() {
//    file();
    Solver::solve();
    return 0;
}

题目4 : 麻烦的第K大问题
数据出弱了…
枚举区间的起始位置,枚举区间的长度,然后用主席树查询区间第k大,总复杂度O(n*(n/len)*logn)。
就酱= =

#define others
#ifdef poj
#include <iostream>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <string>
#include <map>
#include <set>
#endif // poj
#ifdef others
#include <bits/stdc++.h>
#endif // others
//#define file
#define all(x) x.begin(), x.end()
using namespace std;
const double pi = acos(-1.0);

typedef long long LL;
typedef unsigned long long ULL;
void umax(int &a, int b) {
    a = max(a, b);
}
void umin(int &a, int b) {
    a = min(a, b);
}

void file() {
    freopen("a.in", "r", stdin);
//    freopen("1.txt", "w", stdout);
}

namespace Solver {
    const int maxn = 110000;
    using namespace std;
    struct seg{int l, r, sum;}tr[maxn*40];
    int cnt, n, m, root[maxn], tmp, arr[maxn], v[maxn], k;

    void update(int &x, int y, int l, int r, int pos, int val){//单点更新
        int pre = x;//若带修改操作,则这里要查看是否有历史版本
    x = ++cnt;tr[x] = pre? tr[pre] : tr[y];tr[x].sum += val;//每次更新重新建树链
    if(l == r) return ;//更新到单点,更新完成
        int m = (l+r)/2;//对所有包含pos的区间进行更新。
    if(pos <= m) update(tr[x].l, tr[y].l, l, m, pos, val);
        else update(tr[x].r, tr[y].r, m+1, r, pos, val);
    }
    int query(int x, int y, int l, int r, int k){
        if(l == r) return l;//二分锁定第k大
        int m = (l+r)/2;
        int sum = tr[tr[y].l].sum - tr[tr[x].l].sum;//若两个版本的左子树上差值已经大于k,则第k大在左子树上
        if(sum >= k) return query(tr[x].l, tr[y].l, l, m, k);
        else return query(tr[x].r, tr[y].r, m+1, r, k-sum);
    }
    int len, y;
    vector<int> G;
    bool cmp(int a, int b) {
        return a > b;
    }
    void solve() {
        scanf("%d%d%d%d", &n, &len, &y, &k);
        for(int i = 1; i <= n; i++) scanf("%d", &v[i]), update(root[i], root[i-1], 1, n, v[i], 1);
        for(int i = 1; i <= n; i++)
            for(int j = 1; i + j * len - 1<= n; j++) {
                int d = i+j*len-1 - i + 1;
//            cout<<d<<endl;
                G.push_back(query(root[i-1], root[i+j*len-1], 1, n, d+1-j*y));
            }
        sort(all(G), cmp);
//        for(auto v:G) cout<<v<<endl;
        cout<<G[k-1]<<endl;
    }
};

int main() {
//    file();
    Solver::solve();
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值