线段树练习——单点更新(一)

hdu1166 http://acm.hdu.edu.cn/showproblem.php?pid=1166

题意:

中文不说了,大体意思是给定n个数;

接下来每行有一条命令,命令有4种形式:
(1) Add i j,i和j为正整数,表示第i个营地增加j个人(j不超过30)
(2)Sub i j ,i和j为正整数,表示第i个营地减少j个人(j不超过30);
(3)Query i j ,i和j为正整数,i<=j,表示询问第i到第j个营地的总人数;
(4)End 表示结束,这条命令在每组数据最后出现;

很纯的单点更新题目:

View Code
#include <cstdio>
#include <cstring>
#include <iostream>
#define maxn 50002
using namespace std;

int a[maxn*4];

void pushup(int rt)
{
    a[rt] = a[rt<<1] + a[rt<<1|1];
}
void build(int l,int r,int rt)
{
    if (l == r)
    {
        scanf("%d",&a[rt]);
        return ;
    }
    int m = (l + r)>>1;
    build(l,m,rt<<1);
    build(m + 1,r,rt<<1|1);
    pushup(rt);
}

void update(int pos,int sc,int l,int r,int rt)
{
    if (l == r)
    {
        a[rt] += sc;
        return ;
    }
    int m = (l + r)>>1;
    if (pos <= m) update(pos,sc,l,m,rt<<1);
    else update(pos,sc,m + 1,r,rt<<1|1);
    pushup(rt);
}

int query(int L,int R,int l,int r,int rt)
{
    if (l >= L && r <= R)
    {
        return a[rt];
    }
    int res = 0;
    int m = (l + r)>>1;
    if (L <= m) res += query(L,R,l,m,rt<<1);
    if (R > m) res += query(L,R,m + 1,r,rt<<1|1);
    return res;
}
int main()
{
    int t,x,y,n;
    int cas = 1;
    char op[6];
    scanf("%d",&t);
    while (t--)
    {
        printf("Case %d:\n",cas++);
        scanf("%d",&n);
        build(1,n,1);
        while (1)
        {
            scanf("%s",op);
            if (op[0] == 'E') break;
            scanf("%d%d",&x,&y);
            if (op[0] == 'A')
            update(x,y,1,n,1);
            else if (op[0] == 'S')
            update(x,-y,1,n,1);
            else
            printf("%d\n",query(x,y,1,n,1));
        }
    }
    return 0;
}

 

hdu 1754 http://acm.hdu.edu.cn/showproblem.php?pid=1754

中文题目,同样很纯的单点更新的题目,只不过不是求区间和罢了,而是求区间最值问题了。

注意:query时比较的是L,R。。

View Code
#include <iostream>
#include <cstring>
#include <cstdio>
#define maxn 2000002
using namespace std;

int val[maxn*4];

void pushup(int rt)
{
    val[rt] = max(val[rt<<1],val[rt<<1|1]);
}

void build(int l,int r,int rt)
{
    if (l == r)
    {
        scanf("%d",&val[rt]);
        return ;
    }
    int m = (l + r)>>1;
    build(l,m,rt<<1);
    build(m + 1,r,rt<<1|1);
    pushup(rt);
}
void update(int pos,int sc,int l,int r,int rt)
{
    if (l == r)
    {
        val[rt] = sc;
        return ;
    }
    int m = (l + r)>>1;
    if (pos <= m) update(pos,sc,l,m,rt<<1);
    else update(pos,sc,m + 1,r,rt<<1|1);
    pushup(rt);
}

int query(int L,int R,int l,int r,int rt)
{
    if (l >= L && r <= R)
    {
        return val[rt];
    }
    int res = 0;
    int m = (l + r)>>1;
    if (L <= m) res = max(res,query(L,R,l,m,rt<<1));
    if (R > m) res = max(res,query(L,R,m + 1,r,rt<<1|1));
    return res;
}
int main()
{
    int n,q;
    int x,y;
    char op[2];
    while (~scanf("%d%d",&n,&q))
    {
        build(1,n,1);
        while (q--)
        {
            scanf("%s%d%d",op,&x,&y);
            if (op[0] == 'U') update(x,y,1,n,1);
            else printf("%d\n",query(x,y,1,n,1));
        }
    }
    return 0;
}

 hdu 1394 http://acm.hdu.edu.cn/showproblem.php?pid=1394

题意:给定一个序列a[n],求将每个数a[i] (i从1到n)移到最后形成的逆序数的最小值(注意这里也要包括不移动时逆序数);

逆序数定义就是 is the number of pairs (ai, aj) that satisfy i < j and ai > aj.

首先用线段树结构的叶子节点存储每个位置的值是否出现,初始化为0都未出现,若出现则update为1,然后单点向上更新存储这一区间相应位置出现的个数。每次出现一个新数首先query然后再更新树。当这些数输入完毕,即得到没有移动时的逆序数,这里公式的推导:若将v[i]移到最后,则总的逆序数sum要加上比v[i]大的数的个数(n - v[i])减去比v[i]小的数的个数(v[i] - 1)注意:我的处理时从1开始到n的。

View Code
#include <iostream>
#include <cstring>
#include <cstdio>
#define maxn 5007
using namespace std;

int val[maxn*4],v[maxn];

void pushup(int rt)
{
    val[rt] = val[rt<<1] + val[rt<<1|1];
}

void update(int pos,int l,int r,int rt)
{
    if (l == r)
    {
        val[rt] = 1;
        return ;
    }
    int m = (l + r)>>1;
    if (pos <= m) update(pos,l,m,rt<<1);
    else update(pos,m + 1,r,rt<<1|1);
    pushup(rt);
}

int query(int L,int R,int l,int r,int rt)
{
    if (l >= L && r <= R) return val[rt];
    int res = 0;
    int m = (l + r)>>1;
    if (L <= m) res += query(L,R,l,m,rt<<1);
    if (R > m) res += query(L,R,m + 1,r,rt<<1|1);
    return res;
}
int main()
{
    int n;
    while (~scanf("%d",&n))
    {
        int sum = 0;
        for (int i = 0; i < 4*maxn; ++i) val[i] = 0;
        for (int i = 0; i < n; ++i)
        {
            scanf("%d",&v[i]);
            sum += query(v[i] + 1,n,1,n,1);
            update(v[i] + 1,1,n,1);
        }
        int tmp = sum;
        for (int i = 0; i < n; ++i)
        {
            tmp = tmp + (n - v[i] - 1) - (v[i]);
            sum = min(sum,tmp);
        }
        printf("%d\n",sum);
    }
    return 0;
}

 

hdu 2795 http://acm.hdu.edu.cn/showproblem.php?pid=2795

题意:

校园的广告片是h*w的矩形,现在给你n个1*wi的广告,往上面贴,必须满足如果最顶部宽度满足的话,一定要贴在最顶部(贴的广告都是靠最左的而且不会覆盖)。问给定的n个广告序列,被贴在第几行;

思路:

给定的h w 到了10^9很大,但是给定的n相对来说是小的,思考可知我们要以h建立线段树结构,而当出现h>n时,无需建立h个只需建立n个即可(此时如果一行贴一个的话肯定能把n个广告全部贴满)。建立线段树结构h个叶子节点存储w宽度,单点向上更新存放最大宽度,每次插入时检查该区间的最大宽度是否可以放下,可放即往下寻找。

View Code
#include <iostream>
#include <cstring>
#include <cstdio>
#define maxn 200002
using namespace std;

int val[maxn*4],w;

void pushup(int rt)
{
    val[rt] = max(val[rt<<1],val[rt<<1|1]);
}

void build(int l,int r,int rt)
{
    if (l == r)
    {
        val[rt] = w;
        return ;
    }
    int m = (l + r)>>1;
    build(l,m,rt<<1);
    build(m + 1,r,rt<<1|1);
    pushup(rt);
}

void query(int x,int l,int r,int rt)
{
    if (l == r)
    {
        printf("%d\n",l);
        val[rt] -= x;
        return;
    }
    int m = (l + r)>>1;
    if (x <= val[rt<<1]) query(x,l,m,rt<<1);
    else query(x,m + 1,r,rt<<1|1);
    pushup(rt);
}
int main()
{
    int h,n;
    int x;
    while (~scanf("%d%d%d",&h,&w,&n))
    {
        if (h > n) h = n;
        build(1,h,1);
        while (n--)
        {
            scanf("%d",&x);
            if (val[1] < x) puts("-1");
            else  query(x,1,h,1);
        }
    }
    return 0;
}

 

hdu 4217 http://acm.hdu.edu.cn/showproblem.php?pid=4217

CZ做的一道题目,我帮忙看了看。

题意:给定N个数(1---N),K个操作,然后给K数,i1,i2,i3.......ik 每次取走当前状态下等的第i个数(从小到大的),问取走的数的和。

给的那个的n为262144可以断定肯定是O(n*log(n))级的算法,所以选定线段树。

叶子节点赋值为1,这些节点存储的就是数的个数,在添加一个val数组记录是哪个数。每次更新将节点a[rt]变成0,找出对应的val[rt]即可。

这里坑爹的地方时sum求和时,要用—int64.wa了很多次才发现。

View Code
#include <iostream>
#include <cstring>
#include <cstdio>
#define maxn 262145
using namespace std;

int a[maxn*4],val[maxn*4];
int tmp;
__int64 sum;

void pushup(int rt)
{
    a[rt] = a[rt<<1] + a[rt<<1|1];
}
void build(int l,int r,int rt)
{
    if (l == r)
    {
        a[rt] = 1;
        val[rt] = ++tmp;
        return ;
    }
    int m = (l + r)>>1;
    build(l,m,rt<<1);
    build(m + 1,r,rt<<1|1);
    pushup(rt);
}
void update(int sc,int l,int r,int rt)
{
    if (l == r)
    {
        a[rt] = 0;
        sum += val[rt];
        return ;
    }
    int m = (l + r)>>1;
    if (sc <= a[rt<<1]) update(sc,l,m,rt<<1);
    else
    {
        sc -= a[rt<<1];
        update(sc,m + 1,r,rt<<1|1);
    }
    pushup(rt);
}
int main()
{
    int n,q,t,c;
    int cas = 1;
    scanf("%d",&t);
    while (t--)
    {
        for (int i = 0; i < 4*maxn; ++i) a[i] = val[i] = 0;
        tmp = 0;
        scanf("%d%d",&n,&q);
        sum = 0;
        build(1,n,1);
        while (q--)
        {
            scanf("%d",&c);
            update(c,1,n,1);
        }
        printf("Case %d: %I64d\n",cas++,sum);
    }
    return 0;
}

 pku2828  http://poj.org/problem?id=2828

题意:N个人排队买票,每个人对应两个值pos[i] val[i]分别表示第i个人要站在第pos[i]个人之后并且其对应价值为val[i]。问给出插队序列,输出最后从前到后的价值顺序。如果具有相同的pos[i],则肯定是最后一个查到第pos[i]之后,其余的会后移,所以此题的关键就是逆序建树,然后就是插入了,注意在左子树无法满足的条件下转向右子树时记得x -= len[rt<<1];减去左边的数。

View Code
#include <iostream>
#include <cstring>
#include <cstdio>
#include <vector>
#define maxn 200001
using namespace std;

int len[maxn*4];
int pos[maxn],val[maxn];
int ans[maxn];

void pushup(int rt)
{
    len[rt] = len[rt<<1] + len[rt<<1|1];
}
void build(int l,int r,int rt)
{
    if (l == r)
    {
        len[rt] = 1;
        return;
    }
    int m = (l + r)>>1;
    build(l,m,rt<<1);
    build(m + 1,r,rt<<1|1);
    pushup(rt);
}

int update(int x,int l,int r,int rt)
{
    if (l == r)
    {
        len[rt] = 0;
        return l;
    }
    int res = 0;
    int m = (l + r)>>1;
    if (x <= len[rt<<1]) res = update(x,l,m,rt<<1);
    else
    {
        x -= len[rt<<1];
        res = update(x,m + 1,r,rt<<1|1);
    }
    pushup(rt);
    return res;
}

int main()
{
    int n,i;
    while (~scanf("%d",&n))
    {
        for (i = 0; i < n; ++i) scanf("%d%d",&pos[i],&val[i]);
        build(1,n,1);
        for (i = n - 1; i >= 0; --i)
        {
            int p = update(pos[i] + 1,1,n,1);
            ans[p] = val[i];
        }
        for (i = 1; i < n; ++i) printf("%d ",ans[i]);
        printf("%d\n",ans[n]);
    }
    return 0;
}

 pku http://poj.org/problem?id=2886

题意:

N个小孩编号从1到n按顺时针在一个圆圈上完约瑟夫环游戏,每个人有相应的名字与val[i](表示在他之后下一个跳出圆圈的人)。每个人跳出圆圈时会得到f(p)个糖果p表示他是第几个跳出圆圈的人,f(p)表示p的约数的个数。求最大的f(p),并输出其姓名。

思路:

约瑟夫环+反素数。数据量很大模拟约瑟夫环肯定超时,所以要用n*log(n)的数据结构来解--》线段树  线段树来存储区间有多少个未跳出圆圈的点,每次计算出下一个点的相对位置,然后在线段树里面查找,并返回其绝对位置即可。

关键是计算相对位置的公式,我自己不知道怎么推出来的,希望大牛能够指点这里只能记下来了:

            if (val[pos] > 0)//如果是正数,那么下一个的相对位置为
                k = (k-1+val[pos]-1)%tmp+1;//tmp是记录的当前未出队列的总数
            else
                k =  ((k-1+val[pos])%tmp+tmp)%tmp+1;

然后反素数就是一个打表过程了打表函数:

反素数打表 <= 600000

#include<iostream>
#include<cstring>
#include <cstdio>
#define maxn 600000
using namespace std;

int dp[600001];
int main()
{
    int i,j;
    freopen("out.txt","w",stdout);
    memset(dp,0,sizeof(dp));
    for(i=1;i<=maxn;i++)
    {
        for(j=1;i*j<=maxn;j++)
        {
                dp[i*j]++;
        }
    }
    int max=0;
    for(i=2;i<=maxn;i++)
    {
        if(dp[i]>max)
        {
            max=dp[i];
            cout<<i<<",";
        }
    }
    cout<<endl<<endl;
    max=0;
    for(i=2;i<=maxn;i++)
    {
        if(dp[i]>max)
        {
            max=dp[i];
            cout<<dp[i]<<",";
        }
    }
    return 0;
}

所以题解为:

#include<iostream>
#include<cstring>
#include <cstdio>
#define maxn 500007
using namespace std;

int antip[] = {1,2,4,6,12,24,36,48,60,120,180,240,360,720,
840,1260,1680,2520,5040,7560,10080,15120,20160,25200,27720,
45360,50400,55440,83160,110880,166320,221760,277200,
332640,498960,554400};
int pnum[] = {1,2,3,4,6,8,9,10,12,16,18,20,24,30,32,36,40,48,
60,64,72,80,84,90,96,100,108,120,128,144,160,168,180,192,200,
216};

int val[maxn],v[maxn*4];
char name[maxn][11];


void build(int l,int r,int rt)
{
    v[rt] = r - l + 1;//没有pushup的形式
    if (l == r) return ;
    int m = (l + r)>>1;
    build(l,m,rt<<1);
    build(m + 1,r,rt<<1|1);
}

int update(int sc,int l,int r,int rt)
{
    v[rt]--;//没有pushup的形式
    if (l == r) return l;
    int m = (l + r)>>1;
    int res;
    if (sc <= v[rt<<1]) res =  update(sc,l,m,rt<<1);
    else
    {
        sc -= v[rt<<1];
        res = update(sc,m + 1,r,rt<<1|1);
    }
    return res;
}
int main()
{
    int n,k,i;
    while (~scanf("%d%d",&n,&k))
    {
        build(1,n,1);
        int bsum = 0,bnum = 0;
        i = 0;

        while (antip[i] <= n) i++; 
        bnum = antip[i - 1];
        bsum = pnum[i - 1];
       

        for (i = 1; i <= n; ++i) scanf("%s%d",name[i],&val[i]);
        int pos;
        int tmp = n;
        for (i = 1; i <= bnum; ++i)
        {
            pos = update(k,1,n,1);
            tmp--;
            if (tmp == 0) break;
            if (val[pos] > 0)//如果是正数,那么下一个的相对位置为
                k = (k-1+val[pos]-1)%tmp+1;//tmp是记录的当前未出队列的总数
            else
                k =  ((k-1+val[pos])%tmp+tmp)%tmp+1;
        }
        printf("%s %d\n",name[pos],bsum);

    }
}

 

上边的公式没有推出来,不过听说这道题目是经典的二分与线段数的题目,于是又做了一下。线段树存储左右区间未出圆圈的人数,相当于二分时左右区间。于是这里就好理解了,不过我认为最难理解的还是计算k的相对编号了。

if (img[l] > 0) k--;
k = ((k + img[l])%len + len)%len;
if (k == 0) k = len;

k表示的是当前出去的人,len表示的是的当前k出去之后剩余的人数,

1 2 3 4 5 假设2已经出去,当前是3出去,img[3] == 2 则下一个出去的是5,我们只要从在3+2 - 1即可得到下一个出圈人的相对编号(因为3后边的4,5原来的相对编号都-1了),所以img[l]>0时k--

如果是负数就是3 + -2了因为3前边的编号都没变,所以不用-1。还有这事1 2 3 4 不是 0 1 2 3 4所以要注意0的处理。

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#define maxn 500007
using namespace std;

int val[4*maxn];
int len,bnum,bsum,k,pos;
int antip[] = {1,2,4,6,12,24,36,48,60,120,180,240,360,720,
840,1260,1680,2520,5040,7560,10080,15120,20160,25200,27720,
45360,50400,55440,83160,110880,166320,221760,277200,
332640,498960,554400};
int pnum[] = {1,2,3,4,6,8,9,10,12,16,18,20,24,30,32,36,40,48,
60,64,72,80,84,90,96,100,108,120,128,144,160,168,180,192,200,
216};
char name[maxn][12];
int img[maxn];

void build(int l,int r,int rt)
{
    val[rt] = r - l + 1;
    if (l == r) return ;
    int m = (l + r)>>1;
    build(l,m,rt<<1);
    build(m + 1,r,rt<<1|1);
}
void update(int sc,int l,int r,int rt)
{
    val[rt]--;
    if (l == r)
    {
        pos = l;
       // printf(">>%d\n",pos);
        if (len == 0) return;
        if (img[l] > 0) k--;
        k = ((k + img[l])%len + len)%len;
        if (k == 0) k = len;
        return ;
    }
    int m = (l + r)>>1;
    if (sc <= val[rt<<1]) update(sc,l,m,rt<<1);
    else
    {
        sc -= val[rt<<1];
        update(sc,m + 1,r,rt<<1|1);
    }
}
int main()
{
   int i,n;
   while (~scanf("%d%d",&n,&k))
   {
       for (i = 1; i <= n; ++i) scanf("%s%d",name[i],&img[i]);
       bsum = bnum = 0;
       i = 0;
       while (antip[i] <= n) ++i;
       bnum = antip[i - 1];
       bsum = pnum[i - 1];
       build(1,n,1);
       len = n;
       for (i = 1; i <= bnum; ++i)
       {
           len--;
           update(k,1,n,1);
       }
       printf("%s %d\n",name[pos],bsum);
   }
    return 0;
}

 hdu 4302 http://acm.hdu.edu.cn/showproblem.php?pid=4302

题意:

Holedox 在一个长度为L的管道内吃蛋糕,0 y表示在y点落下蛋糕, 1表示Holedox 要吃蛋糕,Holedox 肯定吃离他最近的,如果左边没有去右边吃,如果右边没有去左边吃,如果整个管道都没有就在原地不动,如果左右两边都有,吃离他最近的,如果两边最近距离相同按照上一次的方向吃。问Holedox 经过如上操作后所行走的距离。

线段树做法:

思路:

二分区间[0,cur] [cur,0] cur表示当前位置,线段树求区间长度,以及被吃蛋糕的坐标。

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#define maxn 100007
using namespace std;

int val[4*maxn];

void pushup(int rt)
{
    val[rt] = val[rt<<1] + val[rt<<1|1];
}

void update(int pos,int sc,int l,int r,int rt)
{
    if (l == r)
    {
        val[rt] += sc;
        return ;
    }
    int m = (l + r)>>1;
    if (pos <= m) update(pos,sc,l,m,rt<<1);
    else update(pos,sc,m + 1,r,rt<<1|1);
    pushup(rt);
}

int queryS(int L,int R,int l,int r,int rt)
{
    if (l >= L && r <= R) return val[rt];
    int res = 0;
    int m = (l + r)>>1;
    if (L <= m) res += queryS(L,R,l,m,rt<<1);
    if (R > m) res += queryS(L,R,m + 1,r,rt<<1|1);
    return res;
}

int queryP1(int sc,int l,int r,int rt)
{
    if (l == r) return l;
    int res;
    int m = (l + r)>>1;
    if (sc <= val[rt<<1]) res = queryP1(sc,l,m,rt<<1);
    else
    {
        sc -= val[rt<<1];
        res = queryP1(sc,m + 1,r,rt<<1|1);
    }
    return res;
}
int queryP2(int sc,int l,int r,int rt)
{
    if (l == r) return l;
    int res;
    int m = (l + r)>>1;
    if (sc <= val[rt<<1|1]) res = queryP2(sc,m + 1,r,rt<<1|1);
    else
    {
        sc -= val[rt<<1|1];
        res = queryP2(sc,l,m,rt<<1);
    }
    return res;
}
int main()
{
    int t,x,y,n,q;
    int cas = 1;
    scanf("%d",&t);
    while (t--)
    {
        int cur = 0;
        int d = 1;
        int ans = 0;
        memset(val,0,sizeof(val));
        scanf("%d%d",&n,&q);
        while (q--)
        {
            scanf("%d",&x);
            if (x == 0)
            {
                scanf("%d",&y);
                update(y,1,0,n,1);//单点更新
            }
            else
            {
                if (val[1] == 0) continue;//不存在蛋糕
                //求区间长度
                int s1 = queryS(0,cur,0,n,1);
                int s2 = queryS(cur,n,0,n,1);
                //左边无蛋糕
                if (!s1)
                {
                    d = 1;
                    int pos = queryP2(s2,0,n,1);
                    ans += (pos - cur);
                    cur = pos;
                    update(pos,-1,0,n,1);
                }
                //右边无蛋糕
                else if (!s2)
                {
                    d = 0;
                    int pos = queryP1(s1,0,n,1);
                    ans += (cur - pos);
                    cur = pos;
                    update(pos,-1,0,n,1);
                }
                else
                {
                    int pos1 = queryP1(s1,0,n,1);
                    int pos2 = queryP2(s2,0,n,1);
                    //最近点在左边
                    if (cur - pos1 < pos2 - cur)
                    {
                        d = 0;
                        ans += (cur - pos1);
                        cur = pos1;
                        update(pos1,-1,0,n,1);
                    }
                    //在右边
                    else if (cur - pos1 > pos2 - cur)
                    {
                        d = 1;
                        ans += (pos2 - cur);
                        cur = pos2;
                        update(pos2,-1,0,n,1);
                    }
                    else
                    {
                        if (d)
                        {
                            d = 1;
                            ans += (pos2 - cur);
                            cur = pos2;
                            update(pos2,-1,0,n,1);
                        }
                        else
                        {
                             d = 0;
                            ans += (cur - pos1);
                            cur = pos1;
                            update(pos1,-1,0,n,1);
                        }
                    }
                }
            }
        }
        printf("Case %d: %d\n",cas++,ans);
    }
}

 

set做法(利用set内部红黑树log(n)的排序)

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <set>
#define maxn 100007
using namespace std;

const int inf = 999999999;
multiset<int>Q;
int main()
{
    int t,x,y;
    int n,m;
    int cas = 1;
    scanf("%d",&t);
    while (t--)
    {
        int cur = 0,pre = 0;
        int ans = 0;
        scanf("%d%d",&n,&m);
        multiset<int>::iterator it,del;
        Q.clear();
        while (m--)
        {
            scanf("%d",&x);
            if (x == 0)
            {
                scanf("%d",&y);
                Q.insert(y);
            }
            else
            {
                if (Q.size() == 0) continue;
                int mi = inf;
                for (it = Q.begin(); it != Q.end(); ++it)
                {
                    if (abs(*it - cur) < mi)
                    {
                        mi = abs(*it - cur);
                        del = it;
                    }
                }
                if (mi != inf)
                {
                    for (it = Q.begin(); it != Q.end(); ++it)
                    {
                         if (abs(*it - cur) == mi)
                         {
                             if ((cur - pre)*(*it - cur) > 0)
                             {
                                del = it;
                             }
                         }
                     }
                }
                pre = cur;
                cur = *del;
                Q.erase(del);
                ans += mi;

            }
        }
        printf("Case %d: %d\n",cas++,ans);
    }
    return 0;
}

 优先队列做法:利用左右两边一个出最大,一个出最小。

View Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <set>
#include <queue>
#define maxn 100007
using namespace std;


priority_queue<int,vector<int>,greater<int> >qmin;
priority_queue<int,vector<int>,less<int> >qmax;
int sum[maxn];

int main()
{
    //freopen("4302.txt","r",stdin);
    int t,x,y;
    int n,m;
    int ans,cas = 1;
    scanf("%d",&t);
    while (t--)
    {
        ans = 0;
        int d = 1,cur = 0;
        while (!qmax.empty()) qmax.pop();
        while (!qmin.empty()) qmin.pop();
        memset(sum,0,sizeof(sum));

        scanf("%d%d",&n,&m);
        while (m--)
        {
            scanf("%d",&x);
            if (x == 0)
            {
                scanf("%d",&y);
                sum[y]++;
                //注意不能把cur本身加入
                if (y < cur)
                {
                    if (sum[y] == 1)
                    qmax.push(y);
                }
                else if (y > cur)
                {
                    if (sum[y] == 1)
                    qmin.push(y);
                }
            }
            else
            {
                if (sum[cur])
                {
                    sum[cur]--;
                    continue;
                }
                int lsz = qmax.size();
                int rsz = qmin.size();
                if (lsz == 0 && rsz == 0) continue;
                if (lsz == 0)
                {
                    d = 1;
                    int r = qmin.top();
                    ans += (r - cur);
                    cur = r; qmin.pop();
                    sum[cur]--;
                }
                else if (rsz == 0)
                {
                    d  = 0;
                    int l = qmax.top();
                    ans += (cur - l);
                    cur = l; qmax.pop();
                    sum[cur]--;
                }
                else
                {
                   int l = qmax.top();
                   int r = qmin.top();
                   if (cur - l < r - cur)
                   {
                       d = 0;
                       ans += (cur - l);
                       cur = l; qmax.pop();
                       sum[cur]--;
                   }
                   else if (cur - l > r - cur)
                   {
                       d = 1;
                      ans += (r - cur);
                      cur = r; qmin.pop();
                       sum[cur]--;
                   }
                   else
                   {
                       if (d)
                       {
                           ans += (r - cur);
                           cur = r; qmin.pop();
                           sum[cur]--;
                       }
                       else
                       {
                           ans += (cur - l);
                           cur = l; qmax.pop();
                           sum[cur]--;
                       }
                   }
                }
            }
        }
        printf("Case %d: %d\n",cas++,ans);
    }
    return 0;
}

分别对应优先队列,set,线段树。。

 

就做的几个题来说,单点更新有这么几种

1:节点存储区间和;2:节点存储区间最值;3:节点存储区间满足条件点的个数;

 

 

你可能感兴趣的:(线段树)