CDQ分治题目小结

CDQ分治属于比较特殊的一类分治,许多问题转化为这类分治的时候,时空方面都会有很大节省,而且写起来没有这么麻烦。

这类分治的特殊性在于分治的左右两部分的合并,作用两部分在合并的时候作用是不同的,比如,通过左半部分的影响来更新右半部分,所以分治开始前都要按照某一个关键字排序,然后利用这个顺序,考虑一个区间[l, r]的两部分间的影响。感觉说的太多,还是不如具体题目分析,而且题目也不尽相同,记住几句话是没什么用的。

练习地址:

http://vjudge.net/contest/view.action?cid=55322#overview

Problem A HYSBZ 3110 K大数查询

这个是一个很经典的树套树题目,看别人博客发现CDQ分治能够很好处理。

题意:有n个位置编号1~n,m个操作,每个操作两种类型:1 a b c 表示将第a~b个位置之间的每个位置插入一个数c;2 a b c 查询第a~b个位置之间的所有数中,第c大的数。

 

范围:

N,M<=50000,N,M<=50000

a<=b<=N

 

1操作中abs(c)<=N

2操作中abs(c)<=Maxlongint

分析:

按照CDQ分治的做法,是答案当做关键字来分治,由于答案最终在-n~n之间,这里首先需要一个转化,将区间第c大变成第c小,只需要将每个数变成n-c+1。

对于这类操作类的题目 ,CDQ分治的做法首先要保证的是操作的顺序,接下来以答案为关键字,例如询问结果在L~R之间的操作2,分成两部分递归L~m,m+1~R处理,#11对于操作1如果添加的数<=m,则加入到相应的位置区间;#12否则说明操作1影响答案在右半区间m+1~R的操作2。然后对于每个操作2查询当前位置区间有多少个数,表示该区间<=m已经有多少个数(#21),如果(#22)数目tmp > c (查询数目),说明答案应该在m+1~R,否则在L~m。然后将操作1中影响答案在左半部分的(编号#11)和操作2中答案在左半部分的(#21)集中在一起左半部分,剩下的集中在右半部分。然后递归处理答案在左半部分和右半部分的。每次进行子区间的递归时都将操作分成了2部分,表示不同区间被对应不同的操作。

具体成段增加一个值和查询某一段的和用到了树状数组,也可以用线段树,不过我觉得树状数组解法简洁有力,orz,

上一下原文树状数组链接

http://www.cnblogs.com/lazycal/archive/2013/08/05/3239304.html

我的代码:

bzoj好像不能用cout输出一个表达式,会RE!

  1 /*Time 2014 08 31 , 19:26
  2
  3 */
  4 #include <bits/stdc++.h>
  5 #define in freopen("solve_in.txt", "r", stdin);
  6 #define bug(x) printf("Line %d : >>>>>>>\n", (x));
  7
  8 using namespace std;
  9 typedef long long LL;
 10 const int maxn = 50000 + 100;
 11 LL x1[maxn][2], x2[maxn][2], ans[maxn];
 12 int cnt;
 13
 14 struct Node
 15 {
 16     int l, r, type;
 17     LL c;
 18     int id;
 19 } q[maxn];
 20 int rk[maxn], t1[maxn], t2[maxn];
 21 int n, m;
 22 LL query(LL a[][2], int x)
 23 {
 24     LL res = 0;
 25     for(; x > 0; x -= (x&(-x)))
 26     {
 27         if(a[x][0] == cnt) res += a[x][1];
 28     }
 29     return res;
 30 }
 31 LL query(int l, int r)
 32 {
 33     return query(x1, l)*(r-l+1)+ (r+1)*(query(x1, r)-query(x1, l)) - (query(x2, r)-query(x2, l));
 34 }
 35 void add(LL a[][2], int x, LL c)
 36 {
 37     for(; x <= n; x += ((-x)&x))
 38     {
 39         if(a[x][0] == cnt) a[x][1] += c;
 40         else a[x][0] = cnt, a[x][1] = c;
 41     }
 42 }
 43 void add(int l, int r, int c)
 44 {
 45     add(x1, l, c);
 46     add(x2, l, (LL)l*c);
 47     add(x1, r+1, -c);
 48     add(x2, r+1, (LL)(r+1)*(-c));
 49 }
 50 void solve(int ll, int rr, int l, int r)
 51 {
 52     if(l > r) return;
 53     if(ll == rr)
 54     {
 55         for(int i = l; i <= r; i++)
 56             if(q[rk[i]].type == 2)
 57             {
 58                 ans[rk[i]] = ll;
 59             }
 60         return;
 61     }
 62     int m1 = (ll+rr)>>1, m2 = (l+r)>>1;
 63     cnt++;
 64     t1[0] = t2[0] = 0;
 65     for(int i = l; i <= r; i++)
 66     {
 67         if(q[rk[i]].type == 1)
 68         {
 69             if(q[rk[i]].c <= m1)
 70             {
 71                 add(q[rk[i]].l, q[rk[i]].r, 1);
 72                 t1[++t1[0]] = rk[i];
 73             }
 74             else
 75             {
 76                 t2[++t2[0]] = rk[i];
 77             }
 78         }
 79         else
 80         {
 81             LL xx = query(q[rk[i]].l, q[rk[i]].r);
 82             if(xx < (LL)q[rk[i]].c)
 83             {
 84                 q[rk[i]].c -= xx;
 85                 t2[++t2[0]] = rk[i];
 86             }
 87             else
 88             {
 89                 t1[++t1[0]] = rk[i];
 90             }
 91         }
 92     }
 93     m2 = l+t1[0]-1;
 94
 95     for(int i = l; i <= r; i++)
 96     {
 97         if(i <= m2)
 98         {
 99             rk[i] = t1[i-l+1];
100         }
101         else
102         {
103             rk[i] = t2[i-m2];
104         }
105     }
106     solve(ll, m1, l, m2);
107     solve(m1+1, rr, m2+1, r);
108 }
109 int main()
110 {
111
112     scanf("%d%d", &n, &m);
113     for(int i = 1; i <= m; i++)
114     {
115         rk[i] = i;
116         scanf("%d%d%d%lld", &q[i].type, &q[i].l, &q[i].r, &q[i].c);
117         if(q[i].type == 1) q[i].c = (LL)n-q[i].c+1;
118         q[i].id = i;
119     }
120     solve(1, 2*n+1, 1, m);
121     for(int i = 1; i <= m; i++)
122     {
123         if(q[i].type == 2)
124         {
125            printf("%d\n", n-ans[i]+1);
126         }
127     }
128     return 0;
129 }

Problem B HYSBZ 1492 货币兑换Cash

题意:一开始有S元现金,接下来共有N天,每天两种货币的价格分别为a[i],b[i],以及卖入时,ab货币的比列为r[i],问N天结束时最多能有多少现金。

分析:

最后一天结束时一定时将货币全部换成现金,那么第i天货币数目x[i], y[i],第i天最多持有的现金

f[i] = max{x[j]*a[i]+y[j]*b[i]|(j < i)},

y[j] = f[j]/(a[j]*r[j]+b[j]), x[j] = y[j]*r[j].

化简后f[i]/b[i] - x[j]*a[i]/b[i] = y[j],发现最优解便是使得f[i]/b[i]最大,也就是斜率为-a[i]/b[i]的斜率,截距最大。对于点(x[j], y[j])能够影响到之后的f[i], i >j,f[i]最优解一定落在前i-1天行成的凸壳上,那么怎么高效维护这个凸壳是问题的核心,与普通斜率优化不同的是这题的斜率与x均不会单调,所以事先将斜率排序,然后按照斜率递减的顺序来在凸壳上找最优解是可行的。因为斜率递减的话,切凸壳上点得到的截距会越来越大。然后就是维护以个凸壳,最终这个凸壳相邻两点斜率也要递减。那么每次递归结束时按照x[i]排序,方便下次维护生成凸壳。

代码:

http://vjudge.net/contest/viewSource.action?id=2724881

Problem C CodeForces 396C On Changing Tree

题解见这里 

http://www.cnblogs.com/rootial/p/3948478.html

关键在于两个操作1的合并, 将树的叶子结点编号形成连续区间然后当做线段树做!每次查询时只需将结点变成对应的叶子结点区间在线段树上查询就可以了。

代码:

代码贴不上来。上链接好了。

http://vjudge.net/contest/viewSource.action?id=2726148

Problem D HDU 3698 Let the light guide us

题意:

n*m的两个矩阵,每个格子有两个值,一个是花费cost[i][j],一个是魔力值magic[i][j],(n<=100, m<=5000)要求每行选一个格子且格子对应的花费总和最小,任意响铃两行的格子魔力值满足条件|j-k|<=f[i, j]+f[i-1, k]。

分析:

CDQ分治做法还没想出来,之后在更新吧,看大家博客基本都是线段树做法..

dp[i][j]表示第i行选第j个格子的最小的花费。

分析一下, 对于任意相邻两行[i, j]和[i-1, k]的格子,[i-1, k]的花费dp[i-1][k]能够影响下一行k-magic[i-1, k]~k+magic[i-1, k]范围内格子的花费,    [i, j]能够受上一行

j-magic[i,j]~j+magic[i, j]格子的花费的影响。这样用上一行花费dp[i-1][k]更新k-magic[i-1, k]~k+magic[i-1, k]最小值,到求dp[i, j]时, 查询j-magic[i, j]~j+magic[i,j]最小值min即可, dp[i][j] = min+cost[i][j] .

代码:

//Time 2014 09 01 , 10:22
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <iostream>
#define in freopen("solve_in.txt", "r", stdin);
#define bug(x) printf("Line %d : >>>>>>>\n", (x));
#define lson rt<<1, l, m
#define rson rt<<1|1, m+1, r
#define inf 0x0f0f0f0f
#define pb push_back
using namespace std;
typedef long long LL;

const int maxn = 5555;
const int maxm = 111;
int dp[maxn];
int n, m;
int a[maxm][maxn], b[maxm][maxn], cover[maxn<<2], mi[maxn<<2];
void PushDown(int rt)
{
    cover[rt<<1] = min(cover[rt<<1], cover[rt]);
    cover[rt<<1|1] = min(cover[rt<<1|1], cover[rt]);
    mi[rt<<1] = min(mi[rt<<1], cover[rt<<1]);
    mi[rt<<1|1] = min(mi[rt<<1|1], cover[rt<<1|1]);
    cover[rt] = inf;
}
void update(int rt, int l, int r, int L, int R, int c)
{
    if(L <= l && R >= r)
    {
        cover[rt] = min(cover[rt], c);
        mi[rt] = min(mi[rt], cover[rt]);
        return;
    }
    int m = (l+r)>>1;
    PushDown(rt);
    if(L <= m)
        update(lson, L, R, c);
    if(R > m)
        update(rson, L, R, c);
    mi[rt] = min(mi[rt<<1], mi[rt<<1|1]);
}
int query(int rt, int l, int r, int L, int R)
{
    if(L <= l && R >= r)
    {
        return mi[rt];
    }
    int m = (l+r)>>1;
    int ans = inf;
    PushDown(rt);
    if(L<=m)
        ans = min(ans, query(lson, L, R));
    if(R > m)
        ans = min(ans, query(rson, L, R));
    return ans;
}
void build(int rt, int l, int r){
    cover[rt] = mi[rt] = inf;
    if(l == r)
    {
        return;
    }
    int m = (l+r)>>1;
    build(lson);
    build(rson);
}
int main()
{

    while(scanf("%d%d", &n, &m), n||m)
    {
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
            {
                scanf("%d", &a[i][j]);
                if(i == 1)
                    dp[j] = a[i][j];
            }
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
            {
                scanf("%d", &b[i][j]);
            }
        for(int i = 2; i <= n; i++)
        {
            build(1, 1, m);
            for(int j= 1; j <= m; j++)
            {
                int L = max(1, j-b[i-1][j]);
                int R = min(m, j+b[i-1][j]);
                update(1, 1, m, L, R, dp[j]);
            }
            for(int j = 1; j <= m; j++)
            {
                int L = max(1, j-b[i][j]);
                int R = min(m, j+b[i][j]);
                dp[j] = query(1, 1, m, L, R)+a[i][j];
            }
        }
        cout<<*min_element(dp+1, dp+m+1)<<endl;
    }
    return 0;
}

时间: 2024-11-08 23:54:52

CDQ分治题目小结的相关文章

CDQ分治小结

CDQ分治小结 warning:此文仅用博主复习使用,初学者看的话后果自负.. 复习的时候才发现以前根本就没写过这种东西的总结,简单的扯一扯 cdq分治的经典应用就是解决偏序问题 比如最经典的三维偏序问题 给出\(n\)个数,每个数\(i\),有三个属性\(a_i, b_i, c_i\),现在我们要统计对于每个\(i\),\(a_j \leqslant a_i, b_j \leqslant b_i, c_j \leqslant c_i\)的个数 显然我们可以先把所有数都按\(a_i\)排序一遍,

CDQ分治与整体二分小结

前言 这是一波强行总结. 下面是一波瞎比比. 这几天做了几道CDQ/整体二分,感觉自己做题速度好慢啊. 很多很显然的东西都看不出来 分治分不出来 打不出来 调不对 上午下午晚上的效率完全不一样啊. 完蛋.jpg 绝望.jpg. 关于CDQ分治 CDQ分治,求的是三维偏序问题都知道的. 求法呢,就是在分治外面先把一维变成有序 然后分治下去,左边(l,mid)关于右边(mid+1,r)就不存在某一维的逆序了,所以只有两维偏序了. 这个时候来一波"树状数组求逆序对"的操作搞一下二维偏序 就可

HDU 5126(stars)四维偏序,cdq分治

题意:给两种操作,进行5万次.操作一:加入一个三维序偶(a,b,c)到集合S里:第二种操作,给两个三维序偶(a1,b1,c1)和(a2,b2,c2),问当前S里有多少个序偶(a,b,c)满足a1<=a<=a2, b1<=b<=b2, c1<=c<=c2.题目保证了a1<=a2,b1<=b2,c1<=c2.所有数在[1,1e9]内 链接:http://acm.hdu.edu.cn/showproblem.php?pid=5126 解法:将操作编号也加入到

BZOJ 2225 [Spoj 2371]Another Longest Increasing(CDQ分治)

[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=2225 [题目大意] 给定N个数对(xi,yi),求最长上升子序列的长度. 上升序列定义为{(xi,yi)}满足对i<j有xi<xj且yi<yj. [题解] CDQ分治,将每个区间按照a排序,用区间左边的数据来更新右边的最长上升序列, 为排除a相等但是b上升情况的误统计,在排序时加入下标作为第二关键字, 使得a相等的情况下标小的后更新. [代码] #include <cs

ACdream1157 Segments(CDQ分治 + 线段树)

题目这么说的: 进行如下3种类型操作:1)D L R(1 <= L <= R <= 1000000000) 增加一条线段[L,R]2)C i (1-base) 删除第i条增加的线段,保证每条插入线段最多插入一次,且这次删除操作一定合法3) Q L R(1 <= L <= R <= 1000000000) 查询目前存在的线段中有多少条线段完全包含[L,R]这个线段,线段X被线段Y完全包含即LY <= LX <= RX <= RY) 初学CDQ分治是看了B

SPOJ LIS2 Another Longest Increasing Subsequence Problem 三维偏序最长链 CDQ分治

Another Longest Increasing Subsequence Problem Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://acm.hust.edu.cn/vjudge/problem/visitOriginUrl.action?id=19929 Description Given a sequence of N pairs of integers, find the length of the longest incre

CDQ分治与整体二分总结

Cdq分治和整体二分是两个很奇妙的东西.他们都是通过离线的思想来进行优化,从而更快的求出解. 整体二分通俗的讲就是二分答案,但是它了不起的地方是一下子把所有的答案都二分出来了,从而可以一下子得出所有查询. CDQ分治通俗的讲就是二分查询.通常的做法是把所有的查询分成两半,然后通过递归先计算出左边一半的所有的查询,然后通过这些已知的左半边的值来更新右半边的值.这里,最最重要的思想是通过左半边来更新右半边.更具体一点,就是用左半边的修改来更新右半边的查询. 重要的事情说话三遍: CDQ分治就是通过左

Acdream1157---Segments (CDQ分治)

陈丹琦分治~~~其实一些数据小的时候可以用二维或者多维树状数组做的,而数据大的时候就无力的题目,都可以用陈丹琦分治解决. 题目:由3钟类型操作:1)D L R(1 <= L <= R <= 1000000000) 增加一条线段[L,R]2)C i (1-base) 删除第i条增加的线段,保证每条插入线段最多插入一次,且这次删除操作一定合法3) Q L R(1 <= L <= R <= 1000000000) 查询目前存在的线段中有多少条线段完全包含[L,R]这个线段,线

BZOJ1176---[Balkan2007]Mokia (CDQ分治 + 树状数组)

题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1176 CDQ第一题,warush了好久.. CDQ分治推荐论文: 1 <从<Cash>谈一类分治算法的应用> 陈丹琦 2 <浅谈数据结构题的几个非经典解法>  许昊然 关于CDQ分治,两种要求:①操作不相互影响  ②可以离线处理 题目描述是有问题的,,初始时 全部为0,不是s 题意:二维平面内,两种操作,1 x y v ,位于(x,y)的值加上v...2 x1,