P3565

一个很妙的 dp 设计是设 $f_{i,j}$ 为在 $i$ 的子树内的与 $i$ 距离为 $j$ 的点的个数。设 $f_{i,j}$ 表示在 $i$ 的子树内的满足 $d(a,lca(a,b))=d(b,lca(a,b))=d(i,lca(a,b))+j$ 的无序点对 $(a,b)$ 的个数,其中 $a,b$ 不相等。

这个状态不是正常人能够想到的。尤其是后面那个 $g$。我们考虑转移,容易发现:

$$

f_{i,j}=\sum\limits_v f_{v,j-1}\
g_{i,j}=\sum\limits_v g_{v,j+1}+f’{i,j}*f{v,j-1}

$$

上面的式子中,$v$ 为 $i$ 的一个子节点。第二个式子中的 $f’$ 表示还没有与 $f_v$ 合并的 $f_i$。

上面那个式子显然是正确的。不难发现答案其实就是:

$$

ans=\sum\limits_{u} g_{u,0}\
ans=\sum\limits_{u}\sum\limits_{v\in son_u}\sum\limits_{i} f_{u,i}g_{v,i+1}+f_{v,i-1}g_{u,i}

$$

需要注意在用指针实现的时候,一定要开一个数组当做内存池。先给每个指针一个内存空间。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 500010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}

struct edge{
int to,next;
inline void Init(int to_,int ne_){
to=to_;next=ne_;
}
}li[N<<1];
int head[N],tail;

inline void Add(int from,int to){
li[++tail].Init(to,head[from]);
head[from]=tail;
}

int n;
int *f[N],*g[N];
// int f[N][N],g[N][N];
int fa[N],Son[N],Dep[N],Deep[N],Ans,p[N<<2];
int *o=p;

inline void dfs(int k,int fat){
fa[k]=fat;Dep[k]=Dep[fat]+1;Deep[k]=Dep[k];
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fat) continue;
dfs(to,k);Deep[k]=max(Deep[to],Deep[k]);
if(Deep[Son[k]]<Deep[to]) Son[k]=to;
}
}

inline void Dp(int k){
// printf("k=%d\n",k);
if(Son[k]){
f[Son[k]]=f[k]+1;g[Son[k]]=g[k]-1;Dp(Son[k]);
}
Ans+=g[k][0];
f[k][0]=1;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fa[k]||to==Son[k]) continue;
// if(to==fa[k]) continue;
int dep=Deep[to]-Dep[to];
f[to]=o;o+=((dep+1)<<1);g[to]=o;o+=((dep+1)<<1);
Dp(to);
for(int i=0;i<=dep;i++){
Ans+=f[to][i]*g[k][i+1];
if(i) Ans+=f[k][i-1]*g[to][i];
}
for(int i=0;i<=dep;i++){
g[k][i+1]+=f[k][i+1]*f[to][i];
if(i) g[k][i-1]+=g[to][i];
f[k][i+1]+=f[to][i];
}
}
}

signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);
for(int i=1;i<=n-1;i++){
int from,to;read(from);read(to);Add(from,to);Add(to,from);
}
dfs(1,1);
f[1]=o;o+=(Deep[1]<<1);g[1]=o;o+=(Deep[1]<<1);
Dp(1);
printf("%lld\n",Ans);
return 0;
}

P3899

这个题要注意的一点是,在转移的时候因为带有常数,我们用一个 $tag_k$ 数组表示 $f_k$ 加上 $tag_k$ 之后才是我们原先的数组。所以我们需要对一些 $f_k$ 上的值进行处理。

还需要注意的一点事这个题的转移范围并不是 $f_{to}$ 的范围,因为 $k$ 只是一个上界,实际上 $f_k$ 后面的值还需要赋值。

说的有一点抽象,不妨试一下这个样例:

1
2
3
4
5
6
7
8

1 2
2 3
3 4
4 5
1 6
6 7

这个样例在合并信息到 $1$ 节点的时候,转移的上界不是 $h_2-1$,所以我们需要对整个数组再次进行修正。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 300100
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}

struct edge{
int to,next;
inline void Init(int to_,int ne_){
to=to_;next=ne_;
}
}li[N<<1];
int head[N],tail;

inline void Add(int from,int to){
li[++tail].Init(to,head[from]);
head[from]=tail;
}

struct Ques{
int u,k,id;
inline Ques(){}
inline Ques(int u,int k,int id) : u(u),k(k),id(id) {}
}ques[N];

int n,q,qt,fa[N],Dep[N],h[N],Son[N],tag[N],Size[N],ans[N];
vector<int> v[N];

int p[N<<1];
int *o=p,*f[N];

inline void dfs(int k,int fat){
fa[k]=fat;Dep[k]=Dep[fat]+1;Size[k]=1;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fat) continue;
dfs(to,k);if(h[Son[k]]<h[to]) Son[k]=to;
Size[k]+=Size[to];
}
h[k]=h[Son[k]]+1;
for(int it:v[k]){
ans[ques[it].id]=(Size[k]-1)*min(Dep[k]-1,ques[it].k);
}
}

inline void Dp(int k){
if(Son[k]){
f[Son[k]]=f[k]+1;Dp(Son[k]);
tag[k]=tag[Son[k]]+Size[Son[k]]-1;
f[k][0]+=-tag[Son[k]]-Size[Son[k]]+1;
}
// printf("k=%d\n",k);
// printf("tag=%d\n",tag[k]);
// for(int i=0;i<h[k];i++){
// printf("%d ",f[k][i]);
// }puts("");
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fa[k]||to==Son[k]) continue;
f[to]=o;o+=(h[to]<<1);
Dp(to);
for(int i=0;i<=h[to]-1;i++){
f[k][i+1]+=f[to][i]+Size[to]-1+tag[to];
}
for(int i=0;i<=h[to];i++) f[k][i]-=f[to][h[to]-1]+Size[to]-1+tag[to];
tag[k]+=f[to][h[to]-1]+Size[to]-1+tag[to];
}
for(int it:v[k]){
ans[ques[it].id]+=f[k][min(ques[it].k,h[k]-1)]+tag[k];
}
// printf("k=%d\n",k);
// printf("tag=%d\n",tag[k]);
// for(int i=0;i<h[k];i++){
// printf("%d ",f[k][i]);
// }puts("");
}

signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(q);
for(int i=1;i<=n-1;i++){
int from,to;read(from);read(to);
Add(from,to);Add(to,from);
}
for(int i=1;i<=q;i++){
int u,k;read(u);read(k);
ques[++qt]=Ques(u,k,i);
v[u].push_back(qt);
}
dfs(1,0);
// printf("%lld\n",ans[1]);
f[1]=o;o+=(h[1]<<1);Dp(1);
for(int i=1;i<=q;i++){
printf("%lld\n",ans[i]);
}
return 0;
}

CF1009F

这个应该是比较经典的长链剖分优化 dp 了。我们只需要在更新数组的时候注意更新答案即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1001000
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}

struct edge{
int to,next;
inline void Init(int to_,int ne_){
to=to_;next=ne_;
}
}li[N<<1];
int head[N],tail;

int n;

inline void Add(int from,int to){
li[++tail].Init(to,head[from]);
head[from]=tail;
}

int fa[N],Dep[N],h[N],Son[N];;
int p[N<<2],*o=p,*f[N];
typedef pair<int,int> P;
P ans[N];

inline void dfs(int k,int fat){
Dep[k]=Dep[fat]+1;fa[k]=fat;
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fat) continue;
dfs(to,k);if(h[Son[k]]<h[to]) Son[k]=to;
}
h[k]=h[Son[k]]+1;
}

inline void Dp(int k){
ans[k]=make_pair(INF,INF);
if(Son[k]){
f[Son[k]]=f[k]+1;Dp(Son[k]);ans[k]=ans[Son[k]];ans[k].second++;
}
f[k][0]=1;if(ans[k]>make_pair(-1,0)) ans[k]=make_pair(-1,0);
// printf("k=%d\n",k);
// printf("ans[k]= %d %d\n",ans[k].first,ans[k].second);
for(int x=head[k];x;x=li[x].next){
int to=li[x].to;
if(to==fa[k]||to==Son[k]) continue;
f[to]=o;o+=(h[to]+1);
Dp(to);
for(int i=0;i<h[to];i++){
f[k][i+1]+=f[to][i];
if(ans[k]>make_pair(-f[k][i+1],i+1)) ans[k]=make_pair(-f[k][i+1],i+1);
}
}
}

int main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);
for(int i=1;i<=n-1;i++){
int from,to;read(from);read(to);
Add(from,to);Add(to,from);
}
dfs(1,0);f[1]=o;o+=(h[1]+1);Dp(1);
for(int i=1;i<=n;i++) printf("%d\n",ans[i].second);
return 0;
}