《挑战程序设计竞赛》3.3.2 数据结构-线段树和平方分割 POJ2991 3264 2104 3468 3368 3470 1201 UVA11990(4)


POJ2991 起重机

http://poj.org/problem?id=2991

题意

有一个吊车由很多个不同长度的线段组成,一开始是一条长直线起点在(0,0),尾节点在(0,sum[n]),每条线段之间的夹角的初始值是180度。然后有一些操作a、 b将第a条线段和a+1之间的夹角变成b度,经过每一次操作都要求出尾节点的坐标。

思路

线段树区间更新。
结点值保存该区间的向量及旋转角(注意他给出的不是旋转角)一个区间的向量值=左子区间的向量+右子区间的向量。
求一个向量(x0,y0)逆时针旋转B度后的向量有一个公式:
x1= x0 * cosB - y0 * sinB
y1 = x0 * sinB + y0 * cosB
顺时针就把-B代入:
x1= x0 * cosB + y0 * sinB
y1 = -x0 * sinB + y0 * cosB

这个题的数学原理还没有完全理解,有时间重新看看。

代码

Source Code

Problem: 2991       User: liangrx06
Memory: 1220K       Time: 735MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;

#define M_PI 3.14159265358979323846

const int N = 10000;
const int C = 100000;
const int ST_SIZE = 1<<15;

int n, c;
int L[N];
int S[C], A[N];

double vx[ST_SIZE], vy[ST_SIZE];
double ang[ST_SIZE];

double prv[N];

void init(int k, int l, int r)
{
    ang[k] = vx[k] = 0.0;

    if (r-l == 1) {
        vy[k] = L[l];
    }
    else {
        int chl = k*2+1, chr = k*2+2;
        init(chl, l, (l+r)/2);
        init(chr, (l+r)/2, r);
        vy[k] = vy[chl] + vy[chr];
    }
}

void change(int s, double a, int v, int l, int r)
{
    if (s <= l) return;
    else if (s < r) {
        int chl = v*2+1, chr = v*2+2;
        int m = (l+r)/2;
        change(s, a, chl, l, m);
        change(s, a, chr, m, r);
        if (s <= m) ang[v] += a;

        double s = sin(ang[v]), c = cos(ang[v]);
        vx[v] = vx[chl] + (c * vx[chr] - s * vy[chr]);
        vy[v] = vy[chl] + (s * vx[chr] + c * vy[chr]);
    }
}

int main(void)
{
    while (cin >> n >> c) {
        for (int i = 0; i < n; i ++)
            scanf("%d", &L[i]);
        init(0, 0, n);
        for (int i = 1; i < n; i ++)
            prv[i] = M_PI;

        for (int i = 0; i < c; i ++) {
            scanf("%d%d", &S[i], &A[i]);
            int s = S[i];
            double a = A[i] / 360.0 * 2 * M_PI;

            change(s, a - prv[s], 0, 0, n);
            prv[s] = a;
            printf("%.2f %.2f\n", vx[0], vy[0]);
        }
        printf("\n");
    }

    return 0;
}

POJ3264 最值之差(RMQ)

http://poj.org/problem?id=3264

题意

给出一串的数字,然后给出多个区间a b,输出从a到b的最大的数和最小的数的差。

思路

其实就是RMQ,最基本的线段树,只不过最大值和最小值都要维护。

代码

Source Code

Problem: 3264       User: liangrx06
Memory: 1804K       Time: 1860MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 50000;
const int INF = 10000000;

typedef pair<int, int> P;

int n;
P dat[4*N];

void init(int n0)
{
    n = 1;
    while (n < n0) n <<= 1;
    for (int i = 0; i < 2*n-1; i ++) {
        dat[i].first = INF;
        dat[i].second = -INF;
    }
}

void update(int k, int x)
{
    k += n-1;
    dat[k] = P(x, x);

    while (k > 0) {
        k = (k-1)/2;
        dat[k].first = min(dat[2*k+1].first, dat[2*k+2].first);
        dat[k].second = max(dat[2*k+1].second, dat[2*k+2].second);
    }
}

P query(int a, int b, int k, int l, int r)
{
    if (a <= l && r <= b) return dat[k];
    if (a > r || b < l) return P(INF, -INF);

    P vl = query(a, b, 2*k+1, l, (l+r)/2);
    P vr = query(a, b, 2*k+2, (l+r)/2+1, r);
    return P(min(vl.first, vr.first), max(vl.second, vr.second));
}

int main(void)
{
    int n0, q;
    cin >> n0 >> q;
    init(n0);
    int x;
    for (int i = 0; i < n0; i ++) {
        scanf("%d", &x);
        update(i, x);
    }

    int a, b;
    P p;
    for (int i = 0; i < q; i ++) {
        scanf("%d%d", &a, &b);
        p = query(a-1, b-1, 0, 0, n-1);
        printf("%d\n", p.second - p.first);
    }

    return 0;
}

POJ2104

http://poj.org/problem?id=2104

题意

找出指定区间[i,j]第k小的数。

思路

这个题平方分割和线段树都能做。

区间内第k小的数x,即<=x的有>=k个,找到最小的且满足这个条件的x即是答案。二分枚举x得到答案。
用平方分割的做法,将整个区间分成大小为b的一个个桶。每次查找<=x的个数的复杂度为:
1.对于完全包含在区间内的桶,直接二分O(logb),所以事先每个桶要先排序。
2.对于部分包含在区间内的桶,则直接线性查找O(b)。
满足1的最多有n/b个桶,满足2的最多只有两个桶,所以每次查找<=x个数的复杂度为O((n/b)*logb+b),要使复杂度取最小,b大约取sqrt(nlogn),复杂度为O(sqrt(nlogn))。再加上二分枚举答案,就是O(sqrt(nlogn)*logn)。
同时事先排序预处理复杂度为 O(nlogn)。
总体复杂度为O(nlogn+m*sqrt(nlogn)*logn)。

我按照书中代码写的,提交后TLE(估计是超了一点点,因为搜索其他人按照平方分割写的代码,好几个说是能AC也都是TLE,只有其中一个可以通过,但也濒临TLE)。因为已经掌握了思想,就懒得再改了。
只能说这个题的数据并不适合用分桶法来做。
下面贴我的TLE代码1和某能恰好通过的代码2。

书中也提到这个题可以用线段树来做,也叫归并树。见代码3.

代码1(平方分割,我的TLE代码)

Source Code

Problem: 2104       User: liangrx06
Memory: N/A     Time: N/A
Language: C++       Result: Time Limit Exceeded
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>
using namespace std;

const int N = 100000;
int B = 1000;

int n, m;
int a[N], c[N];
vector<int> b[N];

int solve(int x, int y, int k)
{
    int lb = 0, ub = n;
    while (ub - lb > 1) {
        int mid = (ub + lb) >> 1;
        int num = c[mid];
        int l = x, r = y+1;
        int cnt = 0;
        while (l < r && l % B != 0) if(a[l++] < num) cnt ++;
        while (r > l && r % B != 0) if(a[--r] < num) cnt ++;
        while (l < r) {
            cnt += (lower_bound(b[l/B].begin(), b[l/B].end(), num) - b[l/B].begin());
            l += B;
        }
        if (cnt < k) lb = mid;
        else ub = mid;
    }
    return c[lb];
}

int main(void)
{
    cin >> n >> m;
    B = ceil(sqrt(n * log(n*1.0)));
    if(n == 1) B = 1;
    for (int i = 0; i < n; i ++) {
        scanf("%d", &a[i]);
        b[i/B].push_back(a[i]);
        c[i] = a[i];
    }
    for (int i = 0; i < n/B; i ++)
        sort(b[i].begin(), b[i].end());
    sort(c, c+n);

    int x, y, k;
    for (int i = 0; i < m; i ++) {
        scanf("%d%d%d", &x, &y, &k);
        printf("%d\n", solve(x-1, y-1, k));
    }

    return 0;
}

代码2(平方分割,恰好能AC的代码)

Source Code

Problem: 2104       User: liangrx06
Memory: 3144K       Time: 9954MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <cmath>
#include <vector>
#define sf scanf
#define pf printf

using namespace std;

const int Maxn = 100010;
int val[Maxn],sort_val[Maxn];
int n,m,l,r,k;
vector<int> vec[Maxn];

int binary_sort(int k,int i)
{
    int l = 0, r = vec[i].size() - 1;
    while(l <= r)
    {
        int mid = (l + r) >>1;
        if(vec[i][mid] <= k)
            l = mid + 1;
        else
            r = mid - 1;
    }
    return r + 1;
}
int main()
{
    while(~sf("%d%d",&n,&m))
    {
        int b = ceil(sqrt(n * log(n*1.0)));
        if(n == 1) b = 1;

        for(int i = 0;i < n;i ++)
        {
            sf("%d",&val[i]);
            sort_val[i] = val[i];
            vec[i/b].push_back(val[i]);
        }
        for(int i = 0;i <= (n-1)/b;i++)
            sort(vec[i].begin(),vec[i].end());
        sort(sort_val,sort_val + n);
        while(m --)
        {
            sf("%d%d%d",&l,&r,&k);
            l --;
            r --;
            int x = l / b;
            int y = r / b;
            int L = 0,R = n - 1,mid;
            while(L <= R)
            {
                mid = (L + R) >> 1;
                int key = sort_val[mid];
                int ll = l,rr = r,ans = 0;
                while(ll <= rr && ll < (x + 1) * b)
                    if(val[ll++] <= key) ans ++;

                while(ll <= rr && rr >= y * b)
                    if(val[rr--] <= key) ans ++;

                for(int i = x + 1;i < y;i ++)
                    ans += binary_sort(key,i);
                if(ans < k)
                    L = mid + 1;
                else if(ans >= k)
                    R = mid - 1;

            }
            pf("%d\n",sort_val[L]);
        }
        for(int i = 0;i <= (n-1)/ b;i++)
            vec[i].clear();
    }
    return 0;
}

代码3(线段树)

Source Code

Problem: 2104       User: liangrx06
Memory: 17264K      Time: 6672MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 100000;
const int ST_SIZE = 1 << 18;

int n, m;
int a[N], c[N];
vector<int> dat[ST_SIZE];

void init(int k, int l, int r)
{
    if (r-l == 1) {
        dat[k].push_back(a[l]);
    } else {
        int chl = 2*k+1, chr = 2*k+2;
        int m = (l+r) / 2;
        init(chl, l, m);
        init(chr, m, r);
        dat[k].resize(r-l);
        merge(dat[chl].begin(), dat[chl].end(),
                dat[chr].begin(), dat[chr].end(), dat[k].begin());
    }
}

int cnt(int a, int b, int k, int l, int r, int num)
{
    if (a <= l && r <= b) {
        return lower_bound(dat[k].begin(), dat[k].end(), num) - dat[k].begin();
    } else if (l >= b || a >= r) {
        return 0;
    } else {
        int chl = 2*k+1, chr = 2*k+2;
        int m = (l+r) / 2;
        int res = cnt(a, b, chl, l, m, num);
        res += cnt(a, b, chr, m, r, num);
        return res;
    }
}

int main(void)
{
    while (scanf("%d%d", &n, &m) != EOF) {
        for (int i = 0; i < n; i ++) {
            scanf("%d", &a[i]);
            c[i] = a[i];
        }
        sort(c, c+n);
        init(0, 0, n);

        int x, y, k;
        for (int i = 0; i < m; i ++) {
            scanf("%d%d%d", &x, &y, &k);
            int lb = 0, ub = n;
            while (ub - lb > 1) {
                int mid = (ub + lb) >> 1;
                int num = c[mid];
                if (cnt(x-1, y, 0, 0, n, num) < k) lb = mid;
                else ub = mid;
            }
            printf("%d\n", c[lb]);
        }
        for(int i = 0; i < ST_SIZE; i++)
            dat[i].clear();
    }

    return 0;
}

POJ3468 成段更新,区间求和

http://poj.org/problem?id=3468

题意

给你N个数,Q个操作,操作有两种,‘Q a b ’是询问a~b这段数的和,‘C a b c’是把a~b这段数都加上c。

思路

此题是《挑战》书中例题,线段树和树状数组都可以用,我的测试表明树状数组效率略高一些。

这里的线段树代码每个节点维护两个数据:
a. 给这个节点对应的区间内的所有元素共同加上的值
b. 在这个节点对应的区间中除去a之外其他的值的和
我看过其它的代码,方法与这里不完全一样,感觉这里的方法要更简单些。

另外树状数组的实现代码见我的博客的3.3.1节。

代码(线段树)

Source Code

Problem: 3468       User: liangrx06
Memory: 4356K       Time: 1704MS
Language: C++       Result: Accepted
Source Code
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 100000;

typedef long long LL;

int n;
LL s1[4*N], s2[4*N];

void add(int a, int b, int x, int k, int l, int r)
{
    if (a <= l && r <= b) {
        s1[k] += x;
    } else if (b > l && r > a) {
        s2[k] += (min(b, r) - max(a, l)) * (LL)x;
        int chl = 2*k+1, chr = 2*k+2;
        int m = (l + r) / 2;
        add(a, b, x, chl, l, m);
        add(a, b, x, chr, m, r);
    }
}

LL sum(int a, int b, int k, int l, int r)
{
    if (a <= l && r <= b) {
        return (r-l)*s1[k] + s2[k];
    } else if (b > l && r > a) {
        LL res = (min(b, r) - max(a, l)) * s1[k];
        int chl = 2*k+1, chr = 2*k+2;
        int m = (l + r) / 2;
        res += sum(a, b, chl, l, m);
        res += sum(a, b, chr, m, r);
        return res;
    } else
        return 0;
}

int main(void)
{
    int n, q;
    cin >> n >> q;
    int m;
    for (int i = 0; i < n; i ++) {
        scanf("%d", &m);
        add(i, i+1, m, 0, 0, n);
    }

    char c[2];
    int x, y, z;
    for (int i = 0; i < q; i ++) {
        scanf("%s", c);
        if (c[0] == 'Q') {
            scanf("%d%d", &x, &y);
            printf("%lld\n", sum(x-1, y, 0, 0, n));
        } else {
            scanf("%d%d%d", &x, &y, &z);
            add(x-1, y, z, 0, 0, n);
        }
    }

    return 0;
}

#
http://poj.org/problem?id=

题意

思路

代码


#
http://poj.org/problem?id=

题意

思路

代码


#
http://poj.org/problem?id=

题意

思路

代码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值