Codeforces Round #627 (Div. 3)

题目链接:https://codeforces.com/contest/1324

A - Yet Another Tetris Problem

这个题的俄罗斯方块居然还不能旋转的?

不知道这个算法对不对,假如题目是那种可以放置1*2或者2*1两种方块的话,总是能够先放一系列1*2使得最后剩下的ai全是<=1。这时假如全部都是0或者全部是1则YES,否则若某两个相邻的1(墙也是1)之间有奇数个0,则一定是NO。因为,要么所有格子已经被消除,要么在最后消除这行的时候必定是00111或者10011之类的,连续偶数个0的情况。不过要尝试全部在0的位置竖着放1个2,这样会使得0和1状态翻转,有可能会翻转连续区间的奇偶性。比如:0110,翻转变成1001。

或者对于这种问题用老套路黑白格染色,规定左下角的格子是白色,当有偶数列时,每次消除的黑白格子的数量相同,所以只有当所有的格子的黑白数量相同时,才有可能全部消除(因为引入的新格子不会改变黑白数量的差)。同时易知所有格子的黑白数量相同时,一定可以全部消除,首先用上面的思路给低的格子全部+2,然后全部消掉,最后剩下一行的零星的格子,那么这一行相邻的黑白格,可以通过中间填入躺着的格子连起来,最后有可能会剩下两个底角的格子。这时全部+2使得状态翻转,再做一次就全部消除了。当有奇数列时,会交替多删除一个白格或者多删除一个黑格。所以全部消除的前提条件是黑白格子的数量差<=1。若黑白格子的数量相等,可以通过全部+2的形式使得黑白格子的数量差为1。然后把相邻的黑白格之间用躺着的格子连起来。

所以假如可以竖着或者横着的话,消除的充要条件是:n为偶数:黑白相等,n为奇数,黑白相差<=1。

但是假如是题目的意思的话就判断一下奇偶性就可以了。

B - Yet Another Palindrome Problem

C - Frog Jumps

题意:有个青蛙,一开始只能往右跳,[1,n]有一些字符'L'或者'R'。青蛙可以一开始确定一个最大跳跃距离d,以后他在什么字符的格子就只能往这个方向跳[0,d]步(不能越界)。求跳到n+1需要的最小的d。

题解:可以发现那些'L'是没什么用的(不需要进入'L'格,也可以去到其左边的'R'格,而左边的'R'格也没什么更有用的),每次都要走到'R'上面才能继续前进,所以就找最长的两个'R'之间的距离。默认第一个和最后一个也是'R'。

搞这些花里胡哨的干嘛。

int n;
char s[200005];

void test_case() {
    scanf("%s", s + 1);
    n = strlen(s + 1);
    int ans = 0, prev = 0;
    for(int i = 1; i <= n; ++i) {
        if(s[i] == 'R') {
            ans = max(ans, i - prev);
            prev = i;
        }
    }
    ans = max(ans, n + 1 - prev);
    printf("%d\n", ans);
}

*D - Pair of Topics

题意:求有多少对(i,j)(i<j),满足ai+aj>bi+bj。

题解:移项得ai-bi>bj-aj,即bi-ai<aj-bj,注意两边的形式是不一样的,不是一个求逆序对的问题,不过处理的方法是一样的。也就是查询的都是ai-bi-1,插进树状数组的都是bi-ai。由于是小于号,所以树状数组就正着建。

int n;
int a[200005];
int b[200005];
int x[400005], xtop;
int bit[400005];
 
void add(int x, int v) {
    for(; x <= xtop; x += x & (-x))
        bit[x] += v;
}
 
ll sum(int x) {
    ll res = 0;
    for(; x; x -= x & (-x))
        res += bit[x];
    return res;
}
 
void test_case() {
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    xtop = 0;
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &b[i]);
        x[++xtop] = a[i] - b[i] - 1;
        x[++xtop] = b[i] - a[i];
    }
    sort(x + 1, x + 1 + xtop);
    xtop = unique(x + 1, x + 1 + xtop) - (x + 1);
    ll ans = 0;
    for(int i = 1; i <= n; ++i) {
        int pos1 = lower_bound(x + 1, x + 1 + xtop, a[i] - b[i] - 1) - x;
        ans += sum(pos1);
        int pos2 = lower_bound(x + 1, x + 1 + xtop, b[i] - a[i]) - x;
        add(pos2, 1);
    }
    printf("%lld\n", ans);
}

看了别人的代码才发现可以对原本的数组排序,因为这道题规定i<j只是告诉大家一个对只计算一次,这个和逆序对的那道题是不一样的。因为这道题的查询的数和插入的数不一样,所以不会像统计逆序对的那道题一样产生影响。

收获:要认真移项,不要想当然。

*E - Sleeping Schedule

题意:要睡n次,第i次睡觉之前,在第i-1次刚睡醒的时候可以控制是ai小时后睡觉还是ai-1小时后睡觉。一天有h小时。睡觉的开始时间在[l,r]中是一次正常作息。求n次睡觉能得到多少次正常作息。

一看就知道是个憨b的dp。设前i次睡觉,睡觉结束之后的时间为j,那么由于每次都是恰好睡一天,正好都不需要加上去了。好像因为ai>=1所以其实那个+h防负数是没有必要的。

int a[2005];
int dp[2005][2005];

void test_case() {
    int n, h, l, r;
    scanf("%d%d%d%d", &n, &h, &l, &r);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    memset(dp, -INF, sizeof(dp));
    dp[0][0] = 0;
    for(int i = 1; i <= n; ++i) {
        for(int j = 0, tj; j < h; ++j) {
            tj = (j + a[i]) % h;
            dp[i][tj] = max(dp[i][tj], dp[i - 1][j] + (l <= tj && tj <= r));
            tj = (j + a[i] - 1 + h) % h;
            dp[i][tj] = max(dp[i][tj], dp[i - 1][j] + (l <= tj && tj <= r));
        }
    }
    int ans = 0;
    for(int j = 0; j < h; ++j)
        ans = max(ans, dp[n][j]);
    printf("%d\n", ans);
}

F - Maximum White Subtree

题意:给一棵树,树上有一些黑点白点,对于每个点,选择一个包含该点的连通块,使得白色点的数量比黑色点的数量多的值最大。求这个最值。

题解:看起来很像树形dp的形式,设dp[u][v]为“当前根为节点u的,除去v边方向外的其他整棵树中(也就是除去v边外的子树中),最大的cntw-cntb值”。

这个东西显然可以通过二次扫描的换根法转移,具体为:

首先把白色记为1,黑色记为-1。

先选一个根,比如1,进行一次dfs,在这次dfs中,从叶子向根转移,处理的数组记为dp。

叶子除去唯一的v边之后就没有其他边了,所以只有一个 \(dp[u][p]=color[u]\)

否则一定有子树,必有 \(dp[u][p]=color[u]+\sum_{v\neq p} max(0,dp[v][u])\) ,而除去某个子树的信息要得到父亲那边的信息,暂时无法转移。

然后再进行一次dfs,在这次dfs中,从根向叶子转移,处理的数组依然是dp。

对于根来说,其不存在父亲,那么不走某个子树v0的包含根节点u的整棵子树,就相当于 \(dp[u][v0]=color[u]+\sum_{v\neq v0} max(0,dp[v][u])\) ,后面这个式子可以变形,所以变成 \(dp[u][v0]=color[u]+\sum_{v} max(0,dp[v][u]) - max(0,dp[v0][u])\)

对于非根来说,则有 \(dp[u][v0]=color[u]+\sum_{v\neq p} max(0,dp[v][u]) - max(0,dp[v0][u])+max(0,dp[p][u])\)

那么对于某个点u的答案就是 \(dp[u][p]+max(0,dp[p][u])\)

仔细看看上面的式子还可以合并?并不需要对叶子和根进行特殊处理。

int color[200005];
vector<int> G[200005];
map<int, int> dp[200005];

void dfs1(int u, int p) {
    int sum = color[u];
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs1(v, u);
        sum += max(0, dp[v][u]);
    }
    dp[u][p] = sum;
}

int ans[200005];

void dfs2(int u, int p) {
    int sum = color[u];
    for(auto &v : G[u])
        sum += max(0, dp[v][u]);
    for(auto &v : G[u])
        dp[u][v] = sum - max(0, dp[v][u]);
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs2(v, u);
    }
    ans[u] = dp[u][p] + max(0, dp[p][u]);
}

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &color[i]);
        if(color[i] == 0)
            color[i] = -1;
    }
    for(int i = 1; i <= n - 1; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs1(1, 0);
    dfs2(1, 0);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", ans[i], " \n"[i == n]);
}

上面是用了map的版本,主要是在dfs2的时候要创建dp[u][v]以及使用父亲的dp[p][u],突然奇想这个其实可以直接下传。

int color[200005];
vector<int> G[200005];
int dp[200005];

void dfs1(int u, int p) {
    int sum = color[u];
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs1(v, u);
        sum += max(0, dp[v]);
    }
    dp[u] = sum;
}

int ans[200005];

void dfs2(int u, int p, int dppu) {
    int sum = color[u] + max(0, dppu);
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        sum += max(0, dp[v]);
    }
    for(auto &v : G[u]) {
        if(v == p)
            continue;
        dfs2(v, u, sum - max(0, dp[v]));
    }
    ans[u] = dp[u] + max(0, dppu);
}

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &color[i]);
        if(color[i] == 0)
            color[i] = -1;
    }
    for(int i = 1; i <= n - 1; ++i) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dfs1(1, 0);
    dfs2(1, 0, 0);
    for(int i = 1; i <= n; ++i)
        printf("%d%c", ans[i], " \n"[i == n]);
}

没快多少,反而变复杂了。

提示:在树中要求以每个点为根求一次信息,极有可能是二次扫描换根法树形dp。

posted @ 2020-03-13 03:25  KisekiPurin2019  阅读(402)  评论(8编辑  收藏  举报