AT_arc207_a Affinity for Artifacts 解题总结
\(\small \mathsf{date:20260920}\)
题面:
魔法师小 Snuke 拥有 \(N\) 个编号从 \(1\) 到 \(N\) 的魔法灯。点亮第 \(i\) 个灯的成本为 \(a_i\)。初始时,所有灯都处于熄灭状态。
小 Snuke 拥有一个名为 \(MP\) 的整数值,初始值为 \(X\)。当他点亮一个成本为 \(x\) 的灯时,\(MP\) 会减少 \(X\)。每当小 Snuke 点亮一个灯后,所有成本不为零的灯的成本都会减少 \(1\)。
点亮灯的顺序共有 \(N!\) 种可能,请求出其中不会使 \(MP\) 变为负数(即 \(MP\) 始终 \(≥0\))的方案数,答案对 \(998244353\) 取模。
第一步:处理条件
设重排后的序列为 \(b\)。
则显然有 \(\sum_{i=1}^{n} max(x_i-(i-1),0)\le MP\)。
每一项加上 \(min(x_i,i-1)\),得到 \(\sum_{i=1}^{n} x_i\le MP+min(x_i,i-1)\)。
因此,条件转化为 \(\sum_{i=1}^{n} min(x_i,i-1)\ge T, ~T=\sum x-MP\)。而 \(T\) 是一个可以算出来的常数。
特别地,\(\sum_{i=1}^{n}min(x_i,i-1)\le \sum_{i=0}^{n-1} ~i=\frac{n(n-1)}{2}\)。
所以当 \(T\ge \frac{n(n-1)}{2}\) 时无解。
同理,由于原式大于等于零,\(T\le 0\) 时方案数为 \(n!\),即任意排列均满足条件。
第二步:构造模型
考虑怎么将满足这个条件抽象成模型:\(\sum_{i=1}^{n} min(x_i,i-1)\ge T\)。
发现可以将每一项拆成两半:\(x_i\) 和 \(i-1\),它们分别对应序列 \(x\) 和 \(0,1,...,i-1\)。
将两个序列的元素两两匹配,贡献为两者之中的较小者的值。
要求总贡献 \(\ge T\),求合法匹配方案数。
第三步:核心计数
遇到这样的匹配问题,有一种很厉害的 \(Trick\) 可以处理:
将左右两个序列合并,然后从大到小排序。
依次处理合并序列的每个元素,对于每个元素可以有两种操作:
- 将它“挂起”,暂时不进行匹配,并加入“挂起”序列中。
- 与“挂起”的某个来自另一个序列的元素(如果有)进行匹配,贡献是当前元素的值(因为已经降序,这样就可以将所有“挂起”的同种元素视为等价的)。
这样,我们就可以不再关注当前已经选了哪些元素,只需要记录当前处理的元素数和挂起的两种元素数量即可。
计数问题的一个 \(nature\) :将计数时对“已经排列的元素情况”的依赖转化为“已经排列并具有某种性质的元素个数”的依赖,即将不同的具体情况等价。
现在进行动态规划设计:
设 \(dp_{i,j,k}\) 为处理到合并序列的第 \(i\) 个元素,挂起了 \(j\) 个来自左边的元素,总贡献为 \(k\) 的方案数。
因为右边元素挂起数 \(ri\) 可以用 \(j-l_i+r_i\) (\(l_i\):前 \(i\) 个元素中有多少来自左边, \(r_i\) 同理)算出,因此无需单开一维。
枚举 \(i,j,k\),当
- 第 \(i\) 个元素来自左边:
- 挂起:\(dp_{i+1,j+1,k}+=dp_{i,j,k}\)
- 匹配:\(dp_{i+1,j,k+nd_i.val}+=dp_{i,j,k}\times ri\)
- 第 \(i\) 个元素来自右边:
- 挂起:\(dp_{i+1,j,k}+=dp_{i,j,k}\)
- 匹配:\(dp_{i+1,j-1,k+nd_i.val}+=dp_{i,j,k}\times j\)
这便是完整的状态转移方程。
第四步:编写代码
至此,这道题就差不多做完了。核心是条件的转化和两侧匹配的计数\(Trick\)。
代码如下:
//AT_arc207_a
#include<bits/stdc++.h>
#define int long long
#define X (n-1)*n/2
using namespace std;
const int mod=998244353;
int n,mp,suma,a[105],dp[2][105][5005],fac[105]={1},l[205],r[205];
struct node{
int val,id;//id=0:left,id=1:right
};
bool cmp(node a,node b)
{
return a.val>b.val;
}
signed main()
{
cin>>n>>mp;
for(int i=0;i<n;i++)
{
cin>>a[i];
suma+=a[i];
}
for(int i=1;i<=101;i++)fac[i]=i*fac[i-1]%mod;
if(suma-mp<0)
{
cout<<fac[n];
return 0;
}
if(suma-mp>X)
{
cout<<0;
return 0;
}
int tgt=suma-mp,now=0;
vector<node>nd;
for(int i=0;i<n;i++)
{
nd.push_back({a[i],0});
nd.push_back({i,1});
}
sort(nd.begin(),nd.end(),cmp);
for(int i=0;i<nd.size();i++)
{
l[i+1]=l[i]+(nd[i].id==0);
r[i+1]=r[i]+(nd[i].id==1);
}
dp[0][0][0]=1;
for(int i=0;i<nd.size();i++)
{
memset(dp[now^1],0,sizeof(dp[now^1]));
for(int j=0;j<=l[i];j++)
{
for(int k=0;k<=X;k++)
{
if(nd[i].id==0)//left
{
int ri=j-l[i]+r[i];
if(k+nd[i].val<=X)dp[now^1][j][k+nd[i].val]=(dp[now^1][j][k+nd[i].val]+(dp[now][j][k]*ri%mod))%mod;//不挂起,匹配
dp[now^1][j+1][k]=(dp[now^1][j+1][k]+dp[now][j][k])%mod;
}
else
{
dp[now^1][j][k]=(dp[now^1][j][k]+dp[now][j][k])%mod;//挂起
if((k+nd[i].val<=X)&&j>0)dp[now^1][j-1][k+nd[i].val]=(dp[now^1][j-1][k+nd[i].val]+dp[now][j][k]*j%mod)%mod;
}
}
}
now^=1;
}
int ans=0;
for(int i=tgt;i<=X;i++)
{
ans=(ans+dp[now][0][i])%mod;
}
cout<<ans;
return 0;
}
批注:
把“具体是谁”这个信息丢掉,只保留“有多少个”。

浙公网安备 33010602011771号