洛谷题目传送门
8pts
n!枚举排列,然后判断即可
44pts
首先考虑什么样的序列是符合条件的
题目提示启发我们,可以考虑每个元素的移动
因为总交换次数是要达到下界,所以每一次交换都必须是有益的
考虑如果排列第i个位置是p[i]
若
p
[
i
]
=
i
p[i]=i
p[i]=i,则这位置不能被交换
若
p
[
i
]
<
i
p[i]<i
p[i]<i,则这个数需要往左移动,且不能向右移动
那么不能存在一个j满足,
j
>
i
,
p
[
j
]
<
p
[
i
]
j>i,p[j]<p[i]
j>i,p[j]<p[i],否则这两个位置一定会交换一次,那么
p
[
i
]
p[i]
p[i]就向相反方向移动了,答案一定会变大,即
i
i
i右边的数都比
p
[
i
]
p[i]
p[i]大
若
p
[
i
]
>
i
p[i]>i
p[i]>i,类似的可以得到i左边的数都比
p
[
i
]
p[i]
p[i]小
且这三个限制和原限制是等价的
直接状压选了那些数字即可
字典序的限制可以类似数位dp去搞
复杂度
O
(
2
n
n
)
O(2^nn)
O(2nn)
#include<bits/stdc++.h>
using namespace std;
const int N = 6e5+7;
typedef long long LL;
const int mod = 998244353;
int n,a[N];
LL f[1<<20][2];
void solve()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int m=(1<<n);
memset(f,0,sizeof(f));
f[0][1]=1;
for(int s=0;s<m-1;s++)
{
int p=0;
int a1=0,a2=n+1;
for(int i=1;i<=n;i++)
if((s>>(i-1))&1)
{
p++;
a1=max(a1,i);
}
else a2=min(a2,i);
p++;
for(int limit=0;limit<=1;limit++)
{
int up=(limit?a[p]:1);
for(int i=up;i<=n;i++)
{
int k=(limit&&(i==up));
if(!((s>>(i-1))&1))
{
if(i>=p)
{
if(a1<i)
f[s+(1<<(i-1))][k]=(f[s+(1<<(i-1))][k]+f[s][limit])%mod;
}
else
{
if(i<=a2)
f[s+(1<<(i-1))][k]=(f[s+(1<<(i-1))][k]+f[s][limit])%mod;
}
}
}
}
}
printf("%lld\n",f[m-1][0]);
}
int main()
{
int T;
cin>>T;
while(T--)
{
solve();
}
return 0;
}
80pts
一个重要的结论:
不考虑字典序限制
题目所求等价于求有多少个排列,满足排列中不存在长度大于等于3的下降子序列
必要性:
即若该排列合法,则不存在长度大于等于3的下降子序列
若不然,则存在三个数
i
,
j
,
k
i,j,k
i,j,k满足,
i
<
j
<
k
,
p
[
i
]
>
p
[
j
]
>
p
[
k
]
i<j<k,p[i]>p[j]>p[k]
i<j<k,p[i]>p[j]>p[k]
若
j
<
p
[
j
]
j<p[j]
j<p[j],由上文可知,j左边的数都比j小,与
p
[
i
]
>
p
[
i
]
p[i]>p[i]
p[i]>p[i]矛盾
若
j
>
p
[
j
]
j>p[j]
j>p[j],则j右边的数都比j大,与
p
[
j
]
>
p
[
k
]
p[j]>p[k]
p[j]>p[k]矛盾
所以矛盾,即原命题成立
充分性
即若不存在
p
[
i
]
>
p
[
j
]
>
p
[
k
]
p[i]>p[j]>p[k]
p[i]>p[j]>p[k],则性质成立
考虑一次交换,交换了
p
[
i
]
,
p
[
i
+
1
]
p[i],p[i+1]
p[i],p[i+1]
则由题可知,
p
[
i
]
>
p
[
i
+
1
]
p[i]>p[i+1]
p[i]>p[i+1]
又因为没有长度大于等于3的下降子序列,所以i左边的数都小于
p
[
i
]
p[i]
p[i]
那么就有
p
[
1
…
…
i
−
1
]
p[1……i-1]
p[1……i−1]和
p
[
i
+
1
]
p[i+1]
p[i+1]总共i个数比
p
[
i
]
p[i]
p[i]小,即
p
[
i
]
>
i
p[i]>i
p[i]>i
类似的我们可以得到
p
[
i
+
1
]
<
i
+
1
p[i+1]<i+1
p[i+1]<i+1,那么这次交换对最终排列而言是合法的
所以命题成立
即题目等价于求有多少的排列,满足排列中不存在长度大于等于3的下降子序列
考虑dp
设
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示有多少个长度为i的排列,满足第一个元素是j,且不存在长度大于等于3的下降子序列,则由定义知
j
≤
i
j\leq i
j≤i
考虑第二个数填k
若
j
<
=
k
j<=k
j<=k,则加上dp[i-1][k],这里之所以能等于j,而不会重复,是因为虽然整个序列是一个排列,但是每一个子段并不是一个排列,而我们之所以能转移,是因为我们只考虑元素的相对关系,即离散化之后的值,那么长度为i的数列,长度为i-1的数列,离散化之后有值相等是显然可以的
若
1
<
k
<
j
1<k<j
1<k<j,那么一定会存在一个
j
,
k
,
1
j,k,1
j,k,1的不合法,下降子序列,因此贡献为0
若
k
=
=
1
k==1
k==1
贡献显然并不是
d
p
[
i
−
1
]
[
1
]
dp[i-1][1]
dp[i−1][1]
首先1~j-1一定是升序排列的,否则就会存在不合法的下降子序列
若不合法,则存在
x
<
y
,
j
>
p
[
x
]
>
p
[
y
]
>
1
x<y,j>p[x]>p[y]>1
x<y,j>p[x]>p[y]>1
考虑一个映射:
把x移到x+1的位置上,特别的让j-1移到1的位置上
则新序列和原序列是一一对应的,即是一个双双射
且新数列中存在
(
j
−
1
,
p
[
x
]
−
1
,
p
[
y
]
−
1
)
(j-1,p[x]-1,p[y]-1)
(j−1,p[x]−1,p[y]−1)的下降子序列
所以若填的k等于1,则等价于不存在以j-1开始的长度为3的下降子序列,即dp[i-1][j-1]
综合三种情况
d
p
[
i
]
[
j
]
=
∑
k
=
j
−
1
i
−
1
d
p
[
i
−
1
]
[
k
]
dp[i][j]=\sum_{k=j-1}^{i-1}dp[i-1][k]
dp[i][j]=∑k=j−1i−1dp[i−1][k]
考虑字典序的限制
枚举与给定排列的最长公共前缀i
设前缀最大值为x,满足
p
[
j
]
<
=
x
,
j
>
=
i
p[j]<=x,j>=i
p[j]<=x,j>=i的j的个数为s
那么离散化之后,x就是s
又因为有字典序的限制,所以填的数字应该>p[i],也就是说,当前位置可以填的最小的数是s+1,最大的是n-i+1,当然是离散化之后的
即答案是
A
n
s
=
∑
i
=
1
n
−
1
∑
k
=
s
+
1
n
−
i
+
1
d
p
[
n
−
i
+
1
]
[
k
]
Ans=\sum_{i=1}^{n-1}\sum_{k=s+1}^{n-i+1}dp[n-i+1][k]
Ans=i=1∑n−1k=s+1∑n−i+1dp[n−i+1][k]
预处理dp值,通过前缀和优化,复杂度
O
(
n
2
)
O(n^2)
O(n2)
因为代码很简单,所以略
100pts
观察到答案实际上若干dp数组的后缀和
设
s
(
n
,
m
)
=
∑
i
=
m
n
d
p
[
n
]
[
i
]
s(n,m)=\sum_{i=m}^ndp[n][i]
s(n,m)=∑i=mndp[n][i],其中
m
≤
n
m\leq n
m≤n
则
s
(
n
,
m
)
=
∑
i
=
m
+
1
n
d
p
[
n
]
[
i
]
+
d
p
[
n
]
[
m
]
s(n,m)=\sum_{i=m+1}^ndp[n][i]+dp[n][m]
s(n,m)=i=m+1∑ndp[n][i]+dp[n][m]
=
∑
i
=
m
+
1
n
d
p
[
n
]
[
i
]
+
∑
i
=
m
−
1
n
−
1
d
p
[
n
−
1
]
[
i
]
=\sum_{i=m+1}^ndp[n][i]+\sum_{i=m-1}^{n-1}dp[n-1][i]
=i=m+1∑ndp[n][i]+i=m−1∑n−1dp[n−1][i]
=
s
(
n
,
m
+
1
)
+
s
(
n
−
1
,
m
−
1
)
=s(n,m+1)+s(n-1,m-1)
=s(n,m+1)+s(n−1,m−1)
考虑组合意义,
在平面直角坐标系上从
(
0
,
0
)
(0,0)
(0,0)走到
(
n
,
m
)
(n,m)
(n,m)每次向下走一步或者向右上走一步的方案数
并且不能超过
y
=
x
y=x
y=x这条线和x轴之下
首先仅通过这两种走法,随便走,不可能越过
y
=
x
y=x
y=x,但有可能越过x轴
因为只有第二种会向右走一步,所以一共有n次第二种操作,也可以得到有n-m次第一种操作
为了让这两种操作相似,不妨把想下,改为向右
则原先走到(n,m),新的操作就会走到(2n-m,m),因为多向右走了n-m次
即,每次向右上或者右下走一步,走到(2n-m,m)的方案数,且不能超过x轴
这和原问题也是双射
因为不能超过x轴,所以每个前缀里,向上走的次数要大于等于向下走的次数
这就是卡特兰数的变种问题,方案数为
s
(
n
,
m
)
=
C
2
n
−
m
n
−
m
−
C
2
n
−
m
n
−
m
−
1
s(n,m)=C_{2n-m}^{n-m}-C_{2n-m}^{n-m-1}
s(n,m)=C2n−mn−m−C2n−mn−m−1
A
n
s
=
∑
i
=
1
n
−
1
s
(
n
−
i
+
1
,
s
+
1
)
Ans=\sum_{i=1}^{n-1}s(n-i+1,s+1)
Ans=i=1∑n−1s(n−i+1,s+1)
预处理组合数,用树状数组求s
复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)
#include<bits/stdc++.h>
using namespace std;
const int N = 6e5+7;
typedef long long LL;
const int mod = 998244353;
int c[N];
int n;
void add(int x,int v)
{
for(int i=x;i<=n;i+=i&-i)
c[i]+=v;
}
int ask(int x)
{
int res=0;
for(int i=x;i;i-=i&-i)
res+=c[i];
return res;
}
int a[N];
LL fac[N*2],inv[N*2];
LL M = N*2-2;
LL Pow(LL a,LL b)
{
LL res=1;
while(b)
{
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int pre[N],suf[N];
int leq[N];
LL C(int n,int m)
{
if(n<m) return 0;
return (LL)fac[n]*inv[m]%mod*inv[n-m]%mod;
}
LL S(int n,int m)
{
return (C(2*n-m,n-m)-C(2*n-m,n-m-1)+mod)%mod;
}
void solve()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
pre[i]=max(pre[i-1],a[i]);
suf[n+1]=n+1;
for(int i=n;i>=1;i--)
suf[i]=min(suf[i+1],a[i]);
for(int i=n;i>=1;i--)
{
add(a[i],1);
leq[i]=ask(pre[i]);
}
for(int i=1;i<=n;i++)
add(a[i],-1);
int Max=0;
LL ans=0;
for(int i=1;i<=n;i++)
{
if(Max>suf[i]) break;
if(leq[i]+1<=n-i+1)
ans=(ans+S(n-i+1,leq[i]+1))%mod;
if(a[i]<pre[i]) Max=max(Max,a[i]);
}
printf("%lld\n",ans);
}
int main()
{
int T;
cin>>T;
fac[0]=1;
for(int i=1;i<=M;i++)
fac[i]=(LL)fac[i-1]*i%mod;
inv[M]=Pow(fac[M],mod-2);
for(int i=M-1;i>=0;i--)
inv[i]=(LL)inv[i+1]*(i+1)%mod;
while(T--)
{
solve();
}
return 0;
}