zkw segment-tree 真是太棒了(真的重口味)!写篇博客纪念入门

emmm...首先我们来介绍一下 zkw 线段树这个东西(俗称 "重口味" ,与 KMP 类似,咳咳...)

zkw 线段树的介绍

其实 zkw 线段树和普通线段树区别没多大(区别可大了去了!)

emmm...起码它们的思想是一致的,都是节点维护区间信息嘛。

只不过...普通线段树的维护和查询是递归式,而 zkw线段树是循环式的...

但是不要以为 zkw线段树只是靠循环加速上位的!

zkw线段树能支持非常多强(luan)如(qi)闪(ba)电(zao)的操作(最后例题讲)。

zkw 线段树 与普通线段树 的比较

emmm...这里你看着 普通线段树 的节点比 zkw线段树 的小对吧,但其实两者差不多,(因为线段树是要开4倍空间的啊,这里只是没有画出用不到的节点罢了),

 

zkw 线段树的形态

其实上图...还是无法体现zkw 线段树的具体形态的,(但是相信聪明的你一定看懂了所以我就不讲了)

emmm...于是乎还是上图解释一切

zkw 线段树的建立

首先你要写个循环,让 m 这个值(也就是非叶子节点)大于 n (也就是总叶子结点数),以此保证 这棵树的叶子 能够容纳你要维护的 n 个值

然后你要从 m 倒推 到 1 号节点(注意是 m 倒推回 1 ,保证维护每个节点时该节点的孩子都已经被维护完毕让每个节点维护它左右孩子的信息

代码实现

这里我们假设要维护的信息有:区间和,区间最小值,区间最大值 。 下同

inline void build(int n){
// 维护这么多信息都只需要这么几行,可见维护信息单一时代码应该会短的不像话(压行过的话大概三四行)
for(m=;m<=n;m<<=);
for(int i=m+;i<=m+n;++i)
sum[i]=mn[i]=mx[i]=read();
for(int i=m-;i;--i)
sum[i]=a[i<<]+a[i<<|],
mn[i]=min(mn[i<<],mn[i<<|]),
mx[i]=max(mx[i<<],mx[i<<|]);
}

但是,这里对 mn 和 mx 的处理是在无修改操作的基础上实行的,所以这样写并不支持修改操作。

那么我们可以这样写:

inline void build(){
for(m=;m<=n;m<<=);
for(int i=m+;i<=m+n;++i)
sum[i]=mn[i]=mx[i]=read();
for(int i=m-;i;--i){
sum[i]=sum[i<<]+sum[i<<|]; mn[i]=min(mn[i<<],mn[i<<|]),
mn[i<<]-=mn[i],mn[i<<|]-=mn[i]; mx[i]=max(mx[i<<],mx[i<<|]),
mx[i<<]-=mx[i],mx[i<<|]-=mx[i];
}
}

PS:以下的操作(单点、区间更新,单点、区间查询)所附的代码,都基于可修改的版本

zkw 线段树的更新

单点更新

这个单点更新还是比较好解决的,你只要找到更新的节点所在的叶子结点,然后修改后一直向父节点更新即可。

(这个。。。就不用上图了吧...你脑补一下就差不多了)

代码实现

这里我们假设将一个节点的值增加 v (修改的话...就记录一下原数组,然后算差值就好了吧?)

inline void update_node(int x,int v,int A=){
x+=m,mx[x]+=v,mn[x]+=v;for(;x>;x>>=){
sum[x]+=v;
A=min(mn[x],mn[x^]);
mn[x]-=A,mn[x^]-=A,mn[x>>]+=A;
A=max(mx[x],mx[x^]),
mx[x]-=A,mx[x^]-=A,mx[x>>]+=A;
}
}

区间更新

这个东西...有点麻烦(你得稍微感性理解)。

就是说...你每次要更新一段区间的时候,你要让左端点 -1 ,右端点 +1 。

然后你在更新权值的时候要判断 左端点当前所处的节点是否是它父节点的左孩子,

是的话就让该节点的兄弟(也就是它父节点的右孩子)得到更新,否则不做处理,

然后左节点再向右移一位(也就是跳到了父节点),重复迭代以上步骤。

那么右端点呢?其实也就是和左端点反着来了而已。

还有一点,循环的终止条件?这个简单,就是当左右端点所处的节点是兄弟节点的时候结束循环。

类似的,你更新一个节点时 同样可以用这种方法维护(只不过这样就更麻烦了啊)。

这样我们可以看到要被更新的区间都已经被染成黄色了。但是,zkw 没有下传标记啊!

那么我们查询的区间如果在染成黄色的节点的下部(也就是黄色节点的子树内)该怎么办?

我们可以这样...这样...没错!标记永久化!

因为我们已经将一个节点的标记永久化了,那么在该节点被访问到的时候,只要将当前查询到的、包含在该节点所管辖区间范围内的  区间长度乘上标记值,累加入答案即可。

(具体实现得看代码)

区间更新的特殊情况

同学们有没有注意到一种区间查询的特殊情况?没错,就是右区间+1后到达下一层的特殊情况

就以上图为例,假设维护区间为 1 ~ 7 ,现在对 2 ~ 7 进行区间加操作,那么  t = 7+1 = 8 ,于是 t 就到达了不存在的第 5 层!

现在你想的一定是这种情况该怎么避免这种情况(其实很简单,你在建树确定 m 的值的时候,将判断条件改成 " m<=n+1 " 就行了)

但我现在要证明这种情况不需要避免也不会出问题(基本上...吧?)

我们可以看到,s 和 t 在跳到 0 和 1 时满足了终止条件,并且需要更新的节点都得到了更新,而且,其实 t 就没有更新过节点...

代码实现

这里我们假设要将一段区间的每个数加上 v ,然后维护的信息同上

inline void update_part(int s,int t,int v){
int A=,lc=,rc=,len=;
for(s+=m-,t+=m+;s^t^;s>>=,t>>=,len<<=){ //在这里的 add 就是标记数组了
if(s&^) add[s^]+=v,lc+=len, mn[s^]+=v,mx[s^]+=v;
if(t&) add[t^]+=v,rc+=len, mn[t^]+=v,mx[t^]+=v; sum[s>>]+=v*lc, sum[t>>]+=v*rc; A=min(mn[s],mn[s^]),mn[s]-=A,mn[s^]-=A,mn[s>>]+=A,
A=min(mn[t],mn[t^]),mn[t]-=A,mn[t^]-=A,mn[t>>]+=A; A=max(mx[s],mx[s^]),mx[s]-=A,mx[s^]-=A,mx[s>>]+=A,
A=max(mx[t],mx[t^]),mx[t]-=A,mx[t^]-=A,mx[t>>]+=A;
}
for(lc+=rc;s>1;s>>=1){
sum[s>>]+=v*lc;
A=min(mn[s],mn[s^]),mn[s]-=A,mn[s^]-=A,mn[s>>]+=A,
A=max(mx[s],mx[s^]),mx[s]-=A,mx[s^]-=A,mx[s>>]+=A;
}
}

这里的 lc 和 rc 的所代表的含义需要讲一下

lc 代表左端点所处的节点下有多少长度的区间在更新区间内, rc 同理 ,通俗一点地说,就是 s 和 t 所分别走过的节点中包含的更新过的区间的总长

zkw线段树的查询

单点查询

这个没什么好说的吧,你从叶子结点一直跳父节点,把途中节点的 mn (或者 mx )权值累加,最后得到的就是答案

代码实现

inline int query_node(int x,int ans=){
for(x+=m;x;x>>=) ans+=mn[s];
return ans;
}

区间查询

什么?zkw线段树的区间查询?我不会啊。

那么这里的区间查询...其实有点难说啊!要不就直接上代码得了?咳咳...

这个其实和上面的区间更新的思路差不多,可能要讲的就是标记累加的问题了吧。

那么 lc 和 rc 之前已经讲过了,就是 s 节点和 t 节点分别走过的节点中所包含的更新区间的长度。

那么 add 这个数组啊...啊...啊...这个数组啊,它...要不我们直接看代码吧?

它好在哪里啊?好难说啊...其实它就是记录了你每次大块累加区间时的副产品啊,类似于线段树的懒标记。

但是和普通线段树不一样的是,线段树的查询是自上而下查询(顺便释放标记)然后又自下而上的递归回去的,

而 zkw 的查询是直接自下而上的,于是它无法释放标记,于是它就在遇到某个打过懒标记的节点时,将当前查询到的区间长度乘上标记值,累加入答案。

(所以这还是懒标记啊!不上图了自行脑补。emmm...算了吧那还是上一张图好了

代码实现

inline int query_sum(int s,int t){
int lc=,rc=,len=,ans=;
for(s+=m-,t+=m+;s^t^;s>>=,t>>=,len<<=){
if(s&^) ans+=sum[s^]+len*add[s^],lc+=len;
if(t&) ans+=sum[t^]+len*add[t^],rc+=len; if(add[s>>]) ans+=add[s>>]*lc;
if(add[t>>]) ans+=add[t>>]*rc;
}
for(lc+=rc,s>>=1;s;s>>=)
if(add[s]) ans+=add[s]*lc;
return ans;
} inline int query_min(int s,int t,int L=,int R=,int ans=){
if(s==t) return query_node(s); // 单点要特判, 下同
for(s+=m,t+=m;s^t^;s>>=,t>>=){ // 这里 s 和 t 直接加上 m
L+=mn[s],R+=mn[t];
if(s&^) L=min(L,mn[s^]);
if(t&) R=min(R,mn[t^]);
}
for(ans=min(L,R),s>>=;s;s>>=) ans+=mn[s];
return ans;
} inline int query_max(int s,int t,int L=,int R=,int ans=){
if(s==t) return query_node(s);
for(s+=m,t+=m;s^t^;s>>=,t>>=){
L+=mx[s],R+=mx[t];
if(s&^) L=max(L,mx[s^]);
if(t&) R=max(R,mx[t^]);
}
for(ans=max(L,R),s>>=;s;s>>=) ans+=mx[s];
return ans;
}

这里询问时 s 和 t 不能 -1 或 +1 ,不然会查询到旁边不相干的节点。

然后 s == t 的情况要特判一下,防止 s 和 t 一直都不是兄弟,陷入死循环。

zkw 的代码实现(模板)

完全代码

 //by Judge
#include<cstdio>
#include<iostream>
using namespace std;
const int M=1e5+;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
int x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(int x){
if(C><<)Ot();if(x<)sr[++C]=,x=-x;
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
int n,m,q;
int sum[M<<],mn[M<<],mx[M<<],add[M<<];
inline void build(){
for(m=;m<=n;m<<=);
for(int i=m+;i<=m+n;++i)
sum[i]=mn[i]=mx[i]=read();
for(int i=m-;i;--i){
sum[i]=sum[i<<]+sum[i<<|];
mn[i]=min(mn[i<<],mn[i<<|]),
mn[i<<]-=mn[i],mn[i<<|]-=mn[i];
mx[i]=max(mx[i<<],mx[i<<|]),
mx[i<<]-=mx[i],mx[i<<|]-=mx[i];
}
}
inline void update_node(int x,int v,int A=){
x+=m,mx[x]+=v,mn[x]+=v,sum[x]+=v;
for(;x>;x>>=){
sum[x]+=v;
A=min(mn[x],mn[x^]);
mn[x]-=A,mn[x^]-=A,mn[x>>]+=A;
A=max(mx[x],mx[x^]),
mx[x]-=A,mx[x^]-=A,mx[x>>]+=A;
}
}
inline void update_part(int s,int t,int v){
int A=,lc=,rc=,len=;
for(s+=m-,t+=m+;s^t^;s>>=,t>>=,len<<=){
if(s&^) add[s^]+=v,lc+=len, mn[s^]+=v,mx[s^]+=v;
if(t&) add[t^]+=v,rc+=len, mn[t^]+=v,mx[t^]+=v;
sum[s>>]+=v*lc, sum[t>>]+=v*rc;
A=min(mn[s],mn[s^]),mn[s]-=A,mn[s^]-=A,mn[s>>]+=A,
A=min(mn[t],mn[t^]),mn[t]-=A,mn[t^]-=A,mn[t>>]+=A;
A=max(mx[s],mx[s^]),mx[s]-=A,mx[s^]-=A,mx[s>>]+=A,
A=max(mx[t],mx[t^]),mx[t]-=A,mx[t^]-=A,mx[t>>]+=A;
}
for(lc+=rc;s;s>>=){
sum[s>>]+=v*lc;
A=min(mn[s],mn[s^]),mn[s]-=A,mn[s^]-=A,mn[s>>]+=A,
A=max(mx[s],mx[s^]),mx[s]-=A,mx[s^]-=A,mx[s>>]+=A;
}
}
inline int query_node(int x,int ans=){
for(x+=m;x;x>>=) ans+=mn[x]; return ans;
}
inline int query_sum(int s,int t){
int lc=,rc=,len=,ans=;
for(s+=m-,t+=m+;s^t^;s>>=,t>>=,len<<=){
if(s&^) ans+=sum[s^]+len*add[s^],lc+=len;
if(t&) ans+=sum[t^]+len*add[t^],rc+=len;
if(add[s>>]) ans+=add[s>>]*lc;
if(add[t>>]) ans+=add[t>>]*rc;
}
for(lc+=rc,s>>=;s;s>>=) if(add[s]) ans+=add[s]*lc;
return ans;
}
inline int query_min(int s,int t,int L=,int R=,int ans=){
if(s==t) return query_node(s);
for(s+=m,t+=m;s^t^;s>>=,t>>=){
L+=mn[s],R+=mn[t];
if(s&^) L=min(L,mn[s^]);
if(t&) R=min(R,mn[t^]);
}
for(ans=min(L,R),s>>=;s;s>>=) ans+=mn[s];
return ans;
}
inline int query_max(int s,int t,int L=,int R=,int ans=){
if(s==t) return query_node(s);
for(s+=m,t+=m;s^t^;s>>=,t>>=){
L+=mx[s],R+=mx[t];
if(s&^) L=max(L,mx[s^]);
if(t&) R=max(R,mx[t^]);
}
for(ans=max(L,R),s>>=;s;s>>=) ans+=mx[s];
return ans;
} signed main(){ return ;
}

板子题?这个真没有...(不过你可以拿普通线段树的板子题等练手)

默默放上线段树板子题的链接... 

  1. 线段树 1 

  2. 线段树 2

代码

1.

 //by Judge
#include<cstdio>
#include<iostream>
#define ll long long
using namespace std;
const int M=1e5+;
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
inline ll read(){
ll x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(ll x){
if(C><<)Ot();if(x<)sr[++C]=,x=-x;
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
ll n,m,q;
ll sum[M<<],add[M<<];
inline void build(){
for(m=;m<=n;m<<=);
for(int i=m+;i<=m+n;++i) sum[i]=read();
for(int i=m-;i;--i) sum[i]=sum[i<<]+sum[i<<|];
}
inline void update_part(int s,int t,ll v){
ll A=,lc=,rc=,len=;
for(s+=m-,t+=m+;s^t^;s>>=,t>>=,len<<=){
if(s&^) add[s^]+=v,lc+=len;
if(t&) add[t^]+=v,rc+=len;
sum[s>>]+=v*lc,sum[t>>]+=v*rc;
} for(lc+=rc,s>>=;s;s>>=) sum[s]+=v*lc;
}
inline ll query_sum(int s,int t){
ll lc=,rc=,len=,ans=;
for(s+=m-,t+=m+;s^t^;s>>=,t>>=,len<<=){
if(s&^) ans+=sum[s^]+len*add[s^],lc+=len;
if(t&) ans+=sum[t^]+len*add[t^],rc+=len;
if(add[s>>]) ans+=add[s>>]*lc;
if(add[t>>]) ans+=add[t>>]*rc;
} for(lc+=rc,s>>=;s;s>>=) if(add[s]) ans+=add[s]*lc;
return ans;
}
signed main(){
n=read(),q=read(),build();
int opt,x,y; ll k;
while(q--){
opt=read(),x=read(),y=read();
if(opt&) k=read(),update_part(x,y,k);
else print(query_sum(x,y));
} Ot(); return ;
}

2.

emmm...实在是太晚啦(其实是没有研究过区间乘),所以就...您就自个儿研究吧~~~

推荐例题

题目:  无聊的数列

其实这道题用普通线段树 + 懒标记也可以做 (你可以试试?)

但是用了 zkw 之后...那个代码量的差别,我都不想说什么...(诶?貌似普通线段树用了标记永久化之后差不多也是这个码量?)

代码

 //by Judge
#include<cstdio>
#include<iostream>
using namespace std;
const int M=<<;
int n,m,q,opt,L,R,k,d;
int a[M],lt[M],dt[M];
//#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[<<],*p1=buf,*p2=buf;
inline int read(){
int x=,f=; char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=-;
for(;isdigit(c);c=getchar()) x=x*+c-''; return x*f;
}
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(int x){
if(C><<)Ot();if(x<)sr[++C]=,x=-x;
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n'; Ot();
}
inline void build(){
for(int i=m;i;--i) lt[i]=lt[i<<];
}
inline void update(int L,int R,int k,int d){ //update 还是蛮常规的
for(int l=L+m-,r=R+m+;l^r^;l>>=,r>>=){
if(l&^) a[l^]+=k+(lt[l^]-L)*d,dt[l^]+=d;
if(r&) a[r^]+=k+(lt[r^]-L)*d,dt[r^]+=d;
}
}
inline int query(int p,int res){ //query 感性理解一下:非叶子节点存储的是附加值,也就是操作 1 当中加入的等差数列
for(int i=m+p;i;i>>=) res+=a[i]+(p-lt[i])*dt[i];
return res;
}
int main(){
n=read(),q=read(); for(m=;m<=n;m<<=); printf("%d\n",m);
for(int i=;i<=n;++i) a[m+i]=read(),lt[m+i]=i;
build();
while(q--){
opt=read();
if(opt&) L=read(),R=read(),k=read(),d=read(),update(L,R,k,d);
else k=read(),print(query(k,));
} Ot(); return ;
}

View Code

最后推荐一下:  某位大佬的 blog (写的也蛮详细的但没我详细,emmm...但是他那片博客里的区间求最值是错的,坑!)

有趣的 zkw 线段树(超全详解)的更多相关文章

  1. 超全详解Java开发环境搭建

    摘自:https://www.cnblogs.com/wangjiming/p/11278577.html 超全详解Java开发环境搭建   在项目产品开发中,开发环境搭建是软件开发的首要阶段,也是必 ...

  2. zkw线段树详解

    转载自:http://blog.csdn.net/qq_18455665/article/details/50989113 前言 首先说说出处: 清华大学 张昆玮(zkw) - ppt <统计的 ...

  3. 线段树(单标记+离散化+扫描线+双标记)+zkw线段树+权值线段树+主席树及一些例题

    “队列进出图上的方向 线段树区间修改求出总量 可持久留下的迹象 我们 俯身欣赏” ----<膜你抄>     线段树很早就会写了,但一直没有总结,所以偶尔重写又会懵逼,所以还是要总结一下. ...

  4. HDU 1166 - 敌兵布阵 - [单点修改、区间查询zkw线段树]

    题还是那个题:http://www.cnblogs.com/dilthey/p/6827959.html 不过我们今天换一种线段树实现来做这道题: 关于zkw线段树的讲解:https://zhuanl ...

  5. 线段树简单入门 (含普通线段树, zkw线段树, 主席树)

    线段树简单入门 递归版线段树 线段树的定义 线段树, 顾名思义, 就是每个节点表示一个区间. 线段树通常维护一些区间的值, 例如区间和. 比如, 上图 \([2, 5]\) 区间的和, 为以下区间的和 ...

  6. 【bzoj3685】普通van Emde Boas树 权值zkw线段树

    原文地址:http://www.cnblogs.com/GXZlegend/p/6809743.html 题目描述 设计数据结构支持:1 x  若x不存在,插入x2 x  若x存在,删除x3    输 ...

  7. ZKW线段树

    简介 zkw线段树虽然是线段树的另一种写法,但是本质上已经和普通的递归版线段树不一样了,是一种介于树状数组和线段树中间的存在,一些功能上的实现比树状数组多,而且比线段树好写且常数小. 普通线段树采用从 ...

  8. BZOJ3173 TJOI2013最长上升子序列(Treap+ZKW线段树)

    传送门 Description 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.每插入一个数字,我们都想知道此时最长上升子序列长度是多少? Input ...

  9. 【POJ3468】【zkw线段树】A Simple Problem with Integers

    Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. On ...

随机推荐

  1. linux安装redis操作

    redis官网地址:http://www.redis.io/ 最新版本:2.8.3 在Linux下安装Redis非常简单,具体步骤如下(官网有说明): 1.下载源码,解压缩后编译源码. $ wget ...

  2. Ubuntu18.04LTS安装Nvidia显卡

    笔者在为Ubuntu18.04LTS安装Nvidia显卡驱动之前,早就听说了一系列关于由于Nvidia驱动引起的疑难杂症.选择高质量的教程并保持足够的耐心,就能解 决这些问题.很重要的一点,不要怕把电 ...

  3. sql 语句中as的用法和作用

    我们的Sql语句在很多数据库中都是通用的,比如像Mysql数据库 Access数据库. Oracle数据库.  Sqlite数据库 .甚至在我们的Excel中也可以使用Sql语句. 在我的数据库中有u ...

  4. 在windows环境利用celery实现简单的任务队列

    测试使用环境: 1.Python==3.6.1 2.MongoDB==3.6.2 3.celery==4.1.1 4.eventlet==0.23.0 Celery分为3个部分 (1)worker部分 ...

  5. js 时间格式,加减

    Date.prototype.Format = function (fmt) { //author: rixiao var o = { "M+": this.getMonth() ...

  6. mpvue——仿QQ【一】

    前言 原生仿QQ https://github.com/wangyang0210/Imitate-QQ-For-Mini-Program 这个是当时学习小程序时,模仿的一个demo,只不过是纯页面没啥 ...

  7. [洛谷P1650] 田忌赛马

    贪心难题:总结贪心问题的一般思路 传送门:$>here<$ 题意 田忌和齐王各有n匹马,赛马时一一对应.赢+200,输-200,平+0. 问最多多少钱? 数据范围:$n \leq 2000 ...

  8. 【LOJ6515】贪玩蓝月

    题目大意 有一个双端队列,每个元素是一个物品,每个物品有体积和价值两个属性. 有 \(n\) 个操作,分为 \(5\) 种:前后端插入删除,还有询问:选出一些物品,满足这些物品的体积之和模 \(p\) ...

  9. 哈尔滨工程大学第十四届程序设计竞赛(同步赛)F 小帆帆走迷宫(dp)

    题目描述 小帆帆被困在一个 NxN 的方格矩阵迷宫,每个格子中都有一个整数 A[i][j].小帆帆从迷宫起点(左上角)格子 A[1][1]开始走,每一步可以向右或向下移动,目标是移动到迷宫的出口右下角 ...

  10. 关于ajax 进行post提交 json数据到controller

    首选需要参考的两个博客: www.cnblogs.com/Benjamin/archive/2013/09/11/3314576.html http://www.cnblogs.com/quanyon ...