BZOJ1099 POI2007 树Drz

第一页结束之前的一道槛……

从中文题解到波兰文题解 , 折腾些时间才弄懂-_-# 。希望这篇题解能帮助后来者 , 不要浪费过多的时间捣鼓在google 翻译和晦涩难懂的文字之间……

提示:
1. 本题目前没有一个可行的贪心策略 , 每一次我们都需要考虑所有情况。
2. 在求i 的答案的时候我们先尝试去枚举每一个j , 你会发现 , 只会有那么几种情况。 而每一种情况把绝对值表达式拆开的最终形式都是一样的 , 这提示我们对于每一种情况最小值是可以维护的……
3. 你可能需要用一种数据结构去办到这一点。

详细题解在代码后:

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <string>
#include <vector>
#include <deque>
#include <stack>
#include <algorithm>

using namespace std;
typedef long long ll;

const ll INF = 0x3f3f3f3f3f3f3f3f;
const int maxn = 51000;

__inline int re() { int n; scanf("%d" , &n); return n; }


int n;
ll h[maxn] , res[maxn] ,  ori;
ll l[maxn] , r[maxn] , pl[maxn] , pr[maxn] , to[maxn];

void tension(ll& a , ll b) { a = min(a, b); }

ll cal(int x,int y)
{
     if (x==y) return ori;
     if (x>y) swap(x,y);
     if (x==1&&y==n) return ori-abs(h[1]-h[2])-abs(h[n-1]-h[n])+abs(h[n]-h[2])+abs(h[1]-h[n-1]);
     if (x==1&&y==2) return ori-abs(h[2]-h[3])+abs(h[1]-h[3]);
     if (x==n-1&&y==n) return ori-abs(h[n-1]-h[n-2])+abs(h[n-2]-h[n]);
     if (x+1==y) return ori-abs(h[x-1]-h[x])-abs(h[y+1]-h[y])+abs(h[x]-h[y+1])+abs(h[y]-h[x-1]);
     long long Sum=ori;
     if (x!=1) Sum=Sum-abs(h[x-1]-h[x])+abs(h[x-1]-h[y]);
     Sum=Sum-abs(h[x+1]-h[x])+abs(h[x+1]-h[y]);
     Sum=Sum-abs(h[y-1]-h[y])+abs(h[y-1]-h[x]);
     if (y!=n) Sum=Sum-abs(h[y+1]-h[y])+abs(h[y+1]-h[x]);
     return Sum;
}


bool cmp(int i , int j) { return h[i] < h[j]; }
int id[maxn] , id1[maxn] , id2[maxn];
vector<ll> dic;
void init()
{
    for(int i=1;i<=n;i++)  dic.push_back(h[i]) , id[i] = i;

    sort(id+1, id+1+n, cmp);
    sort(dic.begin(), dic.end());

    for(int i=2;i<n;i++) l[i] = min(h[i-1] , h[i+1]) , r[i] = max(h[i-1], h[i+1]);  
    for(int i=1;i<=n;i++) to[id[i]] = i;
    for(int i=2;i<n;i++) pl[i] = upper_bound(dic.begin(), dic.end(), l[i])-dic.begin();
    for(int i=2;i<n;i++) pr[i] = upper_bound(dic.begin(), dic.end(), r[i])-dic.begin();
}

ll v[maxn*4][3];
int in[maxn];
ll A[3];

void update(int o , int l , int r , int k)
{
    if(l==r) for(int i=0;i<3;i++) v[o][i] = A[i];
    else 
    {
        int mid = (l+r)/2;
        if(to[k]<=mid) update(o*2, l, mid, k);
        else update(o*2+1, mid+1, r, k);
        for(int i=0;i<3;i++) v[o][i] = min(v[o*2][i] , v[o*2+1][i]);
    }
}

ll query(int o , int l , int r , int L , int R , int k)
{
    if(L<=l && r<=R) return v[o][k];
    int mid = (l+r)/2;
    ll res = INF;

    if(L<=mid) res = min(res , query(o*2, l, mid, L, R, k));
    if(R> mid) res = min(res , query(o*2+1, mid+1, r, L, R, k));
    return res;
}

void clear()
{
    for(int i=0;i<=n*4;i++) v[i][0] = v[i][1] = v[i][2] = INF;
    for(int i=0;i<=n;i++) id[i] = id1[i] = id2[i] = i;
    for(int i=0;i<=n;i++) in[i] = 0;
}

bool cmp1(int i , int j)  { return h[i] > h[j]; }
bool cmp2(int i , int j)  { return l[i] > l[j]; }

ll pointValue(int i){ return abs(h[i]-h[i-1])+abs(h[i]-h[i+1]); }

int sum[maxn][3];
void giveINF() { A[0] = A[1] = A[2] = INF; }
void giveOri(int k) { for(int i=0;i<3;i++) A[i] = sum[k][i]; }

void print(ll* a) { for(int i=1;i<=n;i++) cout<<a[i]<<" "; cout<<endl; }
void print(int* a) { for(int i=1;i<=n;i++) cout<<a[i]<<" "; cout<<endl; }

void perform1()
{
    clear();
    sort(id+1, id+n+1 , cmp1);
    sort(id1+1, id1+n+1, cmp2);

    int cnt = 1;
    for(int i=1;i<=n;i++) if(id[i]!=1 && id[i]!=n)
    {
        while(cnt<=n && l[id1[cnt]] >= h[id[i]]) 
        {
            int k = id1[cnt];
            if(k==1 || k==n) { cnt++; continue; }
            ll vnow = h[k-1]+h[k+1]-pointValue(k);
            sum[k][0] = A[0] = vnow - 2*h[k];
            sum[k][1] = A[1] = vnow;
            sum[k][2] = A[2] = vnow + 2*h[k];
            update(1, 0, n, k);
            in[k] = 1;
            cnt++;
        }

        int k = id[i];
        if(in[k-1]) giveINF() , update(1, 0, n, k-1);
        if(in[k]) giveINF() , update(1, 0, n, k);
        if(in[k+1]) giveINF() , update(1, 0, n, k+1);

        ll vnow = ori-2*h[k] - pointValue(k);
        tension(res[k], query(1, 0, n, 0, pl[k], 0)+vnow+l[k]+r[k]);
        tension(res[k], query(1, 0, n, pl[k], pr[k], 1)+vnow+r[k]-l[k]);
        tension(res[k], query(1, 0, n, pr[k], n, 2)+vnow-r[k]-l[k]);

        if(in[k-1]) giveOri(k-1) , update(1, 0, n, k-1);
        if(in[k]) giveOri(k) , update(1, 0, n, k);
        if(in[k+1]) giveOri(k+1) , update(1, 0, n, k+1);
    }
}

bool cmp3(int i , int j) { return h[i]<h[j]; }
bool cmp4(int i , int j) { return r[i]<r[j]; } 
void perform2()
{
    clear();
    sort(id+1, id+n+1 , cmp3);
    sort(id1+1, id1+n+1, cmp4);

    int cnt = 1;
    for(int i=1;i<=n;i++) if(id[i]!=1 && id[i]!=n)
    {
        while(cnt<=n && r[id1[cnt]] <= h[id[i]]) 
        {
            int k = id1[cnt];
            if(k==1 || k==n) { cnt++; continue; }
            ll vnow =-h[k-1]-h[k+1]-pointValue(k);
            sum[k][0] = A[0] = vnow - 2*h[k];
            sum[k][1] = A[1] = vnow;
            sum[k][2] = A[2] = vnow + 2*h[k];
            update(1, 0, n, k);
            in[k] = 1;
            cnt++;
        }

        int k = id[i];
        if(in[k-1]) giveINF() , update(1, 0, n, k-1);
        if(in[k]) giveINF() , update(1, 0, n, k);
        if(in[k+1]) giveINF() , update(1, 0, n, k+1);

        ll vnow = ori+2*h[k] - pointValue(k);
        tension(res[k], query(1, 0, n, 0, pl[k], 0)+vnow+l[k]+r[k]);
        tension(res[k], query(1, 0, n, pl[k], pr[k], 1)+vnow+r[k]-l[k]);
        tension(res[k], query(1, 0, n, pr[k], n, 2)+vnow-r[k]-l[k]);

        if(in[k-1]) giveOri(k-1) , update(1, 0, n, k-1);
        if(in[k]) giveOri(k) , update(1, 0, n, k);
        if(in[k+1]) giveOri(k+1) , update(1, 0, n, k+1);
    }
}

bool cmp5(int i , int j) { return l[i]<l[j]; }
void perform3()
{
    clear();
    sort(id+1, id+n+1 , cmp3);
    sort(id1+1, id1+n+1, cmp5);
    sort(id2+1, id2+n+1, cmp4);

    int cnt = 1 , cnt1 = 1;
    for(int i=1;i<=n;i++) if(id[i]!=1 && id[i]!=n)
    {
        while(cnt<=n && l[id1[cnt]] <= h[id[i]]) 
        {
            int k = id1[cnt];
            if(k==1 || k==n) { cnt++; continue; }
            ll vnow = r[k]-l[k]-pointValue(k);
            sum[k][0] = A[0] = vnow - 2*h[k];
            sum[k][1] = A[1] = vnow;
            sum[k][2] = A[2] = vnow + 2*h[k];
            update(1, 0, n, k);
            in[k] = 1;
            cnt++;
        }
        while(cnt1<=n && r[id2[cnt1]] <= h[id[i]])
        {
            int k = id2[cnt1];
            if(k==1 || k==n) { cnt1++; continue; }
            giveINF();
            update(1, 0, n, k);
            in[k] = 0;
            cnt1++;
        }

        int k = id[i];
        if(in[k-1]) giveINF() , update(1, 0, n, k-1);
        if(in[k]) giveINF() , update(1, 0, n, k);
        if(in[k+1]) giveINF() , update(1, 0, n, k+1);

        ll vnow = ori-pointValue(k);
        tension(res[k], query(1, 0, n, 0, pl[k], 0)+vnow+l[k]+r[k]);
        tension(res[k], query(1, 0, n, pl[k], pr[k], 1)+vnow+r[k]-l[k]);
        tension(res[k], query(1, 0, n, pr[k], n, 2)+vnow-r[k]-l[k]);

        if(in[k-1]) giveOri(k-1) , update(1, 0, n, k-1);
        if(in[k]) giveOri(k) , update(1, 0, n, k);
        if(in[k+1]) giveOri(k+1) , update(1, 0, n, k+1);
    }
}

int main(int argc, char *argv[]) {

    n = re();
    for(int i=1;i<=n;i++) h[i] = re();
    for(int i=2;i<=n;i++) ori += abs(h[i]-h[i-1]);

    if(n<=2) { for(int i=1;i<=n;i++) printf("%lld\n" , ori); exit(0); }
    for(int i=1;i<=n;i++) res[i] = ori;

    for(int i=1;i<n;i++) tension(res[i], cal(i, i+1));
    for(int i=2;i<=n;i++) tension(res[i], cal(i, i-1));
    for(int i=2;i<=n;i++) tension(res[1], cal(1, i)) , tension(res[i], cal(1, i));
    for(int i=1;i<n;i++) tension(res[n], cal(n, i)) , tension(res[i], cal(n , i));

    init();
    perform1();
    perform2();
    perform3();
    for(int i=1;i<=n;i++) printf("%lld\n" , res[i]);

    return 0;
}

我们可以分类讨论再用线段树维护……(如果看到这里不想看了 , 完全可以弃坑写 O(n2) 的暴力)

请按照我的提示2先枚举求一些答案 , 这样下面的思路会自然些:

现在我们正在求i 的答案 , 枚举到另一个数 j , 现在我们要交换i , j的值。 i , j原来在序列里和4个值有关(这个题头尾我们都进行特判 , 而且我们不考虑i , j相邻的情况 , 降低编程复杂度) , i , j交换后只有这4个值可能发生变化。

这4个值以前的值都是固定的 , 所以我们只考虑它们变化后的形式 , 我们先定义几个量。
hii
Mini=min(hi1,hi+1)
Maxi=max(hi1,hi+1)

绝对值最后的展开形式与 , hiMinj,Maxj hjMini,Maxi 有关
可能的情况: hi<Minj;Minjhi<Maxj;Maxjhi
对于j 是类似的。

那么 , 根据乘法原理 , 总共有9种情况。

这9个情况的总体思路都是类似的 , 根据 hiMinj,Maxj 的关系 , 逐渐把符合要求的加入一棵线段树中 , 再根据 hjMini,Maxi 的关系 , 在这棵线段树中查找符合条件的j。

下文中我们把 hiMinj,Maxj 称作第一个关系 , hjMini,Maxi 称作第二个关系。

强调刚刚提到的几个地方:

  • “逐渐加入” : 因为对于每一个i , 并不是所有j 能够满足第一个关系。 只要我们经过适当的排序 , 这个满足这个条件的j 随着i 的变化是逐渐变多的 , 且前面满足条件的j 后面也一定满足。
  • 这里有两个关系的限制 , 但线段树只能控制一个关系, 我们让他控制第二个关系 , 所以第一个关系我们是通过逐渐加入来体现的。

由于加入条件由第一个关系控制 , 所以第一个关系相同的情况可以一起讨论 , 这样就有了代码中的perform1 , perform2 , perform3. 分别对应第一个关系的三种情况。

几个实现的细节:

  • 我们需要把中途查询需要的量预处理离散化 , 方便找到这个点在线段树的位置。
  • 线段树中到底存什么 , 取决于你在讨论第几种情况。 当你把每种情况展开时 , 你会发现对于同一个i , 所有下标为i的量都是常量 , 对于每一个j 我们真正关心的就是剩下的量。 把它们存在j所对应的线段树中的位置就可以啦!

最后再说明一下 , 这份代码中有很多特判的地方 , 我们在线段树中不讨论头尾和相邻的情况。

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