Divide by Zero 2021 and Codeforces Round #714 (Div. 2)

A

  1. 需要找到一个满足长度为n的含有k个峰的排列,直接将前k大的数都作为峰就好了;
  2. 峰的位置为2,4,…这样安排就能安排最多的峰,剩下的按照顺序插入空余位置;
  3. 不能满足的情况:n为奇数时,k > n / 2;n为偶数时,k >= n / 2(这里除法都是下取整)。

B

看题目第一眼感觉就是找规律,可是卡了好久,先过的C…

  1. 先来证明一个推论:
    a 1 = a 2 & a 3 & . . . & a n a_1 = a_2\&a_3\&...\&a_n a1=a2&a3&...&an
    等价于对任意 1 < i < n 1 < i < n 1<i<n
    a 1 & a 2 & . . & a i = a i + 1 & a i + 2 & . . . & a n a_1\&a_2\&..\&a_i = a_{i + 1}\&a_{i + 2}\&...\&a_n a1&a2&..&ai=ai+1&ai+2&...&an

    我用的反证法,反正它是成立的…(狗头保命)
    开始。把第一个式子称为 ① 式,第二个式子称为 ② 式,假设①式成立时存在 1 < i < n 1 < i < n 1<i<n 使得 ② 不成立,即
    ③     a 1 = a 2 & a 3 & . . . & a n ④     a 1 & a 2 & . . & a i ≠ a i + 1 & a i + 2 & . . . & a n ③\ \ \ a_1 = a_2\&a_3\&...\&a_n\\ ④\ \ \ a_1\&a_2\&..\&a_i \neq a_{i + 1}\&a_{i + 2}\&...\&a_n    a1=a2&a3&...&an   a1&a2&..&ai=ai+1&ai+2&...&an
    然后③式两边同时 & \& & ( a 2 & a 3 & . . . & a i ) (a_2\&a_3\&...\&a_i) (a2&a3&...&ai) 这个数,那么 ③ 式左边就变得和 ④ 式左边一样了,但这时候 ③ 式右边还是不变的(想想为什么),变成下面这个
    ⑤       a 1 & a 2 & . . & a i = a 2 & a 3 & . . . & a n ⑤\ \ \ \ \ a_1\&a_2\&..\&a_i=a_2\&a_3\&...\&a_n      a1&a2&..&ai=a2&a3&...&an
    对 ④ 式进行一样 的操作,两边同时 & \& & ( a 2 & a 3 & . . & a i ) (a_2\&a_3\&..\&a_i) (a2&a3&..&ai) 这个数,结果中左边是不变的,得到下面的结果
    ⑥      a 1 & a 2 & . . & a i ≠ a 2 & a 3 & . . . & a n ⑥\ \ \ \ a_1\&a_2\&..\&a_i \neq a_2\&a_3\&...\&a_n     a1&a2&..&ai=a2&a3&...&an
    ⑤ ⑥ 矛盾,所以假设不成立,即前面所说的推论是成立的。

  2. 根据题目意思和上一步得到的推论,要使得
    a 1 & a 2 & . . & a i = a 2 & a 3 & . . . & a n a_1\&a_2\&..\&a_i=a_2\&a_3\&...\&a_n a1&a2&..&ai=a2&a3&...&an
    成立,就要满足下面两个式子
    a 1 = a 2 & a 3 & . . . & a n a 1 & a 2 & . . & a n − 1 = a n a_1=a_2\&a_3\&...\&a_n\\a_1\&a_2\&..\&a_{n - 1}=a_n a1=a2&a3&...&ana1&a2&..&an1=an
    所以只要能找到两个元素,把它们分别放在首位和末尾就可以了。

    假如我们找到 k k k 个元素是满足可以放在首位或者末尾的,那么就从中选取两个,然后中间全排列,就是答案,乘2是因为首尾两个元素也有是顺序的
    a n s = 2 ∗ C k 2 ∗ ( n − 2 ) ! ans = 2 * C_k^2 *(n - 2)! ans=2Ck2(n2)!
    然后要做的就是找到这个 k k k

  3. 上面的式子可以换个写法,即
    a 1 = a 2 & a 3 & . . . & a n a_1=a_2\&a_3\&...\&a_n a1=a2&a3&...&an
    可以写成(这里 ∧ \wedge 是异或操作)
    a 1 ∧ ( a 2 & a 3 & . . . & a n ) = 0 a_1\wedge{(a_2\&a_3\&...\&a_n)} = 0 a1(a2&a3&...&an)=0
    接下来就是判断每个元素,将它异或(剩下n - 1个元素进行 & \& & 操作的结果)得到的结果,看是否为0。如果为0,k++.

    但是每次遍历元素时,对其他元素进行 & \& & 操作求和时间复杂度大,我是用了一个 bit[32] 进行记录。将所有数的每个二进制位记录下来,bit[i]表示在二进制表示中,n个数的二进制表示在第 i 的1的数目。如数组 {1, 2, 3} 那么bit[0] = 2, bit[1] = 1,其他为0.

    这时候遍历到 第 i 个元素时,bit[] 数组先减去 第 i 个元素的记录,然后判断剩下的是否为 n - 1个1(说明剩下n - 1个元素进行 & \& & 操作后这个二进制位仍为 1 ),为 1 则 a 1 & a 2 & . . . & a i − 1 & a i + 1 & . . . & a n a_1\&a_2\&...\&a_{i - 1}\&a_{i + 1}\&...\&a_n a1&a2&...&ai1&ai+1&...&an 的二进制第 i 位 为 1,这样就能直接得到了。

C

一、打表+OEIS

  1. 所有个位数都可以从 0 开始进行多次变化得到,所以尝试查看前面的结果有没有什么规律,OEIS说有(A103377),所以过了:点这里

二、DP思路

  1. 对于从0开始的数列{0,1,2,…,9,10,21,32,…},用 d p [ i ] dp[i] dp[i] 表示第几个数的位数,我这里 i i i 从0开始。
  2. d p [ i ] dp[i] dp[i] 相比 d p [ i − 1 ] dp[i - 1] dp[i1]$ 增加多少,是由 d p [ i − 1 ] dp[i - 1] dp[i1] 对应的数中数字9的数量来决定的:9->10,数位+1.所以可以得到
    d p [ i ] = d p [ i − 1 ] + n u m ( d p [ i − 1 ] 对 应 数 字 中 9 的 数 量 ) dp[i] = dp[i - 1] + num(dp[i - 1]对应数字中9的数量) dp[i]=dp[i1]+num(dp[i1]9
    然后就将问题转化为求 d p [ i − 1 ] dp[i - 1] dp[i1] 对应数字中9的数量。
  3. k k k 个数字中 9 的数量等价于第 k − 8 k - 8 k8 个数的 1 的数量,来看一下 1 的来源:9+1=10,0+1=1。所以分别求出这两个贡献,和就是结果。
  4. 首先是 9+1=10。意思就是看前一个数有多少个9。前一个数到当前数,位数增加,只有 9 是有贡献的。位数增加多少,说明上一个数有多少个 9,也就是当前数有多少个这样产生的 1。所以第 k − 8 k - 8 k8 位这样的 1 的数量为
    d p [ k − 8 ] − d p [ k − 9 ] dp[k - 8] - dp[k - 9] dp[k8]dp[k9]
  5. 然后是 0 + 1,原来的 0 必定是非前缀 0。特殊的只有第一个数是0,dp[0] = 1,其他数的有效 0 都是由 9 + 1 = 10 得到的,所以关注 前一个数有多少个 0 就好了。而这里的 0 和上一步所讨论的“这样的 1”,性质是一样的,都是由 9 + 1 = 10 产生。因为这里求的 0 是上一位的,所以对应的 9 是上上位的,所以第 k − 8 k - 8 k8 中 0 + 1 = 1 得到的 1 的数量,等价于上一个数 9 + 1 = 10 得到的 0 的数量,等价于上上个数中 9 的数量,即
    d p [ k − 9 ] − d p [ k − 10 ] dp[k - 9] - dp[k - 10] dp[k9]dp[k10]
  6. 所以得到
    n u m ( d p [ k ] 对 应 数 字 中 9 的 数 量 ) = d p [ k − 8 ] − d p [ k − 9 ] + d p [ k − 9 ] − d p [ k − 10 ] = d p [ k − 8 ] − d p [ k − 10 ] num(dp[k]对应数字中9的数量)= dp[k - 8] - dp[k - 9] + dp[k - 9] - dp[k - 10]\\ = dp[k - 8] - dp[k - 10] num(dp[k]9=dp[k8]dp[k9]+dp[k9]dp[k10]=dp[k8]dp[k10]
  7. k = i − 1 k = i - 1 k=i1 时,就有
    d p [ i ] = d p [ i − 1 ] + d p [ i − 9 ] − d p [ i − 11 ] dp[i] = dp[i - 1] + dp[i - 9] - dp[i - 11] dp[i]=dp[i1]+dp[i9]dp[i11]
    预处理前十几个数,后面就可以递推打表了
#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N = 2e5 + 17;
const int mod = 1e9 + 7;
int dp[N];

void init(){
    for(int i = 0; i < 10; ++i)
        dp[i] = 1;
    for(int i = 10; i < 13; ++i)
        dp[i] = 2;
    for(int i = 13; i < N; ++i){
        dp[i] = (dp[i - 1] + dp[i - 9] - dp[i - 11] + mod) % mod;
    }
}

void solve(){
    int n, m;
    cin >> n >> m;
    int ans = 0;
    while(n){
        int x = n % 10;
        n /= 10;
        ans += dp[m + x];
        ans %= mod;
    }
    cout << ans << endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    init();
    // for(int i = 1; i <= 1000; i += 1)
    //     cout << i << ' ' << dp[i] << endl;
    int t;
    cin >> t;
    while(t--){
        solve();
    }
    return 0;
}
  1. 状态转移方程可以进一步修改,变成OEIS中给的式子。现在,直接列出来!
    d p [ i ] = d p [ i − 1 ] + d p [ i − 9 ] − d p [ i − 11 ] d p [ i − 1 ] = d p [ i − 2 ] + d p [ i − 10 ] − d p [ i − 12 ] d p [ i − 2 ] = d p [ i − 3 ] + d p [ i − 11 ] − d p [ i − 13 ] d p [ i − 3 ] = d p [ i − 4 ] + d p [ i − 12 ] − d p [ i − 14 ] d p [ i − 4 ] = d p [ i − 5 ] + d p [ i − 13 ] − d p [ i − 15 ] . . . d p [ 14 ] = d p [ 13 ] + d p [ 5 ] − d p [ 3 ] d p [ 13 ] = d p [ 12 ] + d p [ 4 ] − d p [ 2 ] d p [ 12 ] = d p [ 11 ] + d p [ 3 ] − d p [ 1 ] d p [ 11 ] = d p [ 10 ] + d p [ 2 ] − d p [ 0 ] dp[i] = dp[i - 1] + dp[i - 9] - dp[i - 11]\\ dp[i - 1] = dp[i - 2] + dp[i - 10] - dp[i - 12]\\ dp[i - 2] = dp[i - 3] + dp[i - 11] - dp[i - 13]\\ dp[i - 3] = dp[i - 4] + dp[i - 12] - dp[i - 14]\\ dp[i - 4] = dp[i - 5] + dp[i - 13] - dp[i - 15]\\ ...\\ dp[14] = dp[13] + dp[5] - dp[3]\\ dp[13] = dp[12] + dp[4] - dp[2]\\ dp[12] = dp[11] + dp[3] - dp[1]\\ dp[11] = dp[10] + dp[2] - dp[0] dp[i]=dp[i1]+dp[i9]dp[i11]dp[i1]=dp[i2]+dp[i10]dp[i12]dp[i2]=dp[i3]+dp[i11]dp[i13]dp[i3]=dp[i4]+dp[i12]dp[i14]dp[i4]=dp[i5]+dp[i13]dp[i15]...dp[14]=dp[13]+dp[5]dp[3]dp[13]=dp[12]+dp[4]dp[2]dp[12]=dp[11]+dp[3]dp[1]dp[11]=dp[10]+dp[2]dp[0]
    对,没错,累加,消去,最后得到
    d p [ i ] = d p [ i − 9 ] + d p [ i − 10 ] + d p [ 10 ] − d p [ 1 ] − d p [ 0 ] dp[i] = dp[i - 9] + dp[i - 10] + dp[10] - dp[1] - dp[0] dp[i]=dp[i9]+dp[i10]+dp[10]dp[1]dp[0]
    而dp[10] = 2, dp[1] = dp[0] = 1,所以得到最终式子
    d p [ i ] = d p [ i − 9 ] + d p [ i − 10 ] dp[i] = dp[i - 9] + dp[i - 10] dp[i]=dp[i9]+dp[i10]
    再用最终式子交一遍
#include<bits/stdc++.h>
using namespace std;

#define int long long

const int N = 2e5 + 17;
const int mod = 1e9 + 7;
int dp[N];

void init(){
    for(int i = 0; i < 10; ++i)
        dp[i] = 1;
    for(int i = 10; i < 13; ++i)
        dp[i] = 2;
    // for(int i = 13; i < N; ++i){
    //     dp[i] = (dp[i - 1] + dp[i - 9] - dp[i - 11] + mod) % mod;
    // }
    for(int i = 13; i < N; ++i){
        dp[i] = (dp[i - 9] + dp[i - 10]) % mod;
    }
}

void solve(){
    int n, m;
    cin >> n >> m;
    int ans = 0;
    while(n){
        int x = n % 10;
        n /= 10;
        ans += dp[m + x];
        ans %= mod;
    }
    cout << ans << endl;
}

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    init();
    // for(int i = 1; i <= 1000; i += 1)
    //     cout << i << ' ' << dp[i] << endl;
    int t;
    cin >> t;
    while(t--){
        solve();
    }
    return 0;
}

这里是我的一个错误思路,或许可以得到修正。
容易想到,第 k k k 个数字中 9 的数量也等价于第 k − 9 k - 9 k9 个数中 0 的数量。而有效 0 的来源只有 9 + 1 = 10,所以直接得到
d p [ i ] = d p [ i − 1 ] + d p [ i − 9 ] − d p [ i − 10 ] dp[i] = dp[i - 1] + dp[i - 9] - dp[i - 10] dp[i]=dp[i1]+dp[i9]dp[i10]
影响正确性的,是前缀0.
意思就是,第 k 个数字中的一个 9 ,在第 k - 9 中对应的 0 是无效的,即前缀0。如第18个数是 98,第9个数是9。

  • 7
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值