codeforces好题

CF1515E Phoenix and Computers

这是一道很好的 \(\mathrm{dp}\) 题。

首先我们将电脑打开方式分为两种,一种为手动,一种为自动。

那么我们先来考虑 \(n\) 台电脑全部为手动的情况。

如果先打开 \(1\) 号,那么剩余 \(n-1\) 个数中 \([2,n]\) 的顺序一定是 \(2,3,\dots,n\),共 \(1\) 种。

如果先打开 \(2\) 号,那么剩余 \(n-1\) 个数中 \([3,n]\) 的顺序一定是 \(3,4,\dots,n\),所以这 \(n-2\) 个数可以在 \(n-1\) 个空中随便选 \(n-2\) 个位置,然后在把 \(1\) 填入剩余的空中,共有 \(\binom{n-1}{n-2}\) 种。

如果先打开 \(3\) 号,那么剩余 \(n-1\) 个数中 \([4,n]\) 的顺序一定是 \(4,5,\dots,n\),所以这 \(n-3\) 个数可以在 \(n-1\) 个空中随便选 \(n-3\) 个位置,然后在把 \(2,1\) 按序填入剩余的空中,共有 \(\binom{n-1}{n-3}\) 种。

所以对于一般情况,如果先打开 \(k(1\le k\le n)\) 号,那么剩余 \(n-1\) 个数中 \([k+1,n]\) 的顺序一定是 \(k+1,k+2,\dots,n\),所以这 \(n-k\) 个数可以在 \(n-1\) 个空中随便选 \(n-k\) 个位置,然后在把 \(k,k-1,\dots,1\) 按序填入剩余的空中,共有 \(\binom{n-1}{n-k}\) 种。

所以综上所述,\(n\) 台电脑全部为手动的情况共有 \(\sum_{i=1}^{n}\binom{n-1}{n-k}=2^{n-1}\)。

接下来我们考虑最后电脑开启的状态。

很明显最终电脑的状态是一段手动,一个自动,再来一段手动,如此交替。

设 \(f_{i,j}\) 表示前 \(i\) 台电脑,手动 \(j\) 台,第 \(i\) 台手动,且第 \(i+1\) 台为自动的方案数。

那么转移时就从一段手动转移到下一段手动,即 \(f_{i,j}\to f_{i+k+1,j+k}\)。

我们先来考虑这 \(k\) 台手动内部的顺序,我们已经证明,为 \(2^{k-1}\) 种。

再来考虑原来的 \(j\) 台手动和这 \(k\) 台的手动,可以随意穿插,共有 \(\binom{j+k}{k}\) 种。

以及原有的 \(f_{i,j}\) 种,我们可以得出转移方程 \(f_{i+k+1,j+k}=\sum_{k=1}^{n-i-1}f_{i,j}\times2^{k-1}\times\binom{j+k}{k}\)。

初始化 \(f_{i,i}=2^{i-1}\)。

答案为 \(\sum_{i=1}^nf_{n,i}\)。

时间复杂度:\(O(n^3)\)。

#include<iostream>
using namespace std;
int n,mod;
long long C[405][405],pow2[405],f[405][405],ans;
int main()
{
    cin>>n>>mod;
    C[0][0]=1;
    for(int i=1;i<=n;i++)
    {
        C[i][0]=1;
        for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    }
    pow2[0]=1;
    for(int i=1;i<=n;i++) pow2[i]=pow2[i-1]*2%mod;
    for(int i=1;i<=n;i++)
    {
        f[i][i]=pow2[i-1];
        for(int j=1;j<=i;j++)
        {
            for(int k=1;k<=n-i-1;k++) f[i+k+1][j+k]=(f[i+k+1][j+k]+f[i][j]*pow2[k-1]%mod*C[j+k][k]%mod)%mod;
        }
    }
    for(int i=1;i<=n;i++) ans=(ans+f[n][i])%mod;
    cout<<ans<<endl;
    return 0;
}

CF1528B Kavi on Pairing Duty

这是一道很好的 \(\mathrm{dp}\) 题。

引理:设 \(1\) 与 \(x\) 配对,则对于每一个 \(p(x<p\le2n)\),设它与 \(q\) 配对,则 \([p,q]\)(或 \([q,p]\))的长度与 \([1,x]\) 的长度相同。

设 \(f_i\) 表示 \(i\) 时的种数。

对 \(x\) 进行分类讨论。

当 \(n+1\le x\le2n\) 时,根据引理可得 \(1\) 与 \(x\) 配对,\(2\) 与 \(x+1\) 配对,\(3\) 与 \(x+2\) 配对,直至 \(2n-x+1\)与\(2n\) 配对。因为 \(n+1\le x\le2n\),所以 \(1\le2n-x+1\le x\),可以想象到连完之后中间会空下连续的一段,共 \(2x-2n-2\) 个点,个数为 \(f_{x-n-1}\)。所以当 \(n+1\le x\le2n\) 时,个数共有 \(\sum_{i=0}^{n-1}f_i\) 种。

当 \(2\le x\le n\) 时,易得 \(x-1\) 为 \(n\) 的约数且不等于 \(n\),设 \(D(n)\) 表示 \(n\) 除自己外的约数个数,则当 \(2\le x\le n\) 时,个数共有 \(D(n)\) 种。

综上,\(f_n=\sum_{i=0}^{n-1}f_i+D(n)\)。

初始化 \(f_0=1\)。

时间复杂度:\(O(n\log n)\)。

#include<iostream>
using namespace std;
long long mod=998244353;
long long n;
long long dp[1000005];
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        for(int j=2;i*j<=n;j++) dp[i*j]++;
    }
    dp[0]=1;
    long long sum=1;
    for(int i=1;i<=n;i++)
    {
        dp[i]=(dp[i]+sum)%mod;
        sum=(sum+dp[i])%mod;
    }
    cout<<dp[n]<<endl;
    return 0;
}

CF1510D Digits

这是一道很好的 \(\mathrm{dp}\) 题。

状态很容易想出,设 \(f_{i,j}\) 表示前 \(i\) 位乘积 \(\%10\) 后为 \(j\),且乘积最大的值。

但是转移时会发现乘积太大,无法直接转移。

因此我们可以运用对数化乘为加,即 \(x\times y=2^{\log_2x}\times2^{\log_2y}=2^{\log_2x+\log_2y}\)。

所以我们只需记录乘积的 \(\log\) 值即可。

转移方程:\(f_{i,k}=max(f_{i-1,k},f_{i-1,j}+\log_2a_i)\)。

至于方案,我们在更新答案的同时用一个 \(pre\) 数组来记录,即 \(pre_{i,k}=j\)。

初始化 \(f_{0,i}=-\inf\),\(f_{0,1}=0\)。

时间复杂度:\(O(10n)\)

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,k,cnt;
long long a[100005],pre[100005][15],ans[100005];
double f[100005][15];
int main()
{
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>a[i];
    memset(pre,-1,sizeof(pre));
    for(int i=0;i<=9;i++) f[0][i]=-1e9;
    f[0][1]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=9;j++) f[i][j]=f[i-1][j];
        for(int j=0;j<=9;j++)
        {
            int k=j*a[i]%10;
            if(a[i]==1||f[i][k]<f[i-1][j]+(double)log2(a[i]))
            {
                f[i][k]=f[i-1][j]+(double)log2(a[i]);
                pre[i][k]=j;
            }
        }
    }
    if(f[n][k]<0) cout<<-1<<endl;
    else
    {
        for(int i=n;i>=1;i--)
        {
            if(pre[i][k]!=-1)
            {
                ans[++cnt]=a[i];
                k=pre[i][k];
            }
        }
        if(cnt==0) cout<<-1<<endl;
        else
        {
            cout<<cnt<<endl;
            for(int i=1;i<=cnt;i++) cout<<ans[i]<<" ";
            cout<<endl;
        }
    }
    return 0;
}

CF1027E Inverse Coloring

这是一道很好的 \(\mathrm{dp}\) 题。

引理:当一个正方形的第一行与第一列确定之后,这个正方形的漂亮着色就已经确定了。

证明:易得

因此问题就转变为了要求行与列的最大连续同色长度的乘积 \(<k\)。

行与列相同,所以可以只算一边。

设 \(f_{i,j}\) 表示在前 \(i\) 个中,最大连续黑色长度 \(\le j\) 的个数。

不难得出转移方程:\(f_{i,j}=\sum_{k=i-j}^{i-1}f_{k,min(j,k)}\)。

初始化:\(f_{i,i}=1\)

再利用差分,设 \(sum_i\) 表示长度为 \(n\),且最大连续黑色长度 \(=i\) 的个数。

则 \(ans=\sum_{i=1}^n\sum_{j=1}^nsum_i\times sum_j\ [i\times j<k]\)。

最后因为有黑白两色,不要忘了 \(ans\times 2\)。

时间复杂度:\(O(n^3)\)。

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
int n,m;
const int mod=998244353;
long long f[505][505],sum[505],ans;
int main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) f[i][i]=1;
    for(int i=2;i<=n;i++)
    {
        for(int j=0;j<=i;j++)
        {
            for(int k=i-j;k<=i-1;k++) f[i][j]=(f[i][j]+f[k][min(j,k)])%mod;
        }
    }
    for(int i=1;i<=n;i++) sum[i]=(f[n][i]-f[n][i-1]+mod)%mod;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            if(i*j<m) ans=(ans+sum[i]*sum[j]%mod)%mod;
        }
    }
    cout<<ans*2%mod<<endl;
    return 0;
}
上一篇:科普篇 | 跨链兴趣小组(CC-SIG)


下一篇:Codeforces Round #767 (Div. 1)