AT_arc207_a Affinity for Artifacts
CSP-S T4 同款trick(好像是)
首先注意到若某个灯在第 \(i\) 次被点亮,那么它的贡献为 \(\max(0,a_i-i+1)\),可以提出 \(a_i\) 变成 \(a_i+\max(-a_i,-i+1)\),变一下号就可以得到答案条件式子:
已知项移到一边,有 \(X'=\sum a_i-X\),最终的式子:
然后有一个处理这类 \(\min\)/\(\max\) 匹配的trick,就是将他们拉到一个序列中从小到大排序,记录那些是左边部分那些是右边部分,左右匹配,贡献只与在序列中靠后的点有关,设计DP状态 \(f_{i,j,k,s}\) 表示考虑前 \(i\) 个点,有 \(j\) 个左部点和 \(k\) 个右部点未匹配,当前的贡献总和为 \(s\) 的方案数,转移考虑当前点是左部还是右部,有向前匹配和留下来向后匹配两种决策。由于 \(s\) 的范围被限定在了 \(n^2\) 范围,所以这个式子的时空复杂度均为 \(O(n^5)\),过不去这道题。
这个时候发现对于固定的 \(i\) 和 \(j\),如果预处理出前 \(i\) 个点中左部点和右部点的数量,就可以通过 \(j\) 来推出固定的 \(k\),于是DP状态改为 \(f_{i,j,s}\),以当前点为左部点为例,转移如下:
- 与前面未匹配的右部点匹配,由于当前状态为 \(k\),则 \(i-1\) 状态为 \(k+1\),匹配时有 \(k+1\) 种匹配方式,有如下转移:
- 与后面的点匹配,则存在 \(j\) 里:
右部点的转移实际同理,也实际简单。
时间复杂度为 \(O(n^4)\),喵哉喵哉。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll p = 998244353;
ll n,x,a[410],f[210][210][5010],ctl[410],ctr[410],sum,ans;
pair<ll,ll> d[410];
inline int read()
{int x = 0;char ch = getchar();while(ch<'0'||ch>'9') ch = getchar();while(ch>='0'&&ch<='9'){x = (x<<1)+(x<<3)+(ch^48);ch = getchar();}return x;
}
int main()
{n = read(); x = read();for (int i = 1;i <= n;i++) a[i] = read(),sum += a[i];for (int i = 1;i <= n;i++){d[i*2-1] = {a[i],0}; d[i*2] = {i-1,1};}sort(d+1,d+n*2+1,greater<pair<ll,ll>>()); x = sum-x;for (int i = 1;i <= n*2;i++){ctl[i] = ctl[i-1]; ctr[i] = ctr[i-1];if (d[i].second) ctr[i]++;else ctl[i]++;}f[0][0][0] = 1;for (int i = 1;i <= n*2;i++)for (ll j = 0;j <= ctl[i];j++){ll k = ctr[i]-(ctl[i]-j);if (k < 0) continue;// cout << i << " " << j << " " << k << "\n";for (int s = 0;s <= n*(n-1)/2;s++){if (d[i].second){if (s >= d[i].first) f[i][j][s] = (f[i][j][s]+(j+1)*f[i-1][j+1][s-d[i].first]%p)%p;if (k) f[i][j][s] = (f[i][j][s]+f[i-1][j][s])%p;}else{if (s >= d[i].first) f[i][j][s] = (f[i][j][s]+(k+1)*f[i-1][j][s-d[i].first]%p)%p;if (j) f[i][j][s] = (f[i][j][s]+f[i-1][j-1][s])%p;}}}for (int i = max(0ll,x);i <= n*(n-1)/2;i++) ans = (ans+f[n<<1][0][i])%p;cout << ans << "\n";return 0;
}