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;
}

批注:
把“具体是谁”这个信息丢掉,只保留“有多少个”。

posted @ 2026-09-21 09:12  灿若星尘  阅读(25)  评论(0)    收藏  举报