23 年牛客提高组模拟赛 Day5 T3
给你一个长为
的数组 表示原数组 中以 结尾的 LIS 长度,问对于所有 ,原数组有多少种不同的可能
看到数据范围容易想到状压 dp ,赛事想了个比较朴素的 dp :设
发现我们没必要真把数填进去,而是可以确立他们的相对大小关系之后用组合数填数,也就是说我们把要填的数范围变成了
发现枚举子集是孬的,因此考虑顺着填答案,具体的,设
还有一个问题,怎么计算答案?因为我们并不知道第
第一种方法是在 dp 中再记录第
第二种方法是考虑容斥,设
这是一个显然的子集形式的二项式反演问题,考虑容斥减去没填的方案,即:
最终复杂度
code :
#include <bits/stdc++.h>
// #pragma GCC optimize(2)
#define pcn putchar('\n')
#define ll long long
#define LL __int128
#define pii pair<int, int>
#define pli pair<ll, int>
#define pil pair<int, ll>
#define pll pair<ll, ll>
#define MP make_pair
#define fi first
#define se second
#define gsize(x) ((int)(x).size())
#define Min(a, b) (a = min(a, b))
#define Max(a, b) (a = max(a, b))
#define For(i, j, k) for(int i = (j), END##i = (k); i <= END##i; ++ i)
#define For__(i, j, k) for(int i = (j), END##i = (k); i >= END##i; -- i)
#define Fore(i, j, k) for(int i = (j); i; i = (k))
using namespace std;
namespace IO {
template <typename T> T read(T &num){
num = 0; T f = 1; char c = ' '; while(c < 48 || c > 57) if((c = getchar()) == '-') f = -1;
while(c >= 48 && c <= 57) num = (num << 1) + (num << 3) + (c ^ 48), c = getchar();
return num *= f;
}
ll read(){
ll num = 0, f = 1; char c = ' '; while(c < 48 || c > 57) if((c = getchar()) == '-') f = -1;
while(c >= 48 && c <= 57) num = (num << 1) + (num << 3) + (c ^ 48), c = getchar();
return num * f;
}
template <typename T> void Write(T x){
if(x < 0) putchar('-'), x = -x;
if(x == 0){putchar('0'); return ;}
if(x > 9) Write(x / 10);
putchar('0' + x % 10); return ;
}
void putc(string s){ int len = s.size() - 1; For(i, 0, len) putchar(s[ i ]); }
template <typename T> void write(T x, string s = "\0"){ Write( x ), putc( s ); }
}
using namespace IO;
//mt19937_64 rnd(time(0));
//ll random(ll l, ll r){ return (rnd() % (r - l + 1)) + l; }
#ifdef LOCAL
template <typename T> void debug(T x, string s = "\0"){ write(x, s); }
#else
template <typename T> void debug(T x, string s = "\0"){}
#endif
/* ====================================== */
const int maxn = 25;
const int maxm = 3050;
const int maxS = (1 << 20) + 50;
const ll mod = 998244353;
int n, m; int a[ maxn ], maxs[ maxS ];
int C[ maxm ][ maxm ];
int dp[ 2 ][ maxn ][ maxS ]; // dp_{i,j,S} 表示填前 i 种数,第 i 种考虑到 j 位置,填数集合为 S 方案数
// 因为题解是从后往前考虑所以我写的也是
ll ans[ maxn ];
template<typename T> void add(T &x, T y){ x += y; if(x >= mod) x -= mod; }
void mian(){
read(n), read(m); For(i, 1, n) read(a[ i ]);
For(S, 0, (1 << n) - 1) For(i, 0, n - 1) if(S >> i & 1) Max(maxs[ S ], a[ i + 1 ]); // 记录集合 S 中数的最大值
int o = 0; dp[ o ][ n - 1 ][ 0 ] = 1;
For(i, 1, n){
For__(j, n - 1, 0)
For(S, 0, (1 << n) - 1){
int &nw = dp[ o ][ j ][ S ]; if(!nw) continue;
if(j) add(dp[ o ][ j - 1 ][ S ], nw);
else add(dp[ o ^ 1 ][ n - 1 ][ S ], nw);
if((S >> j & 1) == 0 && maxs[ S & ((1 << j) - 1) ] + 1 == a[ j + 1 ]){
if(j) add(dp[ o ][ j - 1 ][ S | (1 << j) ], nw);
else add(dp[ o ^ 1 ][ n - 1 ][ S | (1 << j) ], nw);
}
dp[ o ][ j ][ S ] = 0; // 注意清空
}
ans[ i ] = dp[ o ^ 1 ][ n - 1 ][ (1 << n) - 1 ]; o ^= 1;
// 记录答案,这里如果没有80行清空操作的话写 dp[ o ][ 0 ][ (1 << n) - 1 ] 也是对的
}
ll res = 0;
For(i, 1, n){
ll t = 0; For(j, 1, i){ t = (t + 1ll * ((i - j & 1) ? mod - 1 : 1) * C[ i ][ j ] % mod * ans[ j ]) % mod; }
res = (res + t * C[ m ][ i ]) % mod;
} // 容斥
write(res, "\n");
}
void init(){
}
void treatment(){
int up = 3005;
C[ 0 ][ 0 ] = 1; For(i, 1, up){ C[ i ][ 0 ] = 1; For(j, 1, i){ C[ i ][ j ] = C[ i - 1 ][ j ] + C[ i - 1 ][ j - 1 ]; if(C[ i ][ j ] >= mod) C[ i ][ j ] -= mod; } }
}
int main() {
#ifdef LOCAL
freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
#else
// freopen("data.in", "r", stdin);
// freopen("data.out", "w", stdout);
#endif
treatment();
int T = 1;
// read(T);
while(T --){
init();
mian();
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?