Codeforces Round #202 (Div. 1) (树形dp, dp)

A. Mafia

给你n个数,每次可以令n-1个数减去1,0的还是0,问最少要多少次使得所有数都为0。

思路:理想状态下每次总和是减去n-1的,这种状态的次数与最大的数


B. Apple Tree

给你一颗树,每个叶子节点上有一些苹果,现在要使得每个节点的不同子树的苹果数相等,问最少要去掉多少苹果才可以满足。可以把所有苹果去掉来满足。


思路:设cnt[ u ]表示u这颗子树总共有cnt[u]个苹果,redu[ u ]表示u这颗子树每次最少要减去redu[u]才可以使得子树满足平衡,很容易可以知道cnt[u] 一定是 redu[u]的倍数,对于u的每一个儿子to,要使得每个儿子to上的苹果数相等并且每次要减去的数也相等,所以就是对所有的redu[to]求lcm,意思就是u的每个儿子的子树每次至少要减去lcm,那么u这整颗树每次最少要减去lcm*儿子数,即redu[u] = lcm*儿子数。这里要注意判断lcm是否超过了某一个子树的cnt,如果超过了,那么不可行了,最后肯定是把所有苹果都去掉的,如果没超过,cnt[ u ]即等于 儿子数*(最小的cnt[to],并且是lcm的倍数)。


#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
using namespace std;
#define pb push_back

typedef __int64 ll;
const int maxn = 100000 + 5;

struct Edge {
    int to, next;
}edge[maxn<<1];

int head[maxn], E;
ll cnt[maxn], redu[maxn];

void newedge(int u, int to) {
    edge[E].to = to;
    edge[E].next = head[u];
    head[u] = E++;
}

void init(int n) {
    for(int i = 1;i <= n; i++)
        head[i] = -1;
    E = 0;
}

ll gcd(ll a, ll b) {
    return b ? gcd(b, a%b) : a;
}

ll lcm(ll a, ll b) {
    return a/gcd(a, b)*b;
}

void dfs(int u, int pre) {
    vector<int> son;
    for(int i = head[u];i != -1;i = edge[i].next) {
        int to = edge[i].to;
        if(to == pre)   continue;
        dfs(to, u);
        son.pb(to);
    }
    if(son.size() == 0) {
        redu[u] = 1;
        return ;
    }
    for(int i = 0;i < son.size(); i++) {
        if(redu[son[i]] == 0) {
            redu[u] = 0;
            return ;
        }
    }
    ll m = redu[son[0]];
    for(int i = 0;i < son.size(); i++) {
        m = lcm(m, redu[son[i]]);
    }
    for(int i = 0;i < son.size(); i++) {
        if(m > cnt[son[i]]) {
            redu[u] = 0;
            return ;
        }
    }
    ll cur = cnt[son[0]]/redu[son[0]];
    ll now =  cur/(m/redu[son[0]])*(m/redu[son[0]])*redu[son[0]];
    for(int i = 1;i < son.size(); i++) {
        cur = cnt[son[i]]/redu[son[i]];
        now = min(now, cur/(m/redu[son[i]])*(m/redu[son[i]])*redu[son[i]]);
    }
    redu[u] = m*son.size();
    cnt[u] = now*son.size();
//    printf("m = %I64d u = %d cnt = %I64d redu = %I64d\n", m, u, cnt[u], redu[u]);
}

int main() {
    int n, u, to;
    scanf("%d", &n);
    init(n);
    ll tot = 0;
    for(int i = 1;i <= n; i++)
        scanf("%I64d", &cnt[i]), tot += cnt[i];
    for(int i = 0;i < n-1; i++) {
        scanf("%d%d", &u, &to);
        newedge(u, to);
        newedge(to, u);
    }
    dfs(1, -1);
    printf("%I64d\n", tot - cnt[1]);
    return 0;
}


D. Turtles

给你一个图,#表示不能走,现在有两只乌龟一起从(1, 1)走到 (n, m), (x, y)只能走到 (x+1, y)和(x, y+1),求两只乌龟路线的公共点只有(1,1)和(n,m)的情况数。


思路:

现在设A,B两只乌龟,令A从(1, 2)这个点出发,B从(2, 1)这个点出发,那么A最后肯定是到达(n-1,m),B肯定是到达(n, m-1)的。这样算肯定是包括所有合法路线的,但是还要把不合法路线去除掉,现在考虑每个不合法路线中间至少都会有一个交点,也就是说现在要计算的就是所有路线中至少有一个交点的情况数。

如果我令A从(1, 2)出发到达(n, m-1),B从(2, 1)出发到达(n-1, m),那么这两条路线必然有交点,实际上这个算的也就是所有的不合法情况数!因为对于这两条路线的最后一个交点,只需要把后面A,B的路线换一下,就成了第一种走法了。


#include <stdio.h>
#include <string.h>
typedef __int64 ll;

const int mod = 1000000007;
const int maxn = 3000 + 5;

char s[maxn][maxn];
ll dp[maxn][maxn];

ll getans(int x1, int y1, int x2, int y2) {
    if(x2 < x1 || y2 < y1)  return 0;
    for(int i = x1;i <= x2; i++) {
        for(int j = y1;j <= y2; j++) {
            dp[i][j] = 0;
            if(s[i][j] == '#')  continue;
            if(i == x1 && j == y1)  dp[i][j] ++;
            if(j > y1)  dp[i][j] += dp[i][j-1];
            if(i > x1)  dp[i][j] += dp[i-1][j];
            if(dp[i][j] >= mod) dp[i][j] -= mod;
        }
    }
    return dp[x2][y2];
}

int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1;i <= n; i++)
        scanf("%s", s[i] + 1);
    if(s[1][2] == '#' || s[2][1] == '#' || s[n-1][m] == '#' || s[n][m-1] == '#')    return puts("0"), 0;
    ll ans = getans(1, 2, n-1, m)*getans(2, 1, n, m-1)%mod - getans(1, 2, n, m-1)*getans(2, 1, n-1, m)%mod;
    printf("%I64d\n", (ans%mod + mod)%mod);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值