点分治:

点分治的题目基本一样,都是路径计数。

其复杂度的保证是依靠 $O(n)$ 找重心的,每一次至少将问题规模减小为原先的$1/2$。

找重心我喜欢$BFS$防止爆栈。

 int Root(int x){
dfsn[]=;
q.push(x); fa[x]=;
flag[x]=;
while(!q.empty()){
int x=q.front(); q.pop();
dfsn[++dfsn[]]=x;
for(int i=g[x];i;i=E[i].to)
if(!v[p] && !flag[p]){
fa[p]=x;
flag[p]=;
q.push(p);
}
}
for(int i=;i<=dfsn[];i++){
siz[dfsn[i]]=;
h[dfsn[i]]=;
flag[dfsn[i]]=;
}
int root=;
for(int i=dfsn[];i>=;i--){
int x=dfsn[i];
if(fa[x]){
siz[fa[x]]+=siz[x];
h[fa[x]]=max(h[fa[x]],siz[x]);
}
h[x]=max(h[x],dfsn[]-siz[x]);
if(!root || h[x]<h[root]) root=x;
}
return root;
}

故总共有 $O(logn)$ 层。

在每一层我们分别对不同的块(删点而形成)采用 $O(siz[p])$ 的算法。

主定理 $T(n) = T(n/2) + O(n)$

总体上是 $O(nlogn)$

大体框架如下

 void DC(int x){
v[x]=;
for(int i=g[x];i;i=E[i].to)
if(!v[p]){
// 大体上是f[x]*ft[x]就是
// Ans = (之前的子树的路径数)*(当前子树的路径数)
}
// 将标记什么的清空,注意保证复杂度是O(siz)不是O(n)
for(int i=g[x];i;i=E[i].to)
if(!v[p]) DC(Root(p));
}

然后对于点分治路径的统计,通常有dp,数据结构,数论等等的方法。

注意:要记得上面的方法没有统计以点x为起点的路径条数,记得加上。

例题:

BZOJ 3697

题意:

给出一棵树,每一条边为黑或白,统计满足条件的路径数

1.路径上黑色和白色的个数相等

2.路径上存在一个点使得起点到当前点黑色和白色的个数相等,此点不能是起点终点。

乍一看是没有解法的,套用点分治。

问题转化为统计过点x的合法路径条数。

套用dp

$f(x,0)$ 表示和为x,无休息站的

$f(x,1)$ 表示和为x,有休息站的

$$ans = f(0,0) * ft(0,0) + \sum_{i=-d}^d {f(i,0) \cdot ft(-i,1) + f(i,1) \cdot ft(-i,0) + f(i,1) \cdot ft(-i,1)} $$

 

条件2可以转化为在当前点到x的路径上有点的$dis(p) = dis(now)$

所以注意保证初始化的复杂度

所以记录一下当前的最大深度,初始化 $f$ 数组和 $ft$ 数组的时候从0循环到 $max deep$

 #include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue> #define N 400010
#define p E[i].x
#define LL long long
#define debug(x) cout<<#x<<" = "<<x<<endl; /*
树形dp
f(x,0) 表示和为x,无休息站的
f(x,1) 表示和为x,有休息站的
ans = f[0][0] * ft[0][0] +
∑ f[i][0]*ft[-i][1] + f[i][1]*ft[-i][0] + f[i][1]*ft[-i][1]
(-d <= i <= d)
*/ using namespace std; struct edge{
int x,to,v;
}E[N<<]; int n,totE;
int g[N],dfsn[N],fa[N],siz[N],h[N];
bool v[N],flag[N];
LL ans;
LL f[N][],ft[N][];
queue<int> q; void ade(int x,int y,int v){
E[++totE]=(edge){y,g[x],v}; g[x]=totE;
} int Root(int x){
dfsn[]=;
q.push(x); fa[x]=;
flag[x]=;
while(!q.empty()){
int x=q.front(); q.pop();
dfsn[++dfsn[]]=x;
for(int i=g[x];i;i=E[i].to)
if(!v[p] && !flag[p]){
fa[p]=x;
flag[p]=;
q.push(p);
}
}
for(int i=;i<=dfsn[];i++){
siz[dfsn[i]]=;
h[dfsn[i]]=;
flag[dfsn[i]]=;
}
int root=;
for(int i=dfsn[];i>=;i--){
int x=dfsn[i];
if(fa[x]){
siz[fa[x]]+=siz[x];
h[fa[x]]=max(h[fa[x]],siz[x]);
}
h[x]=max(h[x],dfsn[]-siz[x]);
if(!root || h[x]<h[root]) root=x;
}
return root;
} int mxdep;
int cnt[N],d[N],dis[N]; void dfs(int x,int fa){
mxdep=max(mxdep,d[x]);
if(cnt[dis[x]]) ft[dis[x]][]++;
else ft[dis[x]][]++;
cnt[dis[x]]++;
for(int i=g[x];i;i=E[i].to)
if(p!=fa&&!v[p]){
d[p]=d[x]+;
dis[p]=dis[x]+E[i].v;
dfs(p,x);
}
cnt[dis[x]]--;
} void DC(int x){
v[x]=;
f[n][]=;
int mx=;
for(int i=g[x];i;i=E[i].to)
if(!v[p]){
dis[p]=n+E[i].v;
d[p]=mxdep=;
dfs(p,p);
mx=max(mx,mxdep);
ans+=(f[n][]-)*ft[n][];
for(int j=-mxdep;j<=mxdep;j++){
ans+=ft[n-j][]*f[n+j][]+
ft[n-j][]*f[n+j][]+ft[n-j][]*f[n+j][];
}
for(int j=n-mxdep;j<=n+mxdep;j++){
f[j][]+=ft[j][];
f[j][]+=ft[j][];
ft[j][]=ft[j][]=;
}
}
for(int i=n-mx;i<=n+mx;i++)
f[i][]=f[i][]=;
for(int i=g[x];i;i=E[i].to)
if(!v[p]) DC(Root(p));
} int main(){
scanf("%d",&n);
for(int i=,x,y;i<n;i++){
int v;
scanf("%d%d%d",&x,&y,&v);
if(!v) v=-;
ade(x,y,v); ade(y,x,v);
}
DC(Root());
printf("%lld\n",ans);
return ;
}

然后是HDU 4812

题意:给出一棵树,每一个点有一个点权,找到一条点权乘积为K的路径,输出起点和终点,要求字典序最小。

求一下逆元,然后用map记录前面的所有子树能够到达的权值(乘积)

注意这是点权,不同于边权,所以在处理过点x的路径的时候要谨慎,我的做法是将路径拆为 x点 , 之前子树中的一条链, 当前子树中的一条链。

用map复杂度多一个log,然而也能过。

 #include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
#include <queue> #define N 400010
#define LL long long
#define mod 1000003
#define p E[i].x
#define INF 0x3f3f3f3f
#define ipos map<int,int>::iterator using namespace std; struct edge{
int x,to;
}E[N<<]; map<int,int> ft,f;
int n,ansv[],totE,K;
int h[N],g[N],siz[N],a[N],fa[N],dfsn[N],dis[N];
bool v[N],flag[N];
queue<int> q; int add(int a,int b){
if(a+b>=mod) return a+b-mod;
return a+b;
} int mul(int a,int b){
return (int)((LL)a*(LL)b%mod);
} int qpow(int x,int n){
int ans=;
for(;n;n>>=,x=mul(x,x))
if(n&) ans=mul(ans,x);
return ans;
} int inv(int x){
return (int)qpow((LL)x,mod-2LL);
} void ade(int x,int y){
E[++totE]=(edge){y,g[x]}; g[x]=totE;
} int Root(int x){
dfsn[]=;
q.push(x); fa[x]=;
flag[x]=;
while(!q.empty()){
int x=q.front(); q.pop();
dfsn[++dfsn[]]=x;
for(int i=g[x];i;i=E[i].to)
if(!v[p] && !flag[p]){
fa[p]=x;
flag[p]=;
q.push(p);
}
}
for(int i=;i<=dfsn[];i++){
siz[dfsn[i]]=;
h[dfsn[i]]=;
flag[dfsn[i]]=;
}
int root=;
for(int i=dfsn[];i>=;i--){
int x=dfsn[i];
if(fa[x]){
siz[fa[x]]+=siz[x];
h[fa[x]]=max(h[fa[x]],siz[x]);
}
h[x]=max(h[x],dfsn[]-siz[x]);
if(!root || h[x]<h[root]) root=x;
}
return root;
} void bfs(int x){
dfsn[]=;
q.push(x); flag[x]=;
while(!q.empty()){
int x=q.front(); q.pop();
dfsn[++dfsn[]]=x;
if(!ft.count(dis[x]) || ft[dis[x]]>x)
ft[dis[x]]=x;
for(int i=g[x];i;i=E[i].to)
if(!v[p] && !flag[p]){
flag[p]=;
dis[p]=mul(dis[x],a[p]);
q.push(p);
}
}
for(int i=;i<=dfsn[];i++)
flag[dfsn[i]]=;
} void upd(int a,int b){
if(a>b) swap(a,b);
if(ansv[]>a || ansv[]==a&&ansv[]>b){
ansv[]=a;
ansv[]=b;
}
} void DC(int x){
// printf("node : %d\n",x);
v[x]=;
f.clear();
f[]=x;
for(int i=g[x];i;i=E[i].to)
if(!v[p]){
ft.clear();
dis[p]=a[p];
bfs(p);
for(ipos it=ft.begin();it!=ft.end();it++){
int tmp=(*it).first;
if(f.count(mul(mul(K,inv(tmp)),inv(a[x])))){
upd(f[mul(mul(K,inv(tmp)),inv(a[x]))],
(*it).second);
}
}
for(ipos it=ft.begin();it!=ft.end();it++){
if(!f.count((*it).first)) f[(*it).first]=(*it).second;
else f[(*it).first]=min(f[(*it).first],(*it).second);
}
}
for(int i=g[x];i;i=E[i].to)
if(!v[p]) DC(Root(p));
} int main(){
// freopen("test.in","r",stdin);
while(scanf("%d%d",&n,&K)==){
ansv[]=ansv[]=INF;
totE=;
for(int i=;i<=n;i++) g[i]=;
for(int i=;i<=n;i++) scanf("%d",&a[i]),v[i]=;
for(int i=,x,y;i<n;i++){
int v;
scanf("%d%d",&x,&y);
ade(x,y);
ade(y,x);
}
DC(Root());
if(ansv[]==INF) puts("No solution");
else printf("%d %d\n",ansv[],ansv[]);
}
return ;
}

最后是 国家集训队的 crash的文明世界

题意:

定义:

求所有点的S(i)

很显然是点分治,斯特林数是什么,我不会 TAT

记录$f(i)$表示$\sum {dist(p,x)^i }$,$ft$同理

考虑每一次统计过x的路径时将过x的路径的影响加入子树中的点,同时在最后将$S(i)$加上$f(K)$

坑点就是合并的时候要用一下 二项式展开

$$ S(p) += \sum_{i=0}^K {C(K,i) * f(K-i) * b^i}$$

然后注意要统计全路径,所以上面的框架不适用(上面的框架对于当前子树只能统计遍历过的子树,而应该是统计所有除了当前子树的权值和)

然后就可以了,自己歪歪(看不懂题解),一遍AC爽。

 #include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue> #define N 200010
#define mod 10007
#define M 310
#define p E[i].x using namespace std;
/*
f[j] 之前的 dist(x,p)^j
ft[j] 当前的 dist(x,p)^j
S[p] += ∑C(K,i) * a^{K-i} * b^i (0<=i<=K)
O(lognK)
*/ int n,K,totE;
int g[N],f[M],siz[N],h[N],fa[N],dfsn[N],S[N],d[N];
int C[M][M];
bool v[N],flag[N];
queue<int> q; int add(int a,int b){
if(a+b>=mod) return a+b-mod;
return a+b;
} int mul(int a,int b){
return a*b%mod;
} struct edge{
int x,to;
}E[N<<]; void ade(int x,int y){
E[++totE]=(edge){y,g[x]}; g[x]=totE;
} int qpow(int x,int n){
int ans=;
for(;n;n>>=,x=mul(x,x))
if(n&) ans=mul(ans,x);
return ans;
} int Root(int x){
dfsn[]=;
q.push(x); fa[x]=;
flag[x]=;
while(!q.empty()){
int x=q.front(); q.pop();
dfsn[++dfsn[]]=x;
for(int i=g[x];i;i=E[i].to)
if(!v[p] && !flag[p]){
fa[p]=x;
flag[p]=;
q.push(p);
}
}
for(int i=;i<=dfsn[];i++){
siz[dfsn[i]]=;
h[dfsn[i]]=;
flag[dfsn[i]]=;
}
int root=;
for(int i=dfsn[];i>=;i--){
int x=dfsn[i];
if(fa[x]){
siz[fa[x]]+=siz[x];
h[fa[x]]=max(h[fa[x]],siz[x]);
}
h[x]=max(h[x],dfsn[]-siz[x]);
if(!root || h[x]<h[root]) root=x;
}
return root;
} void bfs(int x){
dfsn[]=; d[x]=;
q.push(x); flag[x]=;
while(!q.empty()){
int x=q.front(); q.pop();
dfsn[++dfsn[]]=x;
for(int i=g[x];i;i=E[i].to)
if(!v[p] && !flag[p]){
d[p]=d[x]+;
flag[p]=;
q.push(p);
}
}
for(int i=;i<=dfsn[];i++){
int tmp=;
for(int j=;j<=K;j++){
f[j]=add(f[j],tmp);
tmp=mul(tmp,d[dfsn[i]]);
}
flag[dfsn[i]]=;
}
} int power[M]; void solve(int rt){
dfsn[]=; d[rt]=;
q.push(rt); flag[rt]=;
while(!q.empty()){
int x=q.front(); q.pop();
dfsn[++dfsn[]]=x;
for(int i=g[x];i;i=E[i].to)
if(!v[p] && !flag[p]){
d[p]=d[x]+;
flag[p]=;
q.push(p);
}
}
for(int i=;i<=dfsn[];i++){
int tmp=;
for(int j=;j<=K;j++){
f[j]=(f[j]-tmp+mod)%mod;
tmp=mul(tmp,d[dfsn[i]]);
}
}
// printf("son : %d\n",rt);
// for(int i=0;i<=K;i++){
// printf("%d%c",f[i],i==K?'\n':' ');
// }
for(int t=;t<=dfsn[];t++){
int x=dfsn[t];
flag[x]=;
power[]=;
for(int i=;i<=K;i++) power[i]=mul(power[i-],d[x]);
for(int i=;i<=K;i++){
// printf("addto %d = %d\n",x,mul(C[K][i], mul(f[K-i],power[i])));
S[x] = add(S[x], mul(C[K][i], mul(f[K-i],power[i])));
}
S[x]=add(S[x],power[K]);
}
// S[x]=add(S[x],power[K]);
for(int i=;i<=dfsn[];i++){
int tmp=;
for(int j=;j<=K;j++){
f[j]=add(f[j],tmp);
tmp=mul(tmp,d[dfsn[i]]);
}
}
}
//S[p] += ∑C(K,i) * a^{K-i} * b^i (0<=i<=K)
void DC(int x){
v[x]=;
// printf("node : %d\n",x);
// for(int i=1;i<=dfsn[0];i++)
// printf("%d%c",dfsn[i],i==dfsn[0]?'\n':' ');
for(int i=;i<=K;i++) f[i]=;
for(int i=g[x];i;i=E[i].to)
if(!v[p]) bfs(p);
// printf("before\n");
// for(int i=0;i<=K;i++) printf("%d%c",f[i],i==K?'\n':' ');
for(int i=g[x];i;i=E[i].to)
if(!v[p]) solve(p);
S[x]=add(S[x],f[K]);
// printf("base = %d\n",f[K]);
for(int i=g[x];i;i=E[i].to)
if(!v[p]) DC(Root(p));
} int main(){
freopen("civilization.in","r",stdin);
freopen("civilization.out","w",stdout);
scanf("%d%d",&n,&K);
C[][]=;
for(int i=;i<=K;i++){
C[i][]=;
for(int j=;j<=i;j++)
C[i][j]=add(C[i-][j-],C[i-][j]);
}
int L,now,A,B,Q,tmp;
scanf("%d%d%d%d%d",&L,&now,&A,&B,&Q);
for(int i=,x,y;i<n;i++){
now=(now*A+B)%Q;
tmp=(i<L)? i:L;
x=i-now%tmp;
y=i+;
ade(x,y);
ade(y,x);
}
DC(Root());
for(int i=;i<=n;i++){
printf("%d\n",S[i]);
}
return ;
}

总结完了点分治,NOI必胜。

NOI前总结:点分治的更多相关文章

  1. NOI前各种Idea总结以及各种文本乱堆

    转载请注明原文地址:https://www.cnblogs.com/LadyLex/p/9227267.html 不过这篇的确没什么*用了转转吧 2018-6-24 关于一类延迟标记(来自UR14 思 ...

  2. NOI前训练日记

    向别人学习一波,记点流水帐.17.5.29开坑. 5.29 早晨看了道据说是树状数组优化DP的题(hdu5542),然后脑补了一个复杂度500^3的meet in the middle.然后死T... ...

  3. [日常] NOI前划水日记

    NOI前划水日记 开坑记录一下每天的效率有多低 5.24 早上被春哥安排了一场NEERC(不过怎么是qualification round啊) 省队势力都跑去参加THU/PKU夏令营了...剩下四个D ...

  4. NOI前的考试日志

    4.14 网络流专项测试 先看T1,不会,看T2,仙人掌???wtf??弃疗.看T3,貌似最可做了,然后开始刚,刚了30min无果,打了50分暴力,然后接着去看T1,把序列差分了一下,推了会式子,发现 ...

  5. NOI.AC 32 Sort——分治

    题目:http://noi.ac/problem/32 从全是0和1的情况入手,可以像线段树一样分治下去,回到本层的时候就是左半部的右边是1,右半部的左边是0,把这两部分换一下就行.代价和时间一样是n ...

  6. noi前机房日常

    2015/6/16 上午a了一道省选分组赛day1t2,并在cf100553H双倍经验,好评 bzoj3152(ctsc2013)贪心,用priority_queue要清空 bx2k上午交了几十题,他 ...

  7. NOI前总结

    最近也就是天天考试,总结一下. 7.1 开场T1T2都是不可做的概率期望,只有T3看起来可做,于是怒干4h+,将题解里面的所有结论都推出来了,大模拟写的一点毛病都没有,可还是因为2-SAT掌握不熟结果 ...

  8. 博客索引and题目列表

    目录 笔记整理 计划 要学的东西 缺省源 要做的题 搜索 高斯消元 矩阵 排列组合 2019.7.9 2019.7.10 kmp ac自动机 2019.7.11 2019.7.15 笔记整理 1.同余 ...

  9. NOI冲刺计划

    省选过了,剩下大概是NOI冲刺了吧.中间还有一大堆诸如会考,CTSC,APIO等东西. 最近先不急着天天刷八中了吧,多在不同网站见一些题,然后再着重提高一下代码准确性.重点把DP这个板块多练习一下,八 ...

随机推荐

  1. jQuery.ajax()方法中參数具体解析

    前言 在项目开发中,为了实现异步向服务端发起请求,最常常使用的就是jQuery.ajax方法了.刚開始需求比較简单,调用jQuery.ajax方法时要传的參数也就那几个常见的參数:url/data/d ...

  2. [转]文件IO详解(二)---文件描述符(fd)和inode号的关系

    原文:https://www.cnblogs.com/frank-yxs/p/5925563.html 文件IO详解(二)---文件描述符(fd)和inode号的关系 ---------------- ...

  3. Centos 6.x 安装Python 3.4.3

    [root@squid ~]# sed -i 's#keepcache=0#keepcache=1#g' /etc/yum.conf [root@squid ~]# grep keepcache /e ...

  4. BZOJ 题目1036: [ZJOI2008]树的统计Count(Link Cut Tree,改动点权求两个最大值和最大值)

    1036: [ZJOI2008]树的统计Count Time Limit: 10 Sec  Memory Limit: 162 MB Submit: 8421  Solved: 3439 [Submi ...

  5. 对交换机VLAN及各种端口类型的理解

    每学习一种技术时,我们往往需要去了解why,即这个技术是为解决什么问题而出现的. VLAN全称为Virtual Local Area Network,即虚拟局域网,是逻辑上的一种划分.一般来说,如果交 ...

  6. ArcGIS 10.3 for Server新特性介绍

    ArcGIS10.3的正式版立即在美国Esri全球用户大会推出.中国的正式发行时间预计在Esri中国的用户大会.以下就将用户比較关心的ArcGIS 10.3 for Server的新特性给大家进行简单 ...

  7. java设计模式----其他模式

    1.桥接:使用桥接模式不只改变你的实现,也改变你的抽象 优点: 将实现予以解耦,让它和界面之间不再永久绑定 抽象和实现可以独立扩展,不会影响到对方 对于“具体的抽象类”所做的改变,不会影响到客户 用途 ...

  8. RecyclerView(替代ListView)使用方法介绍

    在build.gradle文件加入以下代码 compile 'com.android.support:cardview-v7:21.0.3' compile 'com.android.support: ...

  9. bash shell中的特殊用法

    1 ${BASH_SOURCE-$0} 表示当前正在执行的脚本的完整路径. 2 cd -P 以物理路径进入 /usr/localhadoop现在链到/home/houzhizhen/usr/local ...

  10. Maven group, artifact or version defined in the pom file do not match the file ...

    在把library上传到bintray空间的时候报以下错误 Could not upload to 'https://api.bintray.com/content/ping/maven/comm-a ...