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) 个位置,则 (xge 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})

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

[egin{align} &C_{p-1}^{x}*C_{x}^{1}*C_{p-1-x}^{y-1} onumber \ &=frac{(p-1)!}{(p-1-x)!x!}*x*frac{(p-1-x)!}{(p-1-x-(y-1))!(y-1)!} onumber \ &=frac{(p-1)!}{(p-n+1)!(x-1)!(y-1)!} onumber \ &=frac{1}{(x-1)!(y-1)!}*frac{(p-1)!}{(p-n+1)!} onumber \ 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} e C_{n}^{x+y}) 而是 (C_{n}^{x}*C_{n-x}^{y} e 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
", dp[1][n]);
}

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