2024_CCPC网络赛I题

2024_CCPC网络赛I题

题目链接

思路

  • time为1s,n==200,可以\(n^3\)做法。

  • 可以想到枚举每一个时间间隔。原先的思路是对于每一个确定的时间,比如x,通过某种dp求出来时间为x的时候的方案数目。所以比赛的时候一直卡在这里没做出来。

  • 有一个小trick:
    当我们在枚举某一个变量统计结果的时候,我们需要之后刚刚好这个变量为x的时候的结果,可以考虑求解出来所有\(>=x\)的结果,在求解出来所有\(>=(x+1)\)的结果,进行差分可以得到刚好等于x的结果
    也就是用差分的思想来代替原先的刚好等于的情况。
    同时也可以使用\(<=\)

  • 使用了上面的trick之后,处理对于每一个变量x如何求解方案数目。
    状态转移方程:首先可以想到,\(dp[i][j]\)表示前i个人,选择了\(j\)个物品的方案数目。是选择了j件,如果想要表示在前j件里面做了什么事情,还要有新的维度。
    对于当前的时间间隔d,只要物品和人物的时间间隔\(>=d\),这个人就可以选择这个物品。可以先使用sum[]数组,预处理出来每一个人有多少种选择。

    对于第i个人,可以选择的物品的坐标区间是\([0,pos_1]\),对于第i+1个人可以选择的物品的位置的区间:\([0,pos_2]\)

    pos2一定大于等于pos1,也就是前一个人可以选择的区间,一定是后一个人的子集。

    5的选择一定是6里面的选择。

  • 方程转移:
    对于\(dp[i][j]\)。首先一种方式就是

    \[dp[i][j] += dp[i-1][j] \]

  • 另外一种:第i个人选择一件物品:

    \[dp[i][j] += dp[i-1][j-1] * (sum[i] - (j-1)) \]

    \(dp[i-1][j-1]\)是前i-1个人一共选择了j-1件物品,\(sum[i] - (j-1)\)是当前第i个人还有多少个自己可以选择。两者是相乘的关系。(组合数学的内容)。

  • 最后统计一下答案就好了,细节在代码的注释里面。

代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int mod = 998244353;
const int N = 505;
int a[N], b[N];
int g[N];//表示 d >= x 的方案数目。
int sum[N];//sum[i] 表示 在d确定的时候 第i个人 可以拿的所有的行李数目
int f[N][N];//表示在某一个d的情况下,前i个人 拿了j件行李的方案数目。
void solve()
{
    int n, m;
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int j = 1; j <= m; j++) cin >> b[j];
    sort(a + 1, a + 1 + n);
    sort(b + 1, b + 1 + m);
    int ans = 0;

    for(int d = 0; d <= 500; d++)
    {

        for(int i = 0; i <= m; i++) sum[i] = 0;

        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(b[i] - a[j] >= d) sum[i]++;
            }
        }

        for(int i = 0; i <= m; i++)
        {
            for(int j = 0; j <= n; j++)
            {
                f[i][j] = 0;
            }
        }

        for(int i = 0; i <= m; i++)
        {
            f[i][0] = 1;
        }

        for(int i = 1; i <= m; i++)
        {
            for(int j = 1; j <= min(n, sum[i]); j++)
            {
                f[i][j] = (f[i][j] + f[i - 1][j]) % mod;

                f[i][j] = (f[i][j] + f[i - 1][j - 1] *  (sum[i] - (j - 1))) % mod;
            }
        }

        for(int j = 1; j <= n; j++)
        {
            g[d] = f[n][j] + g[d];
            g[d] %= mod;
        }
    }

    for(int d = 0; d + 1 <= 500; d++)
    {
        ans = (ans + (g[d] - g[d + 1]) * d) % mod;
        ans %= mod;
    }
    cout << ans << "\n";
}

signed main()
{
    solve();
    return 0;
}

提一下边界情况:
很明显,最后添加答案的时候,必须要存在至少一件物品被选择。所以统计\(g[d]\),也就是时间大于等于d的方案数的时候,直接从\(f[n][1]\)开始添加。
但是其实,就算从\(f[n][0]\)开始也是一样的,因为如果都用了\(f[n][0]\),后面的差分会把这种情况自动消除。不过在实际意义上就会不好理解。
以及,这个题目,用\(f[i][0]== 1\)赋初值很好理解,如果不想这样,就需要自己加特判手动更新\(f[i][1]\)。然后转移方程从j = 2开始就好了。

posted @ 2024-09-18 12:28  橘赴亦梦人ω  阅读(11)  评论(0编辑  收藏  举报