题解:P14364 [CSP-S 2025] 员工招聘 / employ(民间数据)
好像是正赛场切过的最牛的题。
考虑 dp。先考虑设
上述做法做不了的本质是,我们前面决策选了后面的哪个
我们加入一维
考虑转移:
- 当前的人来面试,且面试成功(
c_x > j 且s_i = 1 ):此时我们知道需要在这里填一个c_x>j 的人,但是我们不知道是谁。因此是
- 当前的人来面试,且面试失败(
c_x > j 且s_i = 0 ):此时j 应当增加1 ,同时因为来参加面试了所以这个人也得满足c_x > j ,k 也得增加1 。根据我们之前的分析,这时候还应当考虑计算所有被提前钦定的c_x = j+1 的贡献,因此我们枚举前面用了l 个c_x = j+1 ,这时候一共有k+1 个待填的空所以是\binom{k+1}{l} ,同时还要从所有c_x = j+1 的人里面选l 个出来排列,记人数为cnt_{j+1} ,则转移式为
- 当前的人玉玉了不来面试(
c_x \le j ):其实与上一种转移很像。不过由于他得满足c_x \le j 所以我们还得额外从已经不可能来的人里选一个幸运选手出来。这个其实也是容易直接得出的,记录pre_j 为c_x \le j 的人数,则我们有可能被选中的人数为pre_j - (i-k) ,其中i-k 为已经在前几天就被确定了的但是c_x \le j 的人数。
最后我们只需要枚举有几个人寄了即可,答案为
还有一个问题:转移要枚举
记得滚动数组。
赛后回忆随手写的丑陋的代码,细节与题解略有出入,仅供参考:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ull unsigned long long
#define pii pair<int,int>
#define fir first
#define sec second
#define chmin(a,b) a=min(a,b)
#define chmax(a,b) a=max(a,b)
#define pb push_back
const int inf=0x3f3f3f3f3f3f3f3f;
const int mod=998244353;
int n,m,dp[2][510][510],fac[510],C[510][510],P[510][510],ans;
string s;
int c[510],rm[510];
signed main()
{
cin>>n>>m>>s;
s=" "+s;
for(int i=1;i<=n;i++)
{
int x;
cin>>x;
c[x]++;
for(int j=0;j<x;j++)rm[j]++;
}
fac[0]=1;
for(int i=1;i<=n;i++)fac[i]=fac[i-1]*i%mod;
for(int i=0;i<=n;i++)
{
C[i][0]=1;
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
for(int j=0;j<=i;j++)P[i][j]=C[i][j]*fac[j]%mod;
}
dp[0][0][0]=1;
for(int i=1;i<=n;i++)
{
memset(dp[i&1],0,sizeof(dp[i&1]));
bool ps=s[i]-'0';
for(int j=0;j<i;j++)if(j<=n-m)
for(int k=0;k<i;k++)
{
int tx=c[j+1];
int tmp=dp[i&1^1][j][k];
if(!tmp)continue;
if(ps)(dp[i&1][j][k+1]+=tmp)%=mod;
int pr=(n-rm[j])-(i-1-k);
if(pr)for(int l=min(k,tx);~l;l--)(dp[i&1][j+1][k-l]+=tmp*pr%mod*C[k][l]%mod*P[tx][l]%mod)%=mod;
if(!ps)for(int l=min(k+1,tx);~l;l--)(dp[i&1][j+1][k+1-l]+=tmp%mod*C[k+1][l]%mod*P[tx][l]%mod)%=mod;
}
}
for(int i=0;i<=n-m;i++)(ans+=dp[n&1][i][rm[i]]*fac[rm[i]]%mod)%=mod;
cout<<ans<<endl;
return 0;
}