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 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) { 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 ; 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 () { 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 ; } 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]; } } signed main () { 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 ); 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 ); 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 () { 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 ; }
Author:
HYLTianMeng
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE