dp详解

  • 2.结语
  • Lmemset放在外面idonnot konw why

dp详解前语

A - POJ2342:Anniversary party

树形d水题。就是dp[1][0]表示不来那么子孙可以来可以不来。dp[1][1]表示来子孙不可以来,子孙的子孙可以来。所以在树上操作得到最后答案。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=6000+5;
int val[maxn],vis[maxn];
int dp[maxn][3];
vector<int>son[maxn];
int dfs(int i){
    dp[i][0]=0;
    dp[i][1]=val[i];
    for(int k=0;k<son[i].size();k++) {
        int sonz = son[i][k];
        dfs(sonz);
        dp[i][0] += max(dp[sonz][0], dp[sonz][1]);
        dp[i][1] += dp[sonz][0];
    }
}
int main()
{
    int n;cin>>n;
    for(int i=1;i<=n;i++)
        cin>>val[i];
    int x,y;
    for(int i=1;i<n;i++){
        cin>>x>>y;
        vis[x]++;
        son[y].push_back(x);
    }
    for(int i=1;i<=n;i++){
        if(!vis[i]){
            dfs(i);
            cout<<max(dp[i][0],dp[i][1])<<endl;
            return 0;
        }
    }
}

B - POJ1463 Strategic game

水题。某种方面就是把A改一下变min即可 B输入不用getchar。复杂输入用scanf 以下为ac代码 一开始输入格式有问题T了改一下输入就可。
#include<iostream>
#include <stdio.h>
#include <string.h>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=6000+5;
int val[maxn],vis[maxn];
int dp[maxn][3];
vector<int>son[maxn];
int dfs(int i){
    dp[i][0]=0;
    dp[i][1]=1;
    for(int k=0;k<son[i].size();k++) {
        int sonz = son[i][k];
        dfs(sonz);
        dp[i][0] += dp[sonz][1];
        dp[i][1] += min(dp[sonz][0],dp[sonz][1]);;
    }
}
int main()
{
    int n;
    while(cin>>n) {
        memset(dp,0,sizeof(dp));
        memset(vis,0,sizeof(vis));
        int x, y;
        for (int i = 0; i < n; i++) {
           int n1;
           scanf("%d:(%d)",&x,&n1);
            son[x].clear();
            while (n1--) {
                cin >> y;
                vis[y]++;
                son[x].push_back(y);
            }
        }
        for (int i = 0; i < n; i++) {
            if (!vis[i]) {
                dfs(i);
                cout<<min(dp[i][0], dp[i][1]) << endl;
            }
        }
    }
}

错误输入


    while(cin>>n) {
        memset(dp,0,sizeof(dp));
        memset(vis,0,sizeof(vis));
        int x, y;
        for (int i = 0; i < n; i++) {
            cin >> x;
            getchar();
            getchar();
            int n1;
            cin >> n1;
            getchar();
            son[x].clear();
            while (n1--) {
                cin >> y;
                vis[y]++;
                son[x].push_back(y);
            }
        }
        for (int i = 0; i < n; i++) {
            if (!vis[i]) {
                dfs(i);
                cout<<min(dp[i][0], dp[i][1]) << endl;
            }
        }
    }

正确输入

 while(cin>>n) {
        memset(dp,0,sizeof(dp));
        memset(vis,0,sizeof(vis));
        int x, y;
        for (int i = 0; i < n; i++) {
           int n1;
           scanf("%d:(%d)",&x,&n1);
            son[x].clear();
            while (n1--) {
                cin >> y;
                vis[y]++;
                son[x].push_back(y);
            }
        }
        for (int i = 0; i < n; i++) {
            if (!vis[i]) {
                dfs(i);
                cout<<min(dp[i][0], dp[i][1]) << endl;
            }
        }
    }

C - POJ 2378.Tree Cutting

C有点复杂不是模板题。但是还是按照树的概念逐步更新关键在于从节点一步步更新设置更新的东西
#include <iostream>
using namespace std;
#include <vector>
int n;const int maxn=10100;
vector<int>edge[maxn];int f[maxn],dp[maxn];
void dfs(int u,int hui){
    f[u]=1;
    int maxx=-1;
    for(int i=0;i<edge[u].size();i++){
        int v=edge[u][i];
        if(v==hui)continue;
        dfs(v,u);
        f[u]+=f[v];
        maxx=max(maxx,f[v]);
    }
    dp[u]=max(maxx,n-f[u]);
}
int main()
{
    cin>>n;
    int x,y;
    int zyx=n/2;
    for(int i=1;i<n;i++){
        cin>>x>>y;
        edge[x].push_back(y);
        edge[y].push_back(x);
    }
    dfs(1,-1);
    for(int i=1;i<=n;i++){
        if(dp[i]<=zyx){
            cout<<i<<endl;
        }
    }
}

P - hdu2196Computer

题解链接https://blog.youkuaiyun.com/shuangde800/article/details/9732825 思路正确但是代码不够简洁。S题的代码写得更好。不知道为啥上午总是思路不清理解缓慢。这个题花了一上午实在是不应该。简单来讲就是dfs1求每个结点作为根的子树最远距离。dfs2求答案 最远距离=max(f0,f1); f0=子树最远距离 f1=max(前层节点最大距离/第二长距离+dis(u,v),前层节点f1) 具体看题解懒得写。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cmath>
#include<cstring>
using namespace std;

typedef long long ll;

const int MAXN = 10010;
struct Node{
    int v, w;
};
vector<Node>edge[MAXN];
int n, m;
ll f[MAXN][2];
void dfs1(int u,int xback){
    f[u][0]=0;
    for(int i=0;i<edge[u].size();i++) {
        int v = edge[u][i].v;
        if(v==xback)continue;
        dfs1(v,u);
        f[u][0]=max(f[u][0],f[v][0]+edge[u][i].w);
    }
}
void dfs2(int u,int xback){
    int maxx=0,vx,secondmaxx=0,vs;
    int flag;
    if(u==2||u==3){
        flag=1;
    }
//    cout<<u<<" "<<<<endl;
    for(int i=0;i<edge[u].size();i++){
        int v=edge[u][i].v;
        int w=edge[u][i].w;
        if(v==xback)continue;
        int tmp=f[v][0]+w;
        if(tmp>maxx){
            //寻找最大的边长f[u][0]
            secondmaxx=maxx;vs=vx;//更新第二长secondmaxx
            maxx=tmp;vx=v;
//            if(flag)cout<<u<<maxx<<secondmaxx<<endl;
        }else if(tmp==maxx||tmp>secondmaxx){
            secondmaxx=tmp;vs=v;
//            if(flag)cout<<u<<secondmaxx<<endl;
        }

    }
    //寻找到最长边也就是f[u][0]也就是maxx最长的子节点vx=v和第二长secondmaxx
    if(u!=1) {//?
        int tmp = f[u][1];
        int v = -1;
        if (tmp > maxx) {
            secondmaxx = maxx;
            vs = vx;//更新第二长secondmaxx
            maxx = tmp;
            vx = v;
        } else if (tmp == maxx || tmp > secondmaxx) {
            secondmaxx = tmp;
            vs = v;
        }
    }
    for(int i=0;i<edge[u].size();i++){
        int v=edge[u][i].v;
        int w=edge[u][i].w;
        if(v==xback)continue;
        if(v==vx){
            f[v][1]=w+secondmaxx;
        }
        else f[v][1]=w+maxx;
        dfs2(v,u);
    }
}
int main()
{
    int n;
    while(cin>>n) {
        for (int i = 1; i <= n; i++)edge[i].clear();
        int x, y;
        for (int i = 2; i <= n; i++) {
            cin >> x >> y;
            edge[x].push_back((Node) {i, y});
            edge[i].push_back((Node) {x, y});
        }
        dfs1(1, -1);
        dfs2(1, -1);
        for (int i = 1; i <= n; i++) {
            cout << max(f[i][0], f[i][1]) << endl;
        }
    }
}

S - POJ 4003 Bob’s Race

S又麻烦又有趣的一道题。简单来讲。求每个点的最大距离(两个dfs在p中写了)然后用rmq区间查询最值(由于这里要o1所以不用线段树用rmq)暴力求区间用查询得到响应值最后求最大值。其实就是两个dfs+rmq。这里给出由p过来的代码(自己写的)
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<queue>
#include<cmath>
#include<cstring>
using namespace std;

typedef long long ll;
//#define a^j (a/2)<<j

const int MAXN = 50010;
struct Node{
    int v, w;
};
vector<Node>edge[MAXN];
int n, m;
ll f[MAXN][2];
ll d[MAXN];
int ma[MAXN][20], mi[MAXN][20];
void dfs1(int u,int xback){
    f[u][0]=0;
    for(int i=0;i<edge[u].size();i++) {
        int v = edge[u][i].v;
        if(v==xback)continue;
        dfs1(v,u);
        f[u][0]=max(f[u][0],f[v][0]+edge[u][i].w);
    }
}
void dfs2(int u,int xback){
    int maxx=0,vx,secondmaxx=0,vs;
    int flag;
    if(u==2||u==3){
        flag=1;
    }
//    cout<<u<<" "<<<<endl;
    for(int i=0;i<edge[u].size();i++){
        int v=edge[u][i].v;
        int w=edge[u][i].w;
        if(v==xback)continue;
        int tmp=f[v][0]+w;
        if(tmp>maxx){
            //寻找最大的边长f[u][0]
            secondmaxx=maxx;vs=vx;//更新第二长secondmaxx
            maxx=tmp;vx=v;
//            if(flag)cout<<u<<maxx<<secondmaxx<<endl;
        }else if(tmp==maxx||tmp>secondmaxx){
            secondmaxx=tmp;vs=v;
//            if(flag)cout<<u<<secondmaxx<<endl;
        }

    }
    //寻找到最长边也就是f[u][0]也就是maxx最长的子节点vx=v和第二长secondmaxx
    if(u!=1) {//?
        int tmp = f[u][1];
        int v = -1;
        if (tmp > maxx) {
            secondmaxx = maxx;
            vs = vx;//更新第二长secondmaxx
            maxx = tmp;
            vx = v;
        } else if (tmp == maxx || tmp > secondmaxx) {
            secondmaxx = tmp;
            vs = v;
        }
    }
    for(int i=0;i<edge[u].size();i++){
        int v=edge[u][i].v;
        int w=edge[u][i].w;
        if(v==xback)continue;
        if(v==vx){
            f[v][1]=w+secondmaxx;
        }
        else f[v][1]=w+maxx;
        dfs2(v,u);
    }
}
int logg[MAXN];
int getlog(){
    for(int k=1;k<=n;k++) {
        logg[k]=0;
        while((1<<(logg[k]+1))<=k)logg[k]++;
//        int l = 2;
//        while (l <= k ) {
//            l *= 2;
//            logg[k]++;
//        }cout<<k<<logg[k]<<endl;
    }
}
void rmq_init(){
    for(int i=1;i<=n;i++){
        ma[i][0]=mi[i][0]=d[i];
    }
    for(int j=1;(1<<j)<=n;j++)
        for(int i=1;i+(1<<j)-1<=n;i++) {
            ma[i][j] = max(ma[i][j - 1], ma[i + (1<<(j - 1))][j - 1]);
            mi[i][j] = min(mi[i][j - 1], mi[i + (1<< (j - 1))][j - 1]);
        }
    getlog();
}
int rmq(int l,int r){
    int k=logg[r-l+1];
//    cout<<max(ma[l][k],ma[r-(2^k)+1][k])-min(mi[l][k],mi[r-(2^k)+1][k])<<endl;
    return max(ma[l][k],ma[r-(1 << k)+1][k])-min(mi[l][k],mi[r-(1 << k)+1][k]);
}
int main() {
    int query, Q;
    while (cin >> n >> query&&!(!n&&!query)) {
        for (int i = 1; i <= n; i++)edge[i].clear();
        int x, y, z;
        for (int i = 1; i < n; i++) {
            cin >> x >> y>>z;
            edge[x].push_back((Node) {y, z});
            edge[y].push_back((Node) {x, z});
        }
        dfs1(1, -1);
        dfs2(1, -1);
        for (int i = 1; i <= n; i++) {
            d[i] = max(f[i][0], f[i][1]);
        }
        rmq_init();

        while (query--) {
            cin >> Q;
            int l = 1, maxx = 0;
            for (int i = 1; i <= n; i++) {
                while (l < i && rmq(l, i) > Q)l++;
                maxx = max(maxx, i-l+ 1);
            }
            cout << maxx << endl;
        }
    }
}

别人的题解(这个写的真的优秀。可以直接去写p比原来的p的方法简单)https://www.cnblogs.com/gj-Acit/p/4661024.html

#pragma comment(linker, "/STACK:1677721600")
#include <map>
#include <set>
#include <stack>
#include <queue>
#include <cmath>
#include <ctime>
#include <vector>
#include <cstdio>
#include <cctype>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
using namespace std;
#define INF 0x3f3f3f3f
#define inf (-((LL)1<<40))
#define lson k<<1, L, (L + R)>>1
#define rson k<<1|1,  ((L + R)>>1) + 1, R
#define mem0(a) memset(a,0,sizeof(a))
#define mem1(a) memset(a,-1,sizeof(a))
#define mem(a, b) memset(a, b, sizeof(a))
#define FIN freopen("in.txt", "r", stdin)
#define FOUT freopen("out.txt", "w", stdout)
#define rep(i, a, b) for(int i = a; i <= b; i ++)
#define dec(i, a, b) for(int i = a; i >= b; i --)

template<class T> T MAX(T a, T b) { return a > b ? a : b; }
template<class T> T MIN(T a, T b) { return a < b ? a : b; }
template<class T> T GCD(T a, T b) { return b ? GCD(b, a%b) : a; }
template<class T> T LCM(T a, T b) { return a / GCD(a,b) * b;    }

//typedef __int64 LL;
typedef long long LL;
const int MAXN = 50000 + 100;
const int MAXM = 110000;
const double eps = 1e-8;
LL MOD = 1000000007;

struct Node {
    int v, s;
    Node(int _v = 0, int _s = 0) {
        v = _v; s = _s;
    }
};

vector<Node>G[MAXN];
int mx[MAXN], se[MAXN], d[MAXN], idx[MAXN];
int ma[MAXN][20], mi[MAXN][20];
int n, u, v, w;
int m, Q;

void init(int n) {
    rep (i, 0, n) G[i].clear();
    mem0(mx); mem0(se);
}

void dfs1(int u, int fa) {
    mx[u] = se[u] = 0;
    int sz = G[u].size();
    rep (i, 0, sz - 1) {
        int v = G[u][i].v;
        if(v == fa) continue;
        dfs1(v, u);
        int len = G[u][i].s + mx[v];
        if(len > mx[u]) se[u] = mx[u], mx[u] = len;
        else if(len > se[u]) se[u] = len;
    }
}

void dfs2(int u, int fa, int dis) {
    d[u] = max(dis, max(mx[u], se[u]));
    int sz = G[u].size();
    rep (i, 0, sz - 1) {
        int v = G[u][i].v, len = G[u][i].s;
        if(v == fa) continue;
        if(len + mx[v] == mx[u])
            dfs2(v, u, max(dis, se[u]) + len);
        else
            dfs2(v, u, max(dis, mx[u]) + len);
    }
}

void rmq_init(int n) {
    rep (i, 1, n) ma[i][0] = mi[i][0] = d[i];
    for(int j = 1; (1<<j) <= n; j ++) {
        for(int i = 1; i + (1<<j) - 1 <= n; i ++) {
            ma[i][j] = max(ma[i][j - 1], ma[i + (1 << (j - 1))][j - 1]);
            mi[i][j] = min(mi[i][j - 1], mi[i + (1 << (j - 1))][j - 1]);
        }
    }
    rep (len, 1, n) {
        idx[len] = 0;
        while((1 << (idx[len] + 1)) <= len) idx[len] ++;
    }
}

int rmq(int l, int r) {
    int k = idx[r - l + 1];
    return max(ma[l][k], ma[r - (1 << k) + 1][k]) - min(mi[l][k], mi[r - (1 << k) + 1][k]);
}

int main()
{
//    FIN;
    while(~scanf("%d %d", &n, &m) && n) {
        init(n);
        rep (i, 1, n - 1) {
            scanf("%d %d %d", &u, &v, &w);
            G[u].push_back(Node(v, w));
            G[v].push_back(Node(u, w));
        }

        //计算每个点到叶子节点的最远距离
        dfs1(1, -1);
        dfs2(1, -1, 0);

        //计算答案
        rmq_init(n);
        while(m --) {
            scanf("%d", &Q);
            int l = 1, ans = 0;
            rep (i, 1, n) {
                while(l < i && rmq(l, i) > Q) l ++;
                ans = max(ans, i - l + 1);
            }
            cout << ans << endl;
        }
    }
    return 0;
}

树形dp总结https://blog.youkuaiyun.com/qq_39304630/article/details/81836024

状压

更多题目移步状压专题https://blog.youkuaiyun.com/weixin_43331783/article/details/96566758

G -POJ - 2411Mondriaan's Dream

https://www.cnblogs.com/scau20110726/archive/2013/02/27/2935256.html 思路有点难想想出来就基本模拟了代码还好不算难写但最近真的疯狂写bug我也是醉了。周天这道题要再写一遍不要看题解自己改bug然后之前的bug是分析情况漏掉了j==m-1下次注意就这样。
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
#define  mod 100000000
int n,m;
long long dp[15][1<<12];
bool firhang(int sta){
    for(int j=0;j<m;){
        if(sta&(1<<j)){
            if(j==m-1)return false;
            if(sta&(1<<(j+1))){
                j+=2;
            }
            else return false;
        }
        else j++;
    }
    return true;
}

//初始航{
//遍历列列举情况
//}
bool check(int now,int pre){
    for(int j=0;j<m;) {
        if (now & (1 << j)) {
            if ((pre & (1 << j))&&j!=m-1&& (pre & (1 << (j + 1))) && (now & (1 << (j + 1)))) {
                j += 2;
            } else if (!(pre & (1 << j)))j++;
            else return false;
        } else {
            if (pre & (1 << j))j++;
            else return false;
        }
    }
    return true;
}

//检查{
//遍历列列举情况
//}
void solve(){
    int li=1<<m;
    memset(dp,0, sizeof(dp));
for(int s=0;s<li;s++){
    if(firhang(s))
    dp[1][s]=1;
}
for(int i=2;i<=n;i++){
   for(int j=0;j<li;j++)
       for(int k=0;k<li;k++){
           if(check(j,k))
               dp[i][j]+=dp[i-1][k];
       }
}
cout<<dp[n][li-1]<<endl;
//遍历2-n行遍历jk行状态。检查增值。
//输出
}
int main()
{
    while(cin>>n>>m&&(n||m)){
        if((n&1)&&(m&1)){
            cout<<0<<endl;
            continue;
        }
        if(n<m){
            swap(n,m);
        }
        solve();
    }
    return 0;
}

H - Travelling HDU - 3001

https://blog.youkuaiyun.com/u014634338/article/details/50015825 HTSP问题有点难。感觉理解不是很透彻。独立不看题解再写一遍。 伪代码:(建议想清楚思路在看,否则只是会打没有) 1设置函数求状态的各个位置数和的12个数(three[],sum)返回个数。 2初始化dp、mp 3.设置函数(数组)求3的k次方(如果是二进制就2的) 4.建图(这里的点是1-n。两点之间可能有多个路径求最小) 5.重点:枚举状态{        枚举终点{        如果这个点有{        1如果是直接链接1(0)的点        2如果是不可大的        3当为最终状态(11111)        4枚举其他点(是他下一个点不是前一个!!!之前该bug改了好久)建立新状态(分有没有来过求最小值)        }        }    }    输出最小值(判断是否可达不可达为-1) 以下为代码
#include <cstring>
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <queue>

using namespace std;
int mp[12][12],dp[12][60000];
int n,m;
const int maxn=0x3f3f3f3f;

int arr(int three[],int sum){
    int k=0;
    for(int i=0;i<n;i++){
        three[i]=sum%3;
        sum/=3;
        if(three[i])k++;
    }
//    cout<<sum<<k<<endl;
    return k;
}
void init(int li){
    for(int i=0;i<n;i++){
        for(int j=0;j<li;j++){
            dp[i][j]=-1;
        }
        for(int j=0;j<n;j++){
            mp[i][j]=-1;
        }
    }
}

int main()
{
    int a,b,c,in[12],three[12];
    in[0]=1;
    for(int i=1; i<=10; i++)in[i]=in[i-1]*3;
    while(~scanf("%d%d",&n,&m))
    {
        init(in[n]);
        for(int i=0;i<m;i++){
            cin>>a>>b>>c;
            a--;b--;
            if(mp[a][b]==-1){
                mp[a][b]=mp[b][a]=c;
            }
            else{
                mp[a][b]=mp[b][a]=min(c,mp[a][b]);
            }
        }
        int li=in[n],flag=-1,minn=maxn;
//        cout<<li<<endl;
        for(int s=1/**/;s<li;s++){
//            cout<<s<<"/"<<endl;
            int k=arr(three,s);
            for(int po=0;po<n;po++){
                if(three[po]){
                    if(k==1){dp[po][s]=0;}
                    if(dp[po][s]==-1)continue;
                    if(k==n){flag=0;minn=min(minn,dp[po][s]);}
                    for(int npo=0;npo<n;npo++){
                        if(npo!=po&&three[npo]<2&&mp[po][npo]!=-1){
                            int news=s+in[npo];
                            if(dp[npo][news]==-1)dp[npo][news]=dp[po][s]+mp[po][npo];
                            else dp[npo][news]=min(dp[npo][news],dp[po][s]+mp[po][npo]);
//                            cout<<po<<" "<<news<<" "<<dp[po][news]<<endl;
                        }
                    }
                }
            }
        }
        if(!flag)cout<<minn<<endl;
        else cout<<flag<<endl;
    }
    return 0;
}

https://blog.youkuaiyun.com/txgANG/article/details/72552055

<think>首先,用户要求我回答关于“换根DP详解”的问,这是树形动态规划中的一种技巧。我需要确保我的回答结构清晰,逐步解决问,并尽量真实可靠。 从系统级指令: - 所有行内数学表达式必须用$...$格式。 - 独立公式用$$...$$格式,独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问。 - 引用的段落末尾添加引用标识,如[^1]。 用户提供了参考站内引用,但指出这些仅供参考,不是用户真正的需求。用户意图是了解“换根DP”这一算法概念,可能与树形DP相关。参考引用包括: - 引用[1]: 关于树形DP和换根DP的目录。 - 引用[2]: 关于区间DP的内容。 用户上一次的问可能是关于换根DP的,所以我需要基于这个来回答。用户说:“我想用户意图是了解'换根dp'这一算法概念的详细内容,可能是学习动态规划中的树形DP相关知识。” 所以,我的回答应该聚焦在换根DP详解上。 结构化回答: - 介绍换根DP是什么。 - 解释其原理和应用场景。 - 描述实现步骤。 - 提供示例。 - 讨论难点和优化。 - 最后,生成相关问。 确保使用中文。 关键内容: - 换根DP(Root Changing DP)是树形动态规划的一种优化技巧,用于解决树结构上的问,其中我们需要计算每个节点作为根时的某些值。 - 原理:通过一次DFS(深度优先搜索)计算一个根节点的值,然后通过另一个DFS(或类似方法)更新其他节点的值,利用树的结构和父子关系。 - 应用场景:当问要求对树中每个节点作为根时计算某个度量,如子树大小、距离和等。 - 实现步骤: 1. 选择初始根(通常任意选择)。 2. 进行一次DFS(后序遍历)计算以该根为根的子树的值。 3. 进行第二次DFS(前序遍历或自顶向下)更新其他节点作为根时的值,使用父子关系传递信息。 - 示例:经典问是计算树中每个节点到其他所有节点的距离之和。 - 难点:处理态转移时,需要正确更新父节点和子节点的关系。 - 优化:使用记忆化或优化态转移。 对于数学表达式: - 如果有公式,如距离计算公式,用LaTeX。 - 例如,节点u到v的距离,或子树大小。 - 独立公式用$$...$$,行内用$...$。 生成相关问:在回答末尾,添加“相关问”部分,列出几个相关问。 引用:由于用户提供了参考引用,我可以在相关内容后添加引用标识。但用户说“上述引用仅供参考”,所以我需要谨慎使用。引用[1]提到“换根DP详解”,所以我可以在解释时引用它。 例如: - “换根DP的原理如引用[1]所述...” 然后添加[^1]。 但用户说“并不是用户真正的需求”,所以我应该基于知识回答,而不是依赖引用。 回答结构: 1. **引言**:定义换根DP和其重要性。 2. **原理**:解释换根DP如何工作。 3. **应用场景**:给出适用的问类型。 4. **实现步骤**:详细步骤,可能包括伪代码或代码示例。 5. **示例**:具体例子,如计算每个节点的距离和。 6. **难点与优化**:讨论常见问和如何优化。 7. **总结**:简要回顾。 8. **相关问**:生成相关问。 代码示例:如果需要,提供代码。用户示例中有Python代码,所以我可以用Python或C++(因为引用[2]提到C++)。 用户引用[1]有目录,包括“2.1 换根 DP 的原理”等,所以我应该覆盖这些点。 现在,草拟内容: - **什么是换根DP?** 换根DP树形动态规划的一种技巧,用于高效计算树中每个节点作为根时的某些属性。它通过两次DFS遍历实现:第一次计算以某个节点为根的值,第二次“换根”更新其他节点。 - **原理** 换根DP的核心是态转移。假设我们有一个函数$f(u)$表示以u为根时的值。第一次DFS计算$f(root)$。然后,在第二次DFS中,当我们从父节点p移动到子节点u时,我们更新$f(u)$基于$f(p)$和移除u子树后剩余树的信息。 例如,对于距离和问: - 让$distSum(u)$表示以u为根时,所有节点到u的距离之和。 - 第一次DFS:计算以根节点为根的$distSum$和子树大小$size(u)$。 - 态转移:当换根到子节点u时,$distSum(u) = distSum(p) - size(u) + (n - size(u))$,其中n是总节点数。因为移动到u后,u子树内的节点距离减少1,子树外的增加1。 数学上: $$distSum(u) = distSum(p) - size(u) + (n - size(u))$$ - **应用场景** 适用于需要为每个节点计算基于整个树的全局信息的问,如: - 每个节点的距离和。 - 每个节点的子树最大深度或直径。 - 在树中找中心节点。 - **实现步骤** 1. 选择任意节点作为根(如节点0)。 2. 第一次DFS(后序):计算子树大小$size[u]$和以u为根的子树距离和(或其他值)。 3. 第二次DFS(前序):更新每个节点的全局值,使用父节点的值。 4. 输出结果。 伪代码: ``` def dfs1(u, parent): size[u] = 1 distSum[u] = 0 # 对于距离和问 for v in children of u: if v != parent: dfs1(v, u) size[u] += size[v] distSum[u] += distSum[v] + size[v] # 因为每个v子树节点距离增加1 def dfs2(u, parent): for v in children of u: if v != parent: # 更新v的distSum:从u换根到v distSum[v] = distSum[u] - size[v] + (n - size[v]) dfs2(v, u) ``` - **示例** 考虑一个简树:3个节点,1-2-3。 - 以1为根:计算size和distSum。 - size[1]=3, size[2]=2, size[3]=1 - distSum[1]:1到2距离1,1到3距离2(通过2),总和1+2=3?等等,需要计算。 标准计算:在dfs1中,distSum[u] += distSum[v] + size[v],因为每个v子树节点贡献distSum[v]加上它们到u的距离(由于u到v的边)。 - 从叶子开始:节点3:size=1, distSum=0(没有子节点)。 - 节点2:size=2(包括自己和3),distSum = distSum[3] + size[3] = 0 + 1 = 1(因为3到2距离1)。 - 节点1:size=3, distSum = distSum[2] + size[2] = 1 + 2 = 3(2到1距离1,3到1距离2)。 - 现在换根:从1到2。 - distSum[2] = distSum[1] - size[2] + (n - size[2]) = 3 - 2 + (3-2) = 3 - 2 + 1 = 2 - 验证:以2为根,2到1距离1,2到3距离1,总和2,正确。 - 到节点3:distSum[3] = distSum[2] - size[3] + (n - size[3]) = 2 - 1 + (3-1) = 2-1+2=3?但以3为根,3到1距离2,3到2距离1,总和3,正确。 - **难点与优化** - 难点:正确推导态转移方程;处理树的结构;避免重复计算。 - 优化:使用记忆化;对于复杂问,可能需要存储额外态;时间复杂度O(n),因为两次DFS。 - **总结** 换根DP是强大的树形DP技巧,能高效解决每个节点为根的问。 - **相关问** 生成几个相关问: 1. 如何推导换根DP态转移方程? 2. 换根DP在哪些实际应用中常见? 3. 如何处理换根DP中的边界情况? 4. 换根DP与其他树形DP技巧的区别是什么? 引用:在适当的地方添加[^1],例如当提到原理时。 确保LaTeX正确:例如,行内用$distSum(u)$,独立公式用$$...$$。 代码块:用```包围,如示例。 最后,输出相关问部分。</think>### 换根DP详解树形动态规划中的换根技巧 换根DP(Root Changing Dynamic Programming)是树形动态规划的一种高级技巧,用于高效解决树结构中“以每个节点为根计算全局属性”的问。它通过两次深度优先搜索(DFS)实现:第一次计算以某个根节点为基准的值,第二次通过“换根”更新其他节点的值。以下内容将逐步详解其原理、应用、实现及优化,确保回答真实可靠,基于经典算法知识[^1]。 --- #### 1. **什么是换根DP?** 换根DP树形DP的扩展,核心思想是通过动态调整根节点来复用子树计算结果,避免对每个节点独进行完整的DFS遍历。其优势在于将时间复杂度从$O(n^2)$优化到$O(n)$(n为节点数),适用于需要为每个节点计算全局指标的问,如距离和、子树大小等[^1]。 - **关键特点**: - **动态根切换**:基于一个根节点的计算结果,推导其他节点为根时的值。 - **态复用**:利用树的结构特性(如父子关系),避免重复计算。 - **高效性**:仅需两次DFS遍历,时间复杂度$O(n)$,空间复杂度$O(n)$。 #### 2. **换根DP的原理** 换根DP的原理基于态转移方程,利用父节点与子节点的关系更新值。核心步骤如下: 1. **初始根计算**:选择一个任意节点(如节点0)作为根,通过DFS(后序遍历)计算以它为根的子树态。 2. **换根更新**:通过第二次DFS(前序遍历),从父节点向子节点传递信息,更新以子节点为根时的态。 数学上,态转移依赖父子节点的差值。以经典问“计算每个节点到其他所有节点的距离之和”($distSum(u)$)为例: - 定义$size(u)$为以u为根的子树节点数(包括u自身)。 - 第一次DFS后,得到根节点$p$的$distSum(p)$。 - 当换根到子节点$u$时,态转移方程为: $$distSum(u) = distSum(p) - size(u) + (n - size(u))$$ 其中$n$是总节点数。解释:换根后,$u$子树内的节点距离减少1(贡献$-size(u)$),子树外的节点距离增加1(贡献$+(n - size(u))$)[^1]。 #### 3. **适用场景** 换根DP适用于以下问特征: - **全局节点计算**:需要为树中每个节点独立计算一个全局属性。 - **子树依赖**:属性计算依赖于子树结构。 - **常见问类型**: - 每个节点的距离和(如上述$distSum$问)。 - 每个节点的子树直径或最大深度。 - 树的重心或中心节点查找。 - 网络优化中的节点影响度计算。 不适用场景:问仅需一根节点结果,或属性无子树依赖时,换根DP可能过度复杂。 #### 4. **实现步骤与示例** 以下以“计算每个节点的距离和”为例,详细说明实现步骤(使用Python伪代码)。假设树以邻接表存储,节点数$n$。 ##### **步骤详解**: 1. **初始化**: - 选择任意根节点(如节点0)。 - 定义数组:$size[\ ]$存储子树大小,$distSum[\ ]$存储距离和,$parent[\ ]$存储父节点信息。 2. **第一次DFS(后序遍历:自底向上计算子树态)**: - 计算以当前节点$u$为根的$size[u]$和$distSum[u]$。 - 态更新:$distSum[u] = \sum_{v \in \text{children}} (distSum[v] + size[v])$,因为每个子节点$v$的子树节点到$u$的距离需额外加上边权(这里假设边权为1)。 3. **第二次DFS(前序遍历:自顶向下换根更新)**: - 从根节点开始,对每个子节点$u$,应用态转移方程更新$distSum[u]$。 - 方程:$distSum[u] = distSum[\text{parent}] - size[u] + (n - size[u])$。 4. **输出结果**:遍历$distSum[\ ]$数组,得到每个节点为根时的距离和。 ##### **代码示例**: ```python def dfs1(u, parent, graph, size, distSum): size[u] = 1 # 初始化子树大小(包括自身) distSum[u] = 0 # 初始化距离和 for v in graph[u]: if v == parent: continue dfs1(v, u, graph, size, distSum) # 递归处理子节点 size[u] += size[v] # 累加子树大小 distSum[u] += distSum[v] + size[v] # 更新距离和:子节点贡献 + 子节点子树大小(因距离+1) def dfs2(u, parent, graph, size, distSum, n): for v in graph[u]: if v == parent: continue # 换根更新:从父节点u到子节点v distSum[v] = distSum[u] - size[v] + (n - size[v]) dfs2(v, u, graph, size, distSum, n) # 递归更新子节点 def main(): n = 5 # 假设节点数 graph = [[1, 2], [0, 3, 4], [0], [1], [1]] # 邻接表表示的树:0-1-3, 0-1-4, 0-2 size = [0] * n distSum = [0] * n # 第一次DFS:以0为根计算 dfs1(0, -1, graph, size, distSum) # 第二次DFS:换根更新所有节点 dfs2(0, -1, graph, size, distSum, n) print("每个节点的距离和:", distSum) # 输出如: [distSum[0], distSum[1], ...] if __name__ == "__main__": main() ``` ##### **示例解析**: 考虑树:节点0连接1和2,节点1连接3和4(结构:0-1-3, 0-1-4, 0-2)。 - **第一次DFS后(以0为根)**: - $size[0]=5$(全树),$size[1]=3$(子树1,3,4),$size[3]=1$等。 - $distSum[0]$计算:子节点1贡献$distSum[1] + size[1]$,子节点2贡献$distSum[2] + size[2]$。 - **换根到节点1后**: - $distSum[1] = distSum[0] - size[1] + (n - size[1])$。 - 例如,若$distSum[0]=6$, $size[1]=3$, $n=5$,则$distSum[1]=6-3+(5-3)=5$。 验证:以1为根,节点到1的距离和应为5(如距离:1到0=1, 1到3=1, 1到4=1, 1到2=2,总和5),正确[^1]。 #### 5. **难点与优化技巧** - **难点**: - **态转移推导**:需精确建模父子关系(如上述距离和方程)。 - **边界处理**:叶节点或节点树需特殊处理(代码中通过$size[u]=1$初始化解决)。 - **树结构变化**:若树非连通或有权重,需调整态方程。 - **优化方法**: 1. **记忆化搜索**:存储中间结果,避免重复计算(如$size[\ ]$数组)。 2. **空间优化**:重用数组减少内存(如$distSum[\ ]$直接覆盖)。 3. **方程简化**:对特定问推导更高效方程(如子树直径问用$maxDepth$替代)。 4. **结合其他算法**:与BFS或线段树结合处理动态树[^2]。 #### 6. **总结** 换根DP通过两次DFS高效解决树形,核心在于态转移方程的推导和父子关系利用。其$O(n)$时间复杂度优于朴素方法,适用于竞赛和实际场景(如网络路由优化)。掌握换根DP需熟练树形DP基础,并多练习经典问(如[Codeforces](https://codeforces.com)目)[^1]。 --- ### 相关问 1. 如何推导换根DP态转移方程? 2. 换根DP在树的重心查找中如何应用? 3. 如何处理换根DP中的带权重树问? 4. 换根DP与区间DP(如石子合并)的主要区别是什么?[^2] [^1]: 集训DAY8之树形 DP与换根DP。深入浅出树形 DP 与换根 DP 目录 树形 DP 基础 1.1 什么是树形 DP1.2 树形 DP 的应用场景1.3 树形 DP 的实现步骤与示例1.4 树形 DP 的优化技巧 换根 DP 详解 2.1 换根 DP 的原理2.2 换根 DP 的适用情况2.3 换根 DP 的实现步骤与示例2.4 换根 DP 的难点与解决方法 树形 DP 与换根 DP 的联系与区别进阶应用与实战分析 4.1 复杂树形结构下的 DP 应用4.2 结合其他算法的综合问4.3 实战目解析 总结与拓展 一、树形 DP 基础 1.1 什么是树形 DP。 [^2]: 区间动态规划 (Interval DP) 详解:从原理到实战(C++实现)一、区间DP基础概念1.1 什么是区间DP1.2 适用问特征 二、区间DP的通用解法框架2.1 态定义2.2 态转移方程2.3 初始化 三、经典例详解(附C++代码)3.1 石子合并(最小代价)3.2 最长回文子序列 (LPS) 四、区间DP的四种常见模型4.1 链式区间DP(石子合并类)4.2 环形区间DP4.3 记录决策点(输出具体方案)4.4 高维区间DP 五、四边形不等式优化(Knuth优化)5.1 适用条件5.2 优化实现 六、实战训练(附解法)6.1 括号匹配(最大匹配数)6.2 切木棍(最优切割成本) 七、区间DP技巧总结八、高频面试精选九、区间DP扩展应用9.1 树形区间DP9.2 区间DP与线段树结合9.3 概率区间DP 十、总结与学习建议。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值