一点值得回看的今天做的树(shu)形(wei)dp记录

诡异数字
https://ac.nowcoder.com/acm/problem/20669
思路:dp[pos, pre, cnt]表示当前位置是pos,上一位出现的数是pre, 这个数出现cnt次的数量。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 25, mod = 20020219;

ll t ,l, r, n, numlim[N], num[N], dp[N][10][1010]; 

ll dfs(int pos, bool limit, bool lead, int pre, int cnt){
    if(!pos)    return 1;
    if(!limit && !lead && ~dp[pos][pre][cnt])    return dp[pos][pre][cnt];
    int up = limit ? num[pos] : 9;
    ll ans = 0;
    for(int i = 0; i <= up; ++ i){
        if(lead && i == 0)    ans = (ans + dfs(pos - 1, limit && i == up, true, -1, 0)) % mod;
        else{
            if(i == pre){
                if(cnt == numlim[i])    continue;
                else    ans = (ans + dfs(pos - 1, limit && i == up, false, i, cnt + 1)) % mod;
            }else    ans = (ans + dfs(pos - 1, limit && i == up, false, i, 1)) % mod;
        }
    }    
    if(!limit && !lead)    dp[pos][pre][cnt] = ans;
    return ans;
}

ll solve(ll x){
    memset(dp, -1, sizeof dp);
    int pos = 0;
    while(x){
        num[++ pos] = x % 10;
        x /= 10;
    }
    return dfs(pos, true, true, -1, 0);
}

int main(){
    cin >> t;
    while(t --){
        cin >> l >> r >> n;
        memset(numlim, 0x3f, sizeof numlim);
        for(int i = 1; i <= n; ++ i){
            ll x, cnt;
            cin >> x >> cnt;
            numlim[x] = min(numlim[x], cnt);
        }
        cout << (solve(r) - solve(l - 1) + mod) % mod << endl;
    }
    return 0;
}

7的意志
https://ac.nowcoder.com/acm/problem/20665
再次证明牛客的星是xjb标的qwq 这是个锤子5星题
思路:用remain存这个数%7的值,sum存这个数每一位相加的和%7的余数。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 25;

ll l, r, dp[N][10][10];
int num[N];

ll dfs(int pos, bool limit, int sum, int remain){
    if(!pos)    return (sum % 7 == 0 && remain % 7 == 0);
    if(!limit && ~dp[pos][sum][remain])        return dp[pos][sum][remain];
    ll ans = 0;
    int up = limit ? num[pos] : 9;
    for(int i = 0; i <= up; ++ i)    ans += dfs(pos - 1, limit && i == up, (sum + i) % 7, (remain * 10 + i) % 7);
    if(!limit)    dp[pos][sum][remain] = ans;
    return ans;
}

ll solve(ll x){
    int pos = 0;
    while(x){
        num[++ pos] = x % 10;
        x /= 10;
    }
    return dfs(pos, true, 0, 0);
}

int main(){
    memset(dp, -1, sizeof dp);
    while(cin >> l >> r){
        if(!l && !r)    break;
        cout << solve(r) - solve(l - 1) << endl;
    }
    return 0;
}

树上子链
https://ac.nowcoder.com/acm/problem/202475
思路:类似树的最长路径求法, dp表示的是选择当前点的情况下能得到的最大单链的值(不能是折线形的, 否则没法推了)用ans实时更新答案(这时可能是折线形的)
tips:可能点权全是负的,所以ans初始化的时候不可以是0.

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 100010, M = 2 * N, inf = 0x3f3f3f3f;

int idx, ne[M], e[M], h[N];
int n;
ll dp[N],ans = -1e12,w[N];

void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++;}

void dfs(int u, int fa){
    ll d1 = 0, d2 = 0;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa)    continue ;
        dfs(j, u);
        if(dp[j] >= d1)    d2 = d1, d1 = dp[j];
        else if(dp[j] > d2)    d2 = dp[j];
    }
    d1 += w[u];
    dp[u] = d1;
    ans = max(d1 + d2, ans);
}

int main(){
    cin >> n;
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n; ++ i)    scanf("%lld", &w[i]);
    for(int i = 1; i <= n - 1; ++ i){
        int a, b;
        scanf("%d%d",&a,&b);
        add(a, b), add(b, a);
    }
    dfs(1, 1);
    //for(int i = 1; i <= n; ++ i)    printf("%d ", dp[i]);
    cout << ans;
    return 0;
}
/*
5
-1 -1 -1 -1 -1
1 2
2 3
3 4
4 5
-1
*/

吉吉王国
https://ac.nowcoder.com/acm/problem/210473
思路:求最大边权最小, 而且最大边权有限制, 想到二分+树形dp check.
tips:这题只是要求最大边权最小, 没有要求总的最小, 要和Rinne Loves Edges 区分开。 详见代码注释的两组样例。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 1010, M = 2 * N, inf = 0x3f3f3f3f;

int idx, ne[M], e[M], w[M], h[N];
int n,m,s;
ll dp[N];

void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;}

void dfs(int u, int fa, int limit){
    bool f = false;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa)    continue ;
        dfs(j, u, limit);
        if(w[i] > limit)    dp[u] += dp[j];
        else    dp[u] += min(dp[j], 1ll * w[i]);
        f = true;
    }    
    if(!f)    dp[u] = inf;
}

int main(){
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n - 1; ++ i){
        int a, b, c;
        scanf("%d%d%d",&a,&b,&c);
        add(a, b, c), add(b, a, c);
    }
    int l = 0, r = 1010; 
    while(l < r){
        int mid = (l + r) >> 1;
        memset(dp, 0, sizeof dp);
        dfs(1, 1, mid);
        if(dp[1] > m)    l = mid + 1;
        else r = mid;
        //printf("mid = %d l = %d r = %d dp = %d\n", mid,l, r, dp[1]);
    }
    if(r == 1010)    puts("-1");    
    else    cout << r;
    return 0;
}
/*
4 11
1 2 10
2 3 5
2 4 6
6

4 10
1 2 10
2 3 5
2 4 6
10
*/

Cell Phone Network
https://ac.nowcoder.com/acm/problem/24953
AcWing 皇宫看守
注意被儿子覆盖的情况比较复杂,需要求出所有儿子的min(dp0, dp1)的和之后减掉当前选择的这个儿子的min,再加上在这个儿子放守卫的情况, 但是发现这个min的和就是已经求出的dp2

/*
dp状态转移方程:
0表示自己覆盖            dp(u,0) += min(dp[j,0],dp[j,1],dp[j,2])
1表示儿子覆盖(自己不放) 找出一个儿子,剩下的+min(dp[j,0],dp[j,1]) + 
2表示父亲覆盖(自己不放)    dp[u,2] += min(dp[j,0], dp[j,1])
*/

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 10010, M = 2 * N;

int idx,ne[M],e[M],h[N],dp[N][3],n;

void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++;}

void dfs(int u, int fa){
    dp[u][0] = 1;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa)    continue ;
        dfs(j, u);
        dp[u][0] += min(dp[j][0], min(dp[j][1], dp[j][2]));
        dp[u][2] += min(dp[j][0], dp[j][1]);
    }
    dp[u][1] = 1e9;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa)    continue ;
        dp[u][1] = min(dp[u][1], dp[u][2] - min(dp[j][0], dp[j][1]) + dp[j][0]);
    }
}

int main(){
    cin >> n;
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n - 1; ++ i){
        int a, b;
        cin >> a >> b;
        add(a, b), add(b, a);
    } 
    dfs(1, 1);
    cout << min(dp[1][0], dp[1][1]);
    return 0;
} 

Accumulation Degree
https://ac.nowcoder.com/acm/problem/51180
思路: 换根DP.

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 200010, M = 2 * N;

int idx, ne[M], e[M], h[N], in[N], w[M];
int n,m,t;
ll dp[N], dp2[N];

void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;} 

void dfs1(int u, int fa){
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa)    continue ;
        dfs1(j, u);
        if(in[j] == 1)    dp[u] += w[i];
        else    dp[u] += min(dp[j], 1ll * w[i]); 
    }
}

void dfs2(int u, int fa){
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa)    continue ;
        if(in[u] == 1)    dp2[j] = w[i] + dp[j];
        else dp2[j] = dp[j] + min(1ll * w[i], dp2[u] - min(dp[j], 1ll * w[i]));
        dfs2(j, u);
    }
}

void solve(){
    idx = 0;
    memset(h, -1, sizeof h);
    memset(in, 0, sizeof in);
    memset(dp, 0, sizeof dp);
    cin >> n;
    for(int i = 1; i <= n - 1; ++ i){
        int a, b, c;
        scanf("%d%d%d", &a,&b,&c);
        add(a, b, c), add(b, a, c),    in[a] ++, in[b] ++;
    }
    dfs1(1, 1);
    dp2[1] = dp[1];
    dfs2(1, 1);
    //for(int i = 1; i <= n; ++ i)    printf("%d ", dp2[i]);
    ll ans = 0;
    for(int i = 1; i <= n; ++ i)    ans = max(ans, dp2[i]);
    cout << ans << endl;
}

int main(){
    cin >> t;
    while(t --)    solve();
    return 0;
}    

Tree
https://ac.nowcoder.com/acm/problem/19782
思路: 换根DP. 从下往上dfs的时候, 父亲的dp值 *= 儿子的dp + 1【这个1可以理解为这个子树的空集。如果所有子树都取空集,那就相当于之选当前这一个点】
从上往下dfs的时候有一个坑点, dp + 1有可能等于1e9 + 7, 这时没有逆元, 没法用快速幂+费马小求逆元, 所以我们可以暴力算得到该点的dp2. 注意dfs1就是暴力算以某个点为根的情况, 所以这里直接调用dfs1即可。

#include<bits/stdc++.h>
#define ll long long 
using namespace std;

const int N = 1e6 + 10, M = 2 * N, mod = 1e9 + 7;

int idx, ne[M], e[M], h[N];
int n;
ll dp[N],dp2[N];

ll ksm(ll a, ll b){
    ll res = 1;
    while(b){
        if(b & 1)    res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }    
    return res;
}

void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++;}

void dfs(int u, int fa){
    dp[u] = 1;
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa)    continue ;
        dfs(j, u);
        dp[u] = (dp[u] * (dp[j] + 1)) % mod;
    }
}

void dfs2(int u, int fa){
    for(int i = h[u]; ~i; i = ne[i]){
        int j = e[i];
        if(j == fa)    continue ;
        if((dp[j] + 1) % mod == 0){
            dfs(j, j);
            dp2[j] = dp[j];
        }    
        dp2[j] = (dp[j] * (dp2[u] * ksm((dp[j] + 1), mod - 2) % mod + 1)) % mod;
        dfs2(j, u);
    }
}

int main(){
    cin >> n;
    memset(h, -1, sizeof h);
    for(int i = 1; i <= n - 1; ++ i){
        int a, b;
        scanf("%d%d",&a,&b);
        add(a, b), add(b, a);
    }
    dfs(1, 1);
    dp2[1] = dp[1];
    dfs2(1, 1);
    for(int i = 1; i <= n; ++ i)    printf("%d\n", dp2[i]);
    return 0;
} 
全部评论

相关推荐

11-09 17:30
门头沟学院 Java
TYUT太摆金星:我也是,好几个华为的社招找我了
点赞 评论 收藏
分享
评论
1
收藏
分享
牛客网
牛客企业服务