有一些问题很多时间都坑在斜率和凸壳上了么……感觉斜率和凸壳各种搞不懂……
我们看到,整体二分的过程实质上是个按照数值来划分操作序列的过程,于是我们的复杂度也就和操作序列的长度线性相关,那么我们在中间维护一些信息的时候,就一定不能有何数列长线性相关的东西,否则会破坏其时间复杂度,具体的复杂度证明请见2013年集训队XHR论文。
贴个典型性的代码吧,带修改区间第k小数
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #define maxn 220000 #define inf 1000000000 using namespace std; struct query { int x,y,k,s,tp,cur; }q[maxn],q1[maxn],q2[maxn]; int a[maxn],ans[maxn],tmp[maxn],t[maxn]; int n,m,num,cnt; void add(int x,int y) { for (int i=x;i<=n;i+=(i&-i)) t[i]+=y; } int ask(int x) { int tmp=0; for (int i=x;i>0;i-=(i&-i)) tmp+=t[i]; return tmp; } void divide(int head,int tail,int l,int r) { //cout<<head<<' '<<tail<<' '<<l<<' '<<r<<endl; if (head>tail) return ; if (l==r) { for (int i=head;i<=tail;i++) if (q[i].tp==3) ans[q[i].s]=l;//,cout<<l<<endl; return ; } int mid=(l+r)>>1; for (int i=head;i<=tail;i++) { if (q[i].tp==1&&q[i].y<=mid) add(q[i].x,1); else if (q[i].tp==2&&q[i].y<=mid) add(q[i].x,-1); else if (q[i].tp==3) tmp[i]=ask(q[i].y)-ask(q[i].x-1); } for (int i=head;i<=tail;i++) { if (q[i].tp==1&&q[i].y<=mid) add(q[i].x,-1); else if (q[i].tp==2&&q[i].y<=mid) add(q[i].x,1); } int l1=0,l2=0; for (int i=head;i<=tail;i++) if (q[i].tp==3) { if (q[i].cur+tmp[i]>q[i].k-1)//q[i].cur+tmp[i]表示累积了多少个数 q1[++l1]=q[i]; else { q[i].cur+=tmp[i]; q2[++l2]=q[i]; } } else { if (q[i].y<=mid) q1[++l1]=q[i]; else q2[++l2]=q[i]; } for (int i=1;i<=l1;i++) q[head+i-1]=q1[i]; for (int i=1;i<=l2;i++) q[head+l1+i-1]=q2[i]; divide(head,head+l1-1,l,mid); divide(head+l1,tail,mid+1,r); } int main() { //freopen("ranking.in","r",stdin); //freopen("ranking.out","w",stdout); scanf("%d%d",&n,&m); for (int i=1;i<=n;i++) { scanf("%d",&a[i]); q[++num].x=i; q[num].y=a[i]; q[num].tp=1; q[num].s=0; } char sign; int x,y,z; for (int i=1;i<=m;i++) { scanf("\n%c",&sign); if (sign=='Q') { scanf("%d%d%d",&x,&y,&z); q[++num].x=x,q[num].y=y,q[num].k=z; q[num].tp=3; q[num].s=++cnt; } else { scanf("%d%d",&x,&y); q[++num].x=x; q[num].y=a[x]; q[num].tp=2; q[num].s=0; q[++num].x=x; q[num].y=y; q[num].tp=1; q[num].s=0; a[x]=y; } } divide(1,num,0,inf); for (int i=1;i<=cnt;i++) printf("%d\n",ans[i]); return 0; }
来看一道例题:
[Poi2011] Meteors
有n个国家和m个空间站,每个空间站都属于一个国家,一个国家可以有多个空间站,所有空间站按照顺序形成一个环,也就是说,m号空间站和1号空间站相邻。
现在,将会有k场流星雨降临,每一场流星雨都会给区间[li,ri]内的每个空间站带来ai单位的陨石,每个国家都有一个收集陨石的目标pi,即第i个国家需要收集pi单位的陨石。
询问:每个国家最早完成陨石收集目标是在第几场流星雨过后。
数据范围:1<=n,m,k<=300000
对于单个查询(假设为第i个国家),我们可以二分k,每次对于一个区间[l,r],手动模拟一下在第mid场流星雨过后,第i个国家一共收集到了多少单位的陨石,如果比pi大,那么答案在[l,mid]范围内,否则答案在[mid+1,r]范围内。
对于多组查询,我们也可以这么做。首先,我们需要用一个列表id[]记录所有查询的编号,刚开始的时候,id[]自然是递增的.同时,我们用一个数组cur[i]记录下,第i个国家在l-1场流星雨过后,收集到的陨石的数目。
主过程为void solve(int head,int tail,int l,int r),表示对于id[head]到id[tail]的所有询问,在[l,r]范围内查询答案,通过上一层的操作,我们保证id[head]到id[tail]的所有询问的答案都在[l,r]范围内。
首先,我们先模拟[l,mid]这么多次操作(在询问重新划分之后,必须要再次模拟,将数组清空),用树状数组或者是线段树计算出在[l,mid]场流星雨之后,每个空间站收集到的陨石的数目。
然后我们查询,每个国家收集到的陨石的数目,要注意的是,我们需要用链表储存每个国家对应的空间站,并且一一枚举,用tmp[id[i]]表示国家id[i]收集到的陨石的数目。
那么从[1,mid]这么多次操作之后,国家id[i]收集到的陨石数目就是tmp[id[i]]+cur[id[i]],如果tmp[id[i]]+cur[id[i]]>p[id[i]],那么表明对于国家id[i],其答案在[l,mid]这个范围内,否则其答案在[mid+1,r]范围内,并将tmp[id[i]]累加到cur[id[i]]上。
还有一个坑点是,tmp[id[i]]可能很大,会爆掉long long,所以如果枚举一个国家的所有空间站的时候,发现tmp[id[i]]已经大于p[id[i]]了,那么就break好了,不然会出错。
因为可能会出现怎么也无法满足的情况,所以我们需要多增加一场流星雨,这场流星雨的数量为infi,保证能够让所有国家都满足要求,那么最后,对于所有答案为k+1的询问,输出NIE就行了。
总的来说,整体二分就是将所有询问一起二分,然后获得每个询问的答案。
CDQ相比整体二分略有不同,整体二分是对答案进行二分,而CDQ分治则是对于所有操作进行二分。
一开始的时候,一般都需要先排序一下,将所有的操作按照一定的性质排起来(Cash一题是按-a[i]/b[i]从大到小排序,Mokia一题是按照x坐标从小到大排序),否则的话和直接暴力没有区别。
接着就是对所有操作进行二分,对于一个区间[l,r],我们首先要对[l,mid]范围内的操作进行查询,所以需要将[l,r]范围内的操作重排,保证[l,mid]范围内的操作的id都<=mid,然后再对[l,mid]范围内的操作进行递归求解。
递归完之后,[l,mid]范围内的所有操作都已经按照先后的顺序排序好了,但是[mid+1,r]范围内的操作还是按照某个关键字排序。我们根据[l,mid]范围内的操作更新[mid+1,r]范围内的操作(在Cash一题中是更新它们的f[]数组,在Mokia中则要和整体二分差不多,先用[l,mid]范围内的操作模拟,再更新[mid+1,r]范围内所有查询的答案)。
更新完之后,我们再递归求解[mid+1,r]。
因为大神CDQ的论文已经讲得很详细了,网上关于CDQ分治的资料也挺多的,就不再讲了,感觉这些神奇的算法还是挺好用的,尤其是在自己懒的时候(许多高级数据结构题的代码量真是醉了……)。
三份代码见下面:
http://victorwonder.is-programmer.com/posts/70210.html?utm_source=tuicool
Meteors
#include <cstdio> #include <algorithm> #include <iostream> #include <cstring> #include <cstdlib> #include <utility> #include <bitset> #include <vector> #include <string> #include <stack> #include <queue> #include <ctime> #include <cmath> #include <list> #include <map> #include <set> using namespace std; typedef long long ll; typedef double D; typedef pair<int,int> pr; const int infi=1000000010; const int N=500010; const int M=2000100; struct node{int x,y,z;}p[N]; int n,m,k,c[N],id[N],ans[N]; int g[N],to[N],nxt[N],tot; int tol[N],tor[N]; ll h[N],tmp[N],cur[N]; void read(int &a) { char ch; while (!((ch=getchar())>='0'&&ch<='9')); a=ch-'0'; while ((ch=getchar())>='0'&&ch<='9') (a*=10)+=ch-'0'; } void add(int pos,int x) {while (pos<=m) h[pos]+=x,pos+=(pos&-pos);} void adddt(int x,int y,int z) {add(x,z); add(y+1,-z);} ll sum(int pos) {ll t=0; while (pos>0) t+=h[pos],pos-=(pos&-pos);return t;} void addop(int x,int y,int z,int i) {p[i].x=x; p[i].y=y; p[i].z=z;} void addpt(int x,int y) {to[++tot]=y; nxt[tot]=g[x]; g[x]=tot;} void solve(int head,int tail,int l,int r) { if (head>tail) return; int i,k,x,mid=(l+r)>>1,lnum=0,rnum=0; if (l==r) { for (i=head;i<=tail;i++) ans[id[i]]=l; return; } for (i=l;i<=mid;i++) { if (p[i].x<=p[i].y) adddt(p[i].x,p[i].y,p[i].z); else adddt(p[i].x,m,p[i].z),adddt(1,p[i].y,p[i].z); } for (i=head;i<=tail;i++) { tmp[id[i]]=0; for (k=g[id[i]];k;k=nxt[k]) { tmp[id[i]]+=sum(to[k]); if (tmp[id[i]]+cur[id[i]]>c[id[i]]) break; } if (cur[id[i]]+tmp[id[i]]>=c[id[i]]) tol[++lnum]=id[i]; else tor[++rnum]=id[i],cur[id[i]]+=tmp[id[i]]; } for (i=l;i<=mid;i++) { if (p[i].x<=p[i].y) adddt(p[i].x,p[i].y,-p[i].z); else adddt(p[i].x,m,-p[i].z),adddt(1,p[i].y,-p[i].z); } for (i=0;i<lnum;i++) id[head+i]=tol[i+1]; for (i=0;i<rnum;i++) id[head+lnum+i]=tor[i+1]; solve(head,head+lnum-1,l,mid); solve(head+lnum,tail,mid+1,r); } int main() { int i,x,y,z; scanf("%d%d",&n,&m); for (i=1;i<=m;i++) { scanf("%d",&x); addpt(x,i); } for (i=1;i<=n;i++) { scanf("%d",&c[i]); id[i]=i; } scanf("%d",&k); for (i=1;i<=k;i++) { scanf("%d%d%d",&x,&y,&z); addop(x,y,z,i); } addop(1,m,infi,++k); solve(1,n,1,k); for (i=1;i<=n;i++) if (ans[i]!=k) printf("%d\n",ans[i]); else puts("NIE"); return 0; }
#include <cstdio> #include <algorithm> #include <iostream> #include <cstring> #include <cstdlib> #include <utility> #include <bitset> #include <vector> #include <string> #include <stack> #include <queue> #include <ctime> #include <cmath> #include <list> #include <map> #include <set> using namespace std; typedef long long ll; typedef double D; typedef pair<int,int> pr; const int infi=2147483647; const int N=100010; struct node{D a,b,r,k;}p[N]; struct func{D x,y;}f[N],v[N]; D ans[N],S; int n,id[N],s[N]; bool operator < (func a,func b) {return a.x<b.x||a.x==b.x&&a.y<b.y;} bool cmp(const int &a,const int &b) {return p[a].k>p[b].k;} D cross(func a,func b,func c) {return (b.x-a.x)*(c.y-b.y)-(b.y-a.y)*(c.x-b.x);} D calc(func t,int i) {return t.x*p[i].a+t.y*p[i].b;} void solve(int l,int r,D maxnum) { if (l==r) { ans[l]=max(ans[l],maxnum); f[l].y=ans[l]/(p[l].a*p[l].r+p[l].b); f[l].x=f[l].y*p[l].r; return; } int i,mid=(l+r)>>1,t1=l,t2=mid+1,t=0,h=0; for (i=l;i<=r;i++) if (id[i]<=mid) s[t1++]=id[i]; else s[t2++]=id[i]; memcpy(id+l,s+l,sizeof(int)*(r-l+1)); solve(l,mid,maxnum); for (i=l;i<=mid;v[t++]=f[i++]) while (t&&cross(v[t-2],v[t-1],f[i])>=0) t--; for (i=mid+1;i<=r;i++) { while (h<t-1&&calc(v[h],id[i])<calc(v[h+1],id[i])) h++; ans[id[i]]=max(ans[id[i]],calc(v[h],id[i])); } solve(mid+1,r,ans[mid]); merge(f+l,f+mid+1,f+mid+1,f+r+1,v); memcpy(f+l,v,sizeof(func)*(r-l+1)); } int main() { int i; scanf("%d%lf",&n,&S); for (i=0;i<n;i++) { scanf("%lf%lf%lf",&p[i].a,&p[i].b,&p[i].r); p[i].k=-p[i].a/p[i].b; id[i]=i; } sort(id,id+n,cmp); solve(0,n-1,S); printf("%.3lf",ans[n-1]); return 0; }
#include <cstdio> #include <algorithm> #include <iostream> #include <cstring> #include <cstdlib> #include <utility> #include <bitset> #include <vector> #include <string> #include <stack> #include <queue> #include <ctime> #include <cmath> #include <list> #include <map> #include <set> using namespace std; typedef long long ll; typedef double D; typedef pair<int,int> pr; const int infi=2147483647; const int N=500010; const int M=2000100; struct node{int op,x,y,z,t,id;}p[N],s[N]; int S,n,num,tot,id[N]; int h[M],ans[N]; bool cmp(const node &a,const node &b) {return a.x<b.x;} void addquery(int i,int op,int x,int y,int z,int id) { p[i].id=i; p[i].op=op; p[i].x=x; p[i].y=y; p[i].z=z; p[i].t=id; } void add(int pos,int x) {while (pos<=n) h[pos]+=x,pos+=(pos&(-pos));} ll sum(int pos) {ll t=0; while (pos>0) t+=h[pos],pos-=(pos&(-pos));return t;} void solve(int l,int r) { if (l==r) return; int mid=(l+r)>>1,i,t1=l-1,t2=mid,t=l; for (i=l;i<=r;i++) if (p[i].id<=mid) s[++t1]=p[i]; else s[++t2]=p[i]; memcpy(p+l,s+l,sizeof(node)*(r-l+1)); for (i=mid+1;i<=r;i++) if (p[i].op==2) { for (;t<=mid&&p[t].x<=p[i].x;t++) if (p[t].op==1) add(p[t].y,p[t].z); ans[p[i].t]+=sum(p[i].y)*p[i].z; } for (i=l;i<t;i++) if (p[i].op==1) add(p[i].y,-p[i].z); solve(l,mid); solve(mid+1,r); } int main() { int i,k,x,y,z,w; scanf("%d%d",&S,&n); while (~scanf("%d",&k)) { if (k==3) break; if (k==1) { scanf("%d%d%d",&x,&y,&z); addquery(++tot,1,x,y,z,0); } else { scanf("%d%d%d%d",&x,&y,&z,&w); num++; addquery(++tot,2,z,w,1,num); addquery(++tot,2,x-1,y-1,1,num); addquery(++tot,2,x-1,w,-1,num); addquery(++tot,2,z,y-1,-1,num); } } sort(p+1,p+1+tot,cmp); solve(1,tot); for (i=1;i<=num;i++) printf("%d\n",ans[i]); return 0; }
cdq分治是一种特别的分治方法,它由cdq神牛于09国家集训队作业中首次提出,因此得名。
首先,cdq分治属于分治的一种。它一般只能处理非强制在线的问题,除此之外这个算法作为某些复杂算法的替代品几乎是没有缺点的。
考虑这样一个问题,有若干询问和若干修改,要求在O(nlogn)时间复杂度内回答所有的询问。(下把询问与修改统称为操作)
我们把[l,r]代表当前处理的操作的区间,即处理第l个到第r个操作,先找到区间的中间m=(l+r)/2
(1)然后对于前一半[l,m]我们先递归解决。
(2)对于所有在[l,m]内的修改操作,枚举处理它对于[m,r]内的所有操作『影响』。
(3)之后递归处理[m,r]这一区间。
复杂度分析:分治共有log(n)层,那么要求每一层都在线性的时间复杂度内完成,才能保证总时间复杂度是O(nlogn)
对于不同的题目,修改和询问是不一样的。在某些修改之间互相独立的题目下,还可以调换(2)(3)的顺序。
当然cdq分治也可以想其他的分治方法一样,巧妙利用归并排序,来实现每层O(logn)
就拿斜率优化dp来说,并且假设要用BST维护决策下凸性,那么就不得不写一个BST。想在考场上调出一个这样的程序,不说很难,也是很耗时耗力的。
如果运用cdq分治,事情就简单得多了:(1)(3)步不说,在(2)中将[l,m]所有的已经统计完的决策点排序,然后可以很方便地获得这个下凸壳,直接两个指针扫一下就可以在O(n)时间复杂度里统计出他们对[m,r]内的状态的影响了。这样做代码很短,容易在考试时候调试出来,节省时间。
好吧,我还是很不擅长描述算法...
没看懂的可以到cdq分治相关看下这篇,写得很不错
例题:
T1:BZOJ 3110: [Zjoi2013]K大数查询
网上看到各种Trie套Trie做法,显然是在坑,因为这题理论分析发现Trie套Trie无论在时间复杂度还是空间复杂度都是挂的,至于最后数据出的弱是另一回事。这题的正解应该是cdq分治。把答案当轴进行分治,对答案的操作,统计出当答案为m=(l+r)时候,每个询问的区间里>=它的数有多少个,这个用从头到尾扫一遍,然后线段树维护的方法做。
如果询问i区间[li,ri]里>=m的数<ci个那么显然,它的ans应该<m,归入0类;反之,则应该大于等于m,归入1类。
如果修改i,增加的数的值>=m那么归入1类,否则归入0类。然后我们发现,归入0类的询问i,在之后的分治中被检验到的ans必然是<=m的,那么对于每个被归入1类的修改,它所有的在区间[l[i],r[i]]内的数都会被统计进入『>=验证到的ans』的数的个数中,那么直接在ci上减去就可以了。
总时间复杂度O(nlog^2n)
cdq分治:http://ideone.com/QmgPc6
Trie套Trie:http://ideone.com/v1VeSN
T2:BZOJ 1492: [NOI2007]货币兑换Cash
斜率优化dp,相当强大的应用。这个题不同于一般的斜率优化dp,需要用bst维护凸壳。用了cdq分治之后,询问和决策就都变成离线了,直接排个序,当成普通的斜率优化dp来做,相当犀利。这里有一个特殊情况,就是对[m+1,r]也是要进行某个关键字的排序的,所以必须事先排序好并记录。
注意,这个时候时间复杂度是O(nlogn) 空间复杂度是O(nlogn)的,如果题目卡内存就不要用了,不过也没有这么sxbk的出题人吧。
http://ideone.com/ISBnao
T3:BZOJ 2527: [Poi2011]Meteors
这题不得不说有点神。大体思路和T1一样,然后就是这题最神的地方,必须要开unsigned long long才能通过本题 ....
如果程序调不出错,或许可以试试修改掉这个玩意儿。我的程序运行速度极慢= =
http://ideone.com/M99tLh
T4: Codeforces #232 div.1 C - On Changing Tree
昨天考试遇到的题目。当时写了树链剖分,然后爆栈了呵呵,没心情写手工栈导致爆零。今天突然意识到cdq分治可做,复杂度O(nlogn),观察了一下时限3s,于是各种乱搞水过
http://ideone.com/KdMvZ0
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
代码:
/*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 = 50000 + 100; LL x1[maxn][2], x2[maxn][2], 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[][2], int x) { LL res = 0; for(; x > 0; x -= (x&(-x))) { if(a[x][0] == cnt) res += a[x][1]; } return res; } LL query(int l, int r) { return query(x1, l)*(r-l+1)+ (r+1)*(query(x1, r)-query(x1, l)) - (query(x2, r)-query(x2, l)); } void add(LL a[][2], int x, LL c) { for(; x <= n; x += ((-x)&x)) { if(a[x][0] == cnt) a[x][1] += c; else a[x][0] = cnt, a[x][1] = c; } } void add(int l, int r, int c) { add(x1, l, c); add(x2, l, (LL)l*c); add(x1, r+1, -c); add(x2, r+1, (LL)(r+1)*(-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 == 2) { ans[rk[i]] = ll; } return; } int m1 = (ll+rr)>>1, m2 = (l+r)>>1; cnt++; t1[0] = t2[0] = 0; for(int i = l; i <= r; i++) { if(q[rk[i]].type == 1) { if(q[rk[i]].c <= m1) { add(q[rk[i]].l, q[rk[i]].r, 1); t1[++t1[0]] = rk[i]; } else { t2[++t2[0]] = 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[0]] = rk[i]; } else { t1[++t1[0]] = rk[i]; } } } m2 = l+t1[0]-1; for(int i = l; i <= r; i++) { if(i <= m2) { rk[i] = t1[i-l+1]; } else { rk[i] = t2[i-m2]; } } solve(ll, m1, l, m2); solve(m1+1, rr, m2+1, r); } int main() { scanf("%d%d", &n, &m); for(int i = 1; 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 == 1) q[i].c = (LL)n-q[i].c+1; q[i].id = i; } solve(1, 2*n+1, 1, m); for(int i = 1; i <= m; i++) { if(q[i].type == 2) { printf("%d\n", n-ans[i]+1); } } return 0; }
//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; }UPDATE:
#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 = 5555; int dp[111][maxn], magic[maxn][maxn], f[maxn][maxn]; PII t[4][maxn], t1[maxn]; void solve(int pos, int l, int r) { if(l == r) { t[0][l] = PII(l-magic[pos-1][l], l); t[1][l] = PII(magic[pos][l]+l, l); t[2][l] = PII(-l-magic[pos-1][l], l); t[3][l] = PII(magic[pos][l]-l, l); return ; } int mid = (l+r)>>1; solve(pos, l, mid); solve(pos, mid+1, r); int res = inf; for(int i = l, j = mid+1; i <= mid; i++) { while(j <= r && t[0][j].first <= t[1][i].first) { res = min(res, dp[pos-1][t[0][j].second]); j++; } dp[pos][t[1][i].second] = min(dp[pos][t[1][i].second], res+f[pos][t[1][i].second]); } res = inf; for(int i = mid+1, j = l; i <= r; i++) { while(j <= mid && t[2][j].first <= t[3][i].first) { res = min(res, dp[pos-1][t[2][j].second]); j++; } dp[pos][t[3][i].second] = min(dp[pos][t[3][i].second], res+f[pos][t[3][i].second]); } for(int k = 0; k < 4; k++) { int l1 = l, l2 = mid+1; 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 = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &f[i][j]); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) scanf("%d", &magic[i][j]); for(int i = 1; i <= n; i++) { for(int j = 1; j <= m; j++) if(i == 1) dp[i][j] = f[i][j]; else { dp[i][j] = dp[i-1][j]+f[i][j]; } if(i != 1) solve(i, 1, m); } cout<<*min_element(dp[n]+1, dp[n]+m+1)<<endl; } return 0; }