2019杭电多校第十场补题(1008,1011)

1008: Coins

这题不能直接贪心,是因为a后面压着一个b,导致局部最优不是全局最优。
但是如果我们每次可以拿两个硬币,那我们就可以贪心:要么拿两个没被压的最大的,要么连取a,b。如果是这样的策略,就可以保证每一次取到的最大值都可以由前一次取到最大值的硬币状态转移过来。
那么我们就分奇偶讨论,从0个开始每次取两个硬币,从1个开始每次取两个硬币。这样就可以得到答案。
先把硬币按a+b排个序方便找当前可以取的最大的a+b,用一个优先队列维护当前可以直接取的硬币。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 50;
struct node{
    int a, b, id;
    node(int _a = 0, int _b = 0, int _id = 0):a(_a), b(_b), id(_id){}
    bool operator < (const node& x)const{
        if(a+b != x.a + x.b) return a+b < x.a + x.b;
        return id < x.id;
    }
}e[maxn];
priority_queue<node> q;
int vis[maxn], db[maxn];//已经用完/已经不是两个
int ans[maxn*2];
int n, p;
void sol(int pos){
    while(pos+2 <= 2*n){
        pos += 2;
        node t1, t2, t3;
        while(p >= 0 && db[p]) p--;
        if(p >= 0) t1 = e[p];//连取两个能得到的最大值

        while(q.size() && vis[q.top().id]) q.pop();
        if(q.size()) t2 = q.top(), q.pop();//不管被压的b,能取到的最大值

        while(q.size() && vis[q.top().id]) q.pop();
        if(q.size()) t3 = q.top(), q.pop();//不管被压的b,能取到的次大值

        if(t1.a + t1.b > t2.a+t2.b+t3.a+t3.b){//连取比较好
            ans[pos] = ans[pos-2] + t1.a + t1.b;
            db[p] = 1;
            vis[p] = 1;
            q.push(t2); q.push(t3);
        }
        else{
            ans[pos] = ans[pos-2] + t2.a+t2.b+t3.a+t3.b;

            if(t2.a) {//取的是a
                int id = t2.id; db[id] = 1;
                q.push(node(0, e[id].b, id));
            }
            else vis[t2.id] = 1;

            if(t3.a){
                int id = t3.id; db[id] = 1;
                q.push(node(0, e[id].b, id));
            }
            else vis[t3.id] = 1;

        }
    }
}
int main()
{
    int T;cin>>T;
    while(T--){
        scanf("%d", &n);
        while(q.size()) q.pop();
        for(int i = 0; i <n; ++i)
            scanf("%d%d", &e[i].a, &e[i].b);
        sort(e, e+n);
        //偶数
        for(int i = 0; i < n; ++i) e[i].id = i, q.push(node(e[i].a, 0, i));
        memset(vis, 0, n<<2); memset(db, 0, n<<2);
        ans[0] = 0; p = n-1;
        sol(0);
        //奇数
        while(q.size()) q.pop();
        memset(vis, 0, n<<2); memset(db, 0, n<<2);
        for(int i = 0; i < n; ++i)  q.push(node(e[i].a, 0, i));//初始化

        int id = q.top().id; q.pop();
        ans[1] = e[id].a;//算ans[1],取走最大的那个
        q.push(node(0, e[id].b, id));//把下面那个加入队列
        db[id] = 1; p = n-1;
        sol(1);
        for(int i = 1; i <= 2*n; ++i){
            if(i > 1) printf(" ");
            printf("%d", ans[i]);
        }printf("\n");
    }
}

1011:Make Rounddog Happy

方法1:
启发式分治。
考虑一种比较暴力的做法:对于每个值,找到以它为最大值的区间左端点/右端点,此时我们可以枚举包含它的区间的左端点或者右端点来统计这个值作为最大值对答案的贡献。这样的复杂度是O(n^2)。
用启发式分治来优化它。

什么是启发式?

在这题中,对于一个最大值,你可以选择枚举包含它的区间的左端点,也可以选择枚举右端点,你只要挑一种就可以。那么我们可以挑点数比较少的来枚举。
对于不重复的数字的限制,我们可以通过一个前缀DP和后缀DP得到每个位置向左和向右无重复数字最远的地方。
分治:
对于一个区间[l,r],假设它最大值位置为pos,以pos为分界点,统计[l,pos-1]和[pos+1,r]的答案,因为它们的最大值和pos没有关系。再看pos这个点对答案的影响:要得到[l,r]中所有包含pos的满足答案的区间,可以选择枚举左端点或右端点,那么选择比较少的来枚举就可以了。
获取区间最大值的位置可以用倍增实现。(st表)

#include<bits/stdc++.h>
#define ll long long
#define lowbit(x) ((x)&(-(x)))
using namespace std;
const int maxn = 3e5 + 50;
int L[maxn], R[maxn], st[maxn][20], lg[maxn]; ll ans;
int last[maxn];
int a[maxn];
int n, k;
int get_pos(int l, int r){
    int t = lg[r-l+1];
    if(a[st[l][t]] < a[ st[r-(1<<t)+1][t] ] ) return st[r-(1<<t)+1][t];
    else return st[l][t];
}
void sol(int l, int r){
    if(l > r) return;
    int pos = get_pos(l, r);//得到[l,r]区间最大值所在的位置
    if(pos-l < r-pos){//左边的点比较少
        int len = a[pos] - k;//长度的最小值
        for(int i = l; i <= pos; ++i){
            int up = min(R[i], r);//最远可以到达的不重复的地方
            int down = max(i+len-1, pos);//右端点至少要到达的地方
            if(up >= down) ans += up-down+1;
        }
    }
    else{
        int len = a[pos]-k;
        for(int i = r; i >= pos; --i){
            int down = max(L[i], l);//向左最远可以到达的不重复的地方
            int up = min(pos, i-len+1);
            if(up >= down) ans += up-down+1;
        }
    }
    sol(l, pos-1); sol(pos+1, r);
}
int main()
{
    lg[1] = 0;
    for(int i = 2; i < maxn; ++i) lg[i] = lg[i>>1]+1;
	int T;cin>>T;
	while(T--){
        scanf("%d%d", &n, &k);
        for(int i = 1; i <= n; ++i){
            scanf("%d", &a[i]);
            st[i][0] = i;
        }
        for(int j = 1; j < 20; ++j){
            for(int i = 1; i <= n+1-(1<<j); ++i){
                if(a[st[i][j-1]] > a[st[i+(1<<(j-1))][j-1]]) st[i][j] = st[i][j-1];
                else st[i][j] = st[i+(1<<(j-1))][j-1];
            }
        }
        memset(last, 0, (n+1)<<2);//
        for(int i = 1; i <= n; ++i){
            L[i] = max(L[i-1], last[a[i]]+1);//不重复情况下最左边可以到达的地方
            last[a[i]] = i;
        }
        for(int i = 1; i <= n; ++i) last[i] = n+1;
        R[n] = n;
        for(int i = n-1; i > 0; --i){
            R[i] = min(R[i+1], last[a[i]]-1);//不重复情况下最右边可以到达的地方
            last[a[i]] = i;
        }
        ans = 0;
        sol(1, n);//分治统计答案
        printf("%lld\n", ans);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值