Educational Codeforces Round 83 (Rated for Div. 2)

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

A - Two Regular Polygons

送分题。

B - Bogosort

题意:给一个数组a进行重排,使得新的数组a中,记b[i]=a[i]-i,则b[i]是唯一的。

题解:从大到小排序,这样就有b[i+1]=a[i+1]-(i+1)<a[i+1]-i<=a[i]-i<b[i]。

C - Adding Powers

题意:给一个n个数的数组b,另一个n个数的数组a初始全是0,第i步(i从0开始计数)可以选择数组a中的一个数加上k^i,或者跳过这一步,问是否能把数组a变成数组b。

题解:对每个数进行k进制分解,看看是否k进制下每一位的和都是<=1的。

D - Count the Arrays

可以初步观察出一些事实:
1、最大的数是只有一个的,否则不会满足左右都是严格升降序。
2、最大的数的取值范围为 \([n-1,m]\)
3、最大的数不能放在两端。
4、相等的数分布在最大的数两侧。

算法1,给位置选数

先枚举最大的数的位置,设其前面有 \(x\) 个位置,后面有 \(y\) 个位置,则 \(x\ge 0,y \ge 0,x+y=n-1\)
再枚举最大的数的取值,观察可以发现最大的数的取值是不受位置影响的,设取值为 \(p \in [n-1,m]\)
再从 \([1,p-1]\) 中选 \(x\) 个数给前面 \(x\) 个位置,选法为 \(C_{p-1}^{x}\)
再从前面 \(x\) 个数中选 \(1\) 个数作为相等的数字放在后面的某个位置, 选法为 \(C_{x}^{1}\)
再从 \([1,p-1]\) 个数中选 \(y-1\) 个数给后面剩下的 \(y-1\) 个位置,且这些数不能与前面 \(x\) 个数相等,选法为 \(C_{p-1-x}^{y-1}\)

这里会发现复杂度爆炸,但是这个形式看起来有规律。

\[\begin{align} &C_{p-1}^{x}*C_{x}^{1}*C_{p-1-x}^{y-1} \nonumber \\ &=\frac{(p-1)!}{(p-1-x)!x!}*x*\frac{(p-1-x)!}{(p-1-x-(y-1))!(y-1)!} \nonumber \\ &=\frac{(p-1)!}{(p-n+1)!(x-1)!(y-1)!} \nonumber \\ &=\frac{1}{(x-1)!(y-1)!}*\frac{(p-1)!}{(p-n+1)!} \nonumber \\ \end{align} \]

后面的部分与 \(x,y\) 无关,前面的部分与 \(p\) 无关,两部分可以分开统计了。

算法2,给数选位置

先从 \([1,m]\) 中选出 \(n-1\) 个数,然后可以把他们离散化到 \([1,n-1]\)
然后从 \([1,n-2]\) 中选出那个相等的数(因为 \(n-1\) 是最大的数,不能相等),放在最大的数两边。
剩下的数既可以放在左边,也可以放在右边,有 \(2^{n-3}\) 种选法。

反思:怪不得这道题这么多人过,算法2是真的简单,所以以后组合数学可以从“给位置选数”以及“给数选位置”两种角度考虑。

关键是得知了: \(C_{n}^{x}*C_{n-x}^{y}\ne C_{n}^{x+y}\) 而是 \(C_{n}^{x}*C_{n-x}^{y}\ne C_{n}^{x+y}*C_{x+y}^{x}\) ,也就是说,等式左侧的规定了哪些数是去 \(x\) 中的,所以在等式右侧也要规定。

*E - Array Shrinking

第一次见区间dp可以这样做的。以前的区间dp貌似都是枚举“最后一次合并区间时发生的状态转移”,但这里的两个区间合并的时候要保证“左右的剩下元素的左右边界相同”。这里就发现自己是没真正明白区间dp的。枚举的是“最后一次合并区间时发生的状态转移”,所以必须是“最后一次合并区间”,换言之,假如枚举的区间是[5 4 3 3][3 3 4 5],回答的就应该是[7],为什么不用考虑下面的[3 3]或者3][3合并呢?因为这些合并应该在长度更小(长度为2)的时候被处理完,并不是“最后一次合并区间”。换言之,在枚举断点,合并左右两端的时候,只需要知道两点:

1、左区间和右区间是否分别能合并成一个数,使得枚举断点的位置确实成为“最后一次合并”。

2、左区间和右区间能合并成的一个数是否相同,使得枚举断点的位置确实成为“最后一次合并”。

int a[505];
int b[505][505];
int dp[505][505];

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= n; ++j) {
            dp[i][j] = INF;
            b[i][j] = 0;
        }
    }
    for(int i = 1; i <= n; ++i) {
        dp[i][i] = 1;
        b[i][i] = a[i];
    }
    for(int len = 2; len <= n; ++len) {
        for(int l = 1; l <= n; ++l) {
            int r = l + len - 1;
            if(r > n)
                break;
            for(int m = l; m < r; ++m) {
                dp[l][r] = min(dp[l][r], dp[l][m] + dp[m + 1][r]);
                if(b[l][m] != 0 && b[l][m] == b[m + 1][r]) {
                    dp[l][r] = 1;
                    b[l][r] = b[l][m] + 1;
                    break;
                }
            }
        }
    }
    printf("%d\n", dp[1][n]);
}

总结:其实就是短的合并都已经被下层枚举了,比如[5 4 3 3 3 3 4 5],假如要问[4 3 3]是否可以合并成一个数,就向下询问[4][3 3]是否可以分别合并成一个数,且这两个数相等(并不需要事先知道要合并成几,因为每个区间完全合并得到的数是唯一的,dfs得到左边之后询问右边即可),再问[4 3][3]行不行,以此类推。

posted @ 2020-03-10 01:04  KisekiPurin2019  阅读(147)  评论(0编辑  收藏  举报