zoukankan      html  css  js  c++  java
  • CF 552(div 3) E Two Teams 线段树,模拟链表

    题目链接:http://codeforces.com/contest/1154/problem/E

    题意:两个人轮流取最大值与旁边k个数,问最后这所有的数分别被谁给取走了

    分析:看这道题一点思路都没有,想不到模拟链表也该能想到线段树的啊

    大部分AC代码是模拟链表的,写起来也更快,但我线段树很不熟,线段树的代码也写一份吧

    另外,以后要养成一种习惯,除了主函数定义int main里有int外,其他地方统一用ll了,不要自己给自己挖坑。。。。。

    线段树:

    意识到是线段树后我建树部分就拿不准怎么做,事实上可以一开始先输入在数组里,建树部分代码稍微改动一下就好

    代码本身不难,就着注释读一读很明显,要注意的部分我也提到了

    这次的模板我是用的CF大佬的,感觉很好用

    然后快读和快写效果十分显著,直接从102ms变成了62ms

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int inf=1<<30;
    const int maxn=2*1e5+7;
    const double pi=acos(-1);
    const int mod=1e9+7;
    int a[maxn],ans[maxn],pre[maxn],nxt[maxn],pos[maxn];
    struct node
    {
        int l,r,sum,lazy;
    }tree[4*maxn];
    inline ll read(){
        ll x=0,tmp=1;
        char ch=getchar();
        while(!isdigit(ch)){
            if(ch=='-') tmp=-1;
            ch=getchar();
        }
        while(isdigit(ch)){
            x=(x<<3)+(x<<1)+ch-48;
            ch=getchar();
        }
        return tmp*x;
    }
    
    inline void write(ll x){
        ll y=10,len=1;
        while(y<=x){
            y=(y<<3)+(y<<1);
            len++;
        }
        while(len--){
            y/=10;
            putchar(x/y+48);
            x%=y;
        }
    }
    inline void pushdown(ll p){
        if(tree[p].lazy){
            tree[p<<1].sum=0; tree[p<<1].lazy=1;
            tree[p<<1|1].sum=0; tree[p<<1|1].lazy=1;
            tree[p].lazy=0;
        }
    }
    
    void build(ll p,ll l,ll r){
        tree[p].l=l; tree[p].r=r;
        if(l==r){
            tree[p].sum=a[l];
            return;
        }
        ll mid=(l+r)>>1;
        build(p<<1,l,mid);
        build(p<<1|1,mid+1,r);
        tree[p].sum=max(tree[p<<1].sum,tree[p<<1|1].sum);
    }
    void update(ll p,ll l,ll r){
        if(l<=tree[p].l&&tree[p].r<=r){
            tree[p].sum=0; tree[p].lazy=1;
            return;
        }
        pushdown(p);
        ll mid=(tree[p].l+tree[p].r)>>1;
        if(l<=mid) update(p<<1,l,r);
        if(r>mid) update(p<<1|1,l,r);
        tree[p].sum=max(tree[p<<1].sum,tree[p<<1|1].sum);
    }
    ll query(ll p,ll l,ll r){
        if(l<=tree[p].l&&tree[p].r<=r) return tree[p].sum;
        pushdown(p);
        ll mid=(tree[p].l+tree[p].r)>>1,ans=0;
        if(l<=mid) ans=max(ans,query(p<<1,l,r));
        if(r>mid) ans=max(ans,query(p<<1|1,l,r));
        tree[p].sum=max(tree[p<<1].sum,tree[p<<1|1].sum);
        return ans;
    }
    int main(){
        ll n,k;scanf("%I64d%I64d",&n,&k);
        nxt[0]=1,pre[n+1]=n;//这点要注意别忘了 
        for(ll i=1;i<=n;i++){
            a[i]=read();
            pos[a[i]]=i;
            pre[i]=i-1;
            nxt[i]=i+1;//咋一看似乎没必要,但取走操作之后可能第5个人的前一个人就不是第四个人而是第二个人了 
        }
        build(1,1,n);
        ll now=0;//这个now是第几组的意思,弄成0,1方便异或更改 
        while(1){
            ll x=query(1,1,n);
            if(x<=0) break;
            ans[pos[x]]=now;
            ll l=pos[x],r=pos[x];
            for(ll i=1;i<=k;i++){
                if(pre[l]==0) break;
                else {
                    l=pre[l];
                    ans[l]=now;
                }
            }
            for(ll i=1;i<=k;i++){
                if(nxt[r]==n+1) break;
                else{
                    r=nxt[r];
                    ans[r]=now;
                }
            }
            update(1,l,r);
            pre[nxt[r]]=pre[l];
            nxt[pre[l]]=nxt[r];//这个更新很灵性吧,就是把最右边的前一位更改为最左边的前一位,最左边的后一位
            //更改为最右边的后一位,也就是把中间被选走的数的影响给剔除,也是nxt和pre数组的意义所在 
            now^=1;
        }
        for(int i=1;i<=n;i++)write(ans[i]+1);
        putchar('
    '); 
        return 0;
    }

    模拟链表:

    就是用两个优先队列来模拟链表,优先队列内的数是按顺序从大到小存好的,刚好满足要求

    看懂了线段树看这个肯定没有一点问题:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int inf=1<<30;
    const int maxn=2*1e5+7;
    const double pi=acos(-1);
    const int mod=1e9+7;
    typedef pair<int,int> P;
    int pre[maxn],nxt[maxn],a[maxn],ans[maxn];
    priority_queue<P> p,q;
    int main(){
        int n,k;scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            pre[i]=i-1,nxt[i]=i+1;
            q.push(P(a[i],i));
        }
        int opt=0;
        while(!q.empty()){
            while(p.size()&&q.top()==p.top()) q.pop(),p.pop();
            if(q.empty()) break;
            int m,i,j;
            //求前缀
            for(i=pre[q.top().second],j=1;j<=k&&i;j++,i=pre[i]){
                p.push(P(a[i],i));
                ans[i]=opt+1;
            }
            for(m=nxt[q.top().second],j=1;j<=k&&m;j++,m=nxt[m]){
                p.push(P(a[m],m));
                ans[m]=opt+1;
            }
            ans[q.top().second]=opt+1;q.pop();
            opt^=1;
            nxt[i]=m,pre[m]=i;
        }
        for(int i=1;i<=n;i++)cout<<ans[i];
        cout<<endl;
        return 0;
    }
  • 相关阅读:
    551. 学生出勤纪录 I
    387. 字符串中的第一个唯一字符
    剑指OFFER 字符串的排列
    剑指OFFER 斐波那契数列
    剑指OFFER 重建二叉树
    剑指OFFER 二维数组中的查找
    剑指OFFER 和为S的两个数字
    剑指OFFER 左旋转字符串
    剑指OFFER 合并两个排序的链表
    剑指OFFER 从尾到头打印链表
  • 原文地址:https://www.cnblogs.com/qingjiuling/p/10732297.html
Copyright © 2011-2022 走看看