T1[计数类DP/转化]给出2个排列p,q,长度都是n,其中p完全给出,\(\exists pi=0 \Leftrightarrow i位置可以填任意[1,n]之间的数使得q构成排列\),问长度是n的01串S的个数,使得存在2*n的矩阵,满足\(a_{1,i}<a_{1,j}\Leftrightarrow pi<pj,a_{2,i}<a_{2,j}\Leftrightarrow qi<qj,si=0\Leftrightarrow a_{1,i}=a_{2,i}\)。(n<=100)
考场
没思路,发现枚举什么都会T飞,\(O(n!*2^n*n)\),然后发现对于qi=0的看起来就很随和,就蒙了一个\(2^n\),骗了5分
正解
套路性的思路转化,2个相对变化等价于只变化一个,所以sortp之后只需要考虑在a1升序下的满足条件,
偏序关系转化图论,发现1、2、3的条件使得矩形的任意上下点之间有边连接,定义\(ai>aj \Leftrightarrow edge(ai->aj)\),那么因为p顺序固定了,所以环(矛盾关系)方向固定了,所以矛盾当且仅当\(qi>qj,si=1,sj=0\).
抽象化s的含义,把10看成选或者不选,不合法序列就是\(\exists qi>qj,i和j都选择\),合法序列就是\(\forall i_{choose},j_{choose},qi<qj\),所以就是qi的上升子序列个数.
考虑没有空位只需要记录上一位最大,有就添加中间有多少已选择的未知,
\(f[i][j]:前i个数,ai是上升序列最后一位,有j个选择的qj=0的位置\)
\(f[i][j]+=f[k][l]*C(val[i-k],j-l)*C(chose[i-k],j-l)\),表示从k-i的区间中填补上l个上升子序列,可以选择的数值是在[qk,qi]之间没出现的,可以选择的位置是[k,i]之间0的个数。
\(ans=\sum f[n][l]*A(chose_n-l)\),表示剩下的空位随便选择,全排列。
点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define chu printf
#define rint register int
#define ll long long
#define ull unsigned long long
inline ll re() {
ll x = 0, h = 1;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
h = -1;
ch = getchar();
}
while (ch <= '9' && ch >= '0') {
x = (x << 1) + (x << 3) + (ch ^ 48);
ch = getchar();
}
return x * h;
}
const int N = 110;
const int mod = 998244353;
inline void upd(int& fg, int fp) {
fg += fp;
fg = (fg < 0) ? (fg + mod) : ((fg >= mod) ? (fg - mod) : fg);
}
int f[N][N], s[N], g[N], n, C[N][N];
int q[N], fac[N];
int chs[N];
struct node {
int a, b;
bool operator<(const node& U) const { return a < U.a; }
} p[N];
// f[i][j]:表示前i个数,有j个选择的不确定的位置的方案数
// s[i]:前i个位置有多少等着选择
// g[i]:前i个值有多少等着选择
int main() {
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
freopen("surprise.in", "r", stdin);
freopen("surprise.out", "w", stdout);
n = re();
for (rint i = 1; i <= n; ++i) p[i].a = re();
for (rint i = 1; i <= n; ++i) p[i].b = re();
sort(p + 1, p + 1 + n);
for (rint i = 1; i <= n; ++i) q[i] = p[i].b;
chs[++chs[0]] = 0;
for (rint i = 1; i <= n; ++i) {
// chu("q[%d]:%d\n",i,q[i]);
if (q[i] == 0)
s[i] = s[i - 1] + 1;
else
s[i] = s[i - 1];
if (q[i])
g[q[i]] = 1, chs[++chs[0]] = i;
}
s[n + 1] = s[n];
q[n + 1] = n + 1;
chs[++chs[0]] = n + 1;
for (rint i = 1; i <= n; ++i) g[i] = g[i - 1] + (!g[i]); //有的值不选择
C[0][0] = 1;
for (rint i = 1; i <= n; ++i) {
// chu("q[%d]:%d\n",i,q[i]);
C[i][0] = 1;
for (rint j = 1; j <= i; ++j) upd(C[i][j], (1ll * (C[i - 1][j] + C[i - 1][j - 1]) % mod));
}
fac[0] = fac[1] = 1;
for (rint i = 2; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % mod;
f[0][0] = 1;
// chu("C[]:%d\n",C[5][2]);
for (rint I = 2, i = chs[I]; I <= chs[0]; ++I, i = chs[I]) //枚举当前的有数的位置
{
for (rint j = 0; j <= s[i]; ++j) //枚举当前选择j未知数
{
// chu("upd:f[%d][%d]\n",i,j);
for (rint K = 1, k = chs[K]; K < I; ++K, k = chs[K]) //枚举从k位置转移
{
// chu("q[%d]:%d q[%d]:%d\n",i,q[i],k,q[k]);
if (q[i] < q[k])
continue;
for (rint l = 0; l <= min(j, s[k]); ++l) //枚举上一个状态选择了l未知数
{
//目前选了now个未知数
if (!f[k][l])
continue;
// chu("can from:%d %d:%d\n",k,l,f[k][l]);
int now = j - l;
if (s[i] - s[k] >= now && g[q[i] - 1] - g[q[k]] >= now)
upd(f[i][j], (1ll * f[k][l] * C[s[i] - s[k]][now] % mod *
C[g[q[i] - 1] - g[q[k]]][now] % mod) %
mod);
}
}
// chu("f[%d][%d]:%d * fac[%d]:%d\n",i,j,f[i][j],s[n]-j,fac[s[n]-j]);
}
}
int ans = 0;
for (rint i = 0; i <= s[n]; ++i) upd(ans, 1ll * f[n + 1][i] * fac[s[n] - i] % mod);
chu("%d", ans);
return 0;
}
/*
2
1 2
2 1
*/