2024_CCPC网络赛I题

2024_CCPC网络赛I题

题目链接

思路

  • time为1s,n==200,可以n3做法。

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

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

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

    对于第i个人,可以选择的物品的坐标区间是[0,pos1],对于第i+1个人可以选择的物品的位置的区间:[0,pos2]

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

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

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

    dp[i][j]+=dp[i1][j]

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

    dp[i][j]+=dp[i1][j1](sum[i](j1))

    dp[i1][j1]是前i-1个人一共选择了j-1件物品,sum[i](j1)是当前第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 @   橘赴亦梦人ω  阅读(13)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示