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-1][j] \]
对于\(dp[i][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开始就好了。