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!

 /*Time 2014 08 31 , 19:26

 */
#include <bits/stdc++.h>
#define in freopen("solve_in.txt", "r", stdin);
#define bug(x) printf("Line %d : >>>>>>>\n", (x)); using namespace std;
typedef long long LL;
const int maxn = + ;
LL x1[maxn][], x2[maxn][], ans[maxn];
int cnt; struct Node
{
int l, r, type;
LL c;
int id;
} q[maxn];
int rk[maxn], t1[maxn], t2[maxn];
int n, m;
LL query(LL a[][], int x)
{
LL res = ;
for(; x > ; x -= (x&(-x)))
{
if(a[x][] == cnt) res += a[x][];
}
return res;
}
LL query(int l, int r)
{
return query(x1, l)*(r-l+)+ (r+)*(query(x1, r)-query(x1, l)) - (query(x2, r)-query(x2, l));
}
void add(LL a[][], int x, LL c)
{
for(; x <= n; x += ((-x)&x))
{
if(a[x][] == cnt) a[x][] += c;
else a[x][] = cnt, a[x][] = c;
}
}
void add(int l, int r, int c)
{
add(x1, l, c);
add(x2, l, (LL)l*c);
add(x1, r+, -c);
add(x2, r+, (LL)(r+)*(-c));
}
void solve(int ll, int rr, int l, int r)
{
if(l > r) return;
if(ll == rr)
{
for(int i = l; i <= r; i++)
if(q[rk[i]].type == )
{
ans[rk[i]] = ll;
}
return;
}
int m1 = (ll+rr)>>, m2 = (l+r)>>;
cnt++;
t1[] = t2[] = ;
for(int i = l; i <= r; i++)
{
if(q[rk[i]].type == )
{
if(q[rk[i]].c <= m1)
{
add(q[rk[i]].l, q[rk[i]].r, );
t1[++t1[]] = rk[i];
}
else
{
t2[++t2[]] = rk[i];
}
}
else
{
LL xx = query(q[rk[i]].l, q[rk[i]].r);
if(xx < (LL)q[rk[i]].c)
{
q[rk[i]].c -= xx;
t2[++t2[]] = rk[i];
}
else
{
t1[++t1[]] = rk[i];
}
}
}
m2 = l+t1[]-; for(int i = l; i <= r; i++)
{
if(i <= m2)
{
rk[i] = t1[i-l+];
}
else
{
rk[i] = t2[i-m2];
}
}
solve(ll, m1, l, m2);
solve(m1+, rr, m2+, r);
}
int main()
{ scanf("%d%d", &n, &m);
for(int i = ; i <= m; i++)
{
rk[i] = i;
scanf("%d%d%d%lld", &q[i].type, &q[i].l, &q[i].r, &q[i].c);
if(q[i].type == ) q[i].c = (LL)n-q[i].c+;
q[i].id = i;
}
solve(, *n+, , m);
for(int i = ; i <= m; i++)
{
if(q[i].type == )
{
printf("%d\n", n-ans[i]+);
}
}
return ;
}

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 = ;
const int maxm = ;
int dp[maxn];
int n, m;
int a[maxm][maxn], b[maxm][maxn], cover[maxn<<], mi[maxn<<];
void PushDown(int rt)
{
cover[rt<<] = min(cover[rt<<], cover[rt]);
cover[rt<<|] = min(cover[rt<<|], cover[rt]);
mi[rt<<] = min(mi[rt<<], cover[rt<<]);
mi[rt<<|] = min(mi[rt<<|], cover[rt<<|]);
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)>>;
PushDown(rt);
if(L <= m)
update(lson, L, R, c);
if(R > m)
update(rson, L, R, c);
mi[rt] = min(mi[rt<<], mi[rt<<|]);
}
int query(int rt, int l, int r, int L, int R)
{
if(L <= l && R >= r)
{
return mi[rt];
}
int m = (l+r)>>;
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)>>;
build(lson);
build(rson);
}
int main()
{ while(scanf("%d%d", &n, &m), n||m)
{
for(int i = ; i <= n; i++)
for(int j = ; j <= m; j++)
{
scanf("%d", &a[i][j]);
if(i == )
dp[j] = a[i][j];
}
for(int i = ; i <= n; i++)
for(int j = ; j <= m; j++)
{
scanf("%d", &b[i][j]);
}
for(int i = ; i <= n; i++)
{
build(, , m);
for(int j= ; j <= m; j++)
{
int L = max(, j-b[i-][j]);
int R = min(m, j+b[i-][j]);
update(, , m, L, R, dp[j]);
}
for(int j = ; j <= m; j++)
{
int L = max(, j-b[i][j]);
int R = min(m, j+b[i][j]);
dp[j] = query(, , m, L, R)+a[i][j];
}
}
cout<<*min_element(dp+, dp+m+)<<endl;
}
return ;
}

UPD:

问了一下CLQ终于知道CDQ是怎么搞的了。

分析一下,CDQ分治也是在转移的时候发挥作用,在需要求dp[i][j]的时候需要用上一行中dp[i-1][k]最小的来更新,且满足|j-k|<=magic[i][j]+magic[i-1][k]这个不等式, 按照CDQ分治的做法,需要将其变形为下面两个形式:

1. k > j 时, k-magic[i-1][k] <= magic[i][j]+j;

2. j > k 时, -magic[i-1][k]-k <= magic[i][j]-j;

分治的时候只需要分别考虑k > j时, dp[i-1][k]的最小值,及 j >k时, dp[i-1][k]的最小值, 然后更新就可以了。以k >j 为例,分治区间[l, r]表示相应格子的列的范围, 然后利用列坐标>=mid+1的dp[i-1][j]去更新列坐标

j <=m的dp[i][j], 具体可以这样先将i-1行每个格子按k-magic[i-1][k]增序排列,第i行格子按照magic[i][j]+j增序排列,有了单调性对于每个dp[i][j]只需要从队首往后扫, 并记录最小值, 可以证明这样对于magic[i][j]+j排在后面的dp[i][j]值的更新也是满足最优的,找到最优值就直接更新。

对于j > k的情况类似,这样一想中间过程又和前面几个题目类似了。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm> #define pb push_back
#define inf 0x0f0f0f0f
#define bug(x) printf("line %d: >>>>>>>>>>>>>>>\n", (x));
#define in freopen("solve_in.txt", "r", stdin);
#define SZ(x) ((int)x.size())
using namespace std;
typedef pair<int, int> PII;
const int maxn = ;
int dp[][maxn], magic[maxn][maxn], f[maxn][maxn];
PII t[][maxn], t1[maxn]; void solve(int pos, int l, int r)
{
if(l == r)
{
t[][l] = PII(l-magic[pos-][l], l);
t[][l] = PII(magic[pos][l]+l, l);
t[][l] = PII(-l-magic[pos-][l], l);
t[][l] = PII(magic[pos][l]-l, l);
return ;
}
int mid = (l+r)>>;
solve(pos, l, mid);
solve(pos, mid+, r);
int res = inf;
for(int i = l, j = mid+; i <= mid; i++)
{
while(j <= r && t[][j].first <= t[][i].first)
{ res = min(res, dp[pos-][t[][j].second]);
j++;
}
dp[pos][t[][i].second] = min(dp[pos][t[][i].second], res+f[pos][t[][i].second]);
}
res = inf;
for(int i = mid+, j = l; i <= r; i++)
{
while(j <= mid && t[][j].first <= t[][i].first)
{ res = min(res, dp[pos-][t[][j].second]);
j++;
}
dp[pos][t[][i].second] = min(dp[pos][t[][i].second], res+f[pos][t[][i].second]);
}
for(int k = ; k < ; k++)
{
int l1 = l, l2 = mid+;
for(int i = l; i <= r; i++)
if(l1 <= mid && (l2 > r || t[k][l1].first < t[k][l2].first))
t1[i] = t[k][l1++];
else t1[i] = t[k][l2++];
for(int i = l; i <= r; i++) t[k][i] = t1[i];
}
}
int main()
{ int n, m;
while(scanf("%d%d", &n, &m), n||m)
{
for(int i = ; i <= n; i++)
for(int j = ; j <= m; j++)
scanf("%d", &f[i][j]);
for(int i = ; i <= n; i++)
for(int j = ; j <= m; j++)
scanf("%d", &magic[i][j]);
for(int i = ; i <= n; i++)
{
for(int j = ; j <= m; j++)
if(i == )
dp[i][j] = f[i][j];
else
{
dp[i][j] = dp[i-][j]+f[i][j];
}
if(i != )
solve(i, , m);
}
cout<<*min_element(dp[n]+, dp[n]+m+)<<endl;
}
return ;
}

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

  1. cdq分治的小结

    cdq分治 是一种特殊的分治 他的思想: 1.分治l,mid 2.分治mid+1,r 3.计算l,mid对mid+1,r的影响 3就是最关键的地方 这也是cdq的关键点 想到了这一步基本就可以做了 接 ...

  2. 【学习笔记】CDQ分治(等待填坑)

    因为我对CDQ分治理解不深,所以这篇博客只是我现在的浅显理解有任何不对的,希望大佬指出. 首先就是CDQ分治适用的题型: (1)带修改,但修改互相独立 (2)必须允许离线 (3)解决数据结构的题,能把 ...

  3. CDQ分治&整体二分学习个人小结

    目录 小结 CDQ分治 二维LIS 第一道裸题 bzoj1176 Mokia bzoj3262 陌上花开 bzoj 1790 矩形藏宝地 hdu5126四维偏序 P3157 [CQOI2011]动态逆 ...

  4. CDQ分治小结

    CDQ分治小结 warning:此文仅用博主复习使用,初学者看的话后果自负.. 复习的时候才发现以前根本就没写过这种东西的总结,简单的扯一扯 cdq分治的经典应用就是解决偏序问题 比如最经典的三维偏序 ...

  5. CDQ分治与整体二分小结

    前言 这是一波强行总结. 下面是一波瞎比比. 这几天做了几道CDQ/整体二分,感觉自己做题速度好慢啊. 很多很显然的东西都看不出来 分治分不出来 打不出来 调不对 上午下午晚上的效率完全不一样啊. 完 ...

  6. CDQ分治总结

    \(CDQ\)分治小结 标签:知识点总结 阅读体验:https://zybuluo.com/Junlier/note/1326395 身为一个资深菜鸡 \(CDQ\)分治一开始学了一晚上,后来某一天又 ...

  7. BZOJ 2683 简单题 ——CDQ分治

    [题目分析] 感觉CDQ分治和整体二分有着很本质的区别. 为什么还有许多人把他们放在一起,也许是因为代码很像吧. CDQ分治最重要的是加入了时间对答案的影响,x,y,t三个条件. 排序解决了x ,分治 ...

  8. HDU5618 & CDQ分治

    Description: 三维数点 Solution: 第一道cdq分治...感觉还是很显然的虽然题目不能再傻逼了... Code: /*=============================== ...

  9. 初识CDQ分治

    [BZOJ 1176:单点修改,查询子矩阵和]: 1176: [Balkan2007]Mokia Time Limit: 30 Sec  Memory Limit: 162 MBSubmit: 200 ...

随机推荐

  1. php处理字符串常用函数

    1查找字符位置函数: strpos($str,search,[int]):查找search在$str中的第一次位置从int开始: stripos($str,search,[int]):函数返回字符串在 ...

  2. 获取iframe 内元素的方法

    1,原生的方法 首先给iframe 设置 id 属性 var obj = document.getElementById('iframe').contentWindow; setTimeout(fun ...

  3. JAXB - Annotations, Top-level Elements: XmlRootElement

    A class that describes an XML element that is to be a top-level element, i.e., one that can function ...

  4. js 获取url中的查询字符串

    function getUrlParam(name) { var reg = new RegExp("(^|&)" + name + "=([^&]*)( ...

  5. VBA实现随意输入组合码,查询唯一标识码

    记录背景: 需要在excel中查询出组合码,对应的唯一标识码. 举例 组合码:4+5+6+9+1*2   标识码:A1 界面随意输入组合码:1*2+4+5+6+9  输出标识码:A1 VBA实现: P ...

  6. C#中的集合

    [集合不同于数组,是一组可变类型的.可变数量的元素的组合,这些元素可能共享某些特征,需要以某种操作方式一起进行操作.一般来讲,为了便于操作这些元素的类型是相同的] [集合与数组的区别:数组是连续的.同 ...

  7. ###学习《Effective C++》

    开源中国. #@date: 2014-06-16 #@author: gerui #@email: forgerui@gmail.com 前几天买了好几本书,其中有一本是<Effective C ...

  8. [jquery]基础篇--this与$this区别

    参考: http://www.cnblogs.com/hannover/p/4109779.html 1.JQuery this和$(this)的区别 相信很多刚接触JQuery的人,很多都会对$(t ...

  9. web前端面试题收集(一)

    CSS中margin和padding的区别? Javascript中如何检测一个变量是一个String类型?请写出函数实现. 网页中实现一个计算当年还剩多少时间的倒计时程序,要求网页上实时动态显示“x ...

  10. Python快速入门学习笔记(二)

    注:本学习笔记参考了廖雪峰老师的Python学习教程,教程地址为:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb49318210 ...