Live2D Demo

Codeforces 1671 F Permutation Counting 题解

题目链接

\(p_i>p_{i+1}\)的位置个数称为间隔数

首先想到一个暴力做法。从小到大挨个添加1-n中的每个数,注意到添加数i时,只能添加到当前序列的最后11个位置中,否则逆序对数就会超。令\(dp_{i,j,p,msk}\)表示当前添加到数i,当前逆序对数为j,间隔数为p,msk是一个集合表示当前序列最后11个位置中哪些满足\(p_i>p_{i+1}\)。转移比较简单。但是这个dp的第一维是n,但n很大。矩阵快速幂也是不行的,后面的维度也很大。

我们考虑把一个长度为n的排列分段。令\(mx_i\)表示\(max_{j=1}^i p_j\)。我们找到从左往右第一个满足\(mx_i=i\)的位置,也就是第一个满足\(p_1\cdots p_i\)是一个排列的位置,然后把1-i分为一段,数组剩下的部分统一减i,然后不断进行同样的分段操作直到序列用完。发现分出的每一段都是一个"小的排列",且左侧的一个小排列中的所有\(p_i\)都小于右侧任意一个小排列中的\(p_i\)。注意到两个段之间不会贡献任何的逆序对数或间隔数,所以排列p的逆序对数和间隔数等于每一小段内部的逆序对数和间隔数之和。

一个小段的长度如果>12,那它的逆序对数就>11,所以此时的p肯定不合法。证明:令小段为一个1-k的排列\(b_1\cdots b_k\),其前缀max为\(c_1\cdots c_k\)。对于任意\(1\le i<k\),满足\(b_i<c_i\)。我们从左往右一个个加入\(b_i\),当加入到i(\(1\le i <k\))时,如果还有满足\(x<b_i\)的x没被加入,那肯定能至少形成一个还没被统计的逆序对;如果不存在这样的x,那肯定存在一个j满足\(j<i,b_j>b_i\)且加入j时有不止一个这样的x,我们现在加入i只是消耗了加入j时产生的一个逆序对而已。综上,总的逆序对个数一定会\(\ge k-1\)。因此我们可以用一个简单的背包算出数组\(f_{i,j,p}\)表示长度为i,逆序对数为j,间隔数为p的小段个数。为了保证对于任意\(1\le i<k\)满足\(b_i<c_i\),dp的时候需要状压一下。

算出f数组之后为了得到n的答案,可能会想到多项式快速幂什么的,其实根本不需要多项式技巧。注意到小段的长度\(>1\)时,逆序对数和间隔数都不为0;而小段长度为1时这两个值都为0。所以长度>1的小段个数\(\le 11\),再做一次背包,然后用插板法把其他长度为1的小段插进去即可。

没仔细算时间复杂度,反正不高。

点击查看代码
#include <bits/stdc++.h>

#define rep(i,n) for(int i=0;i<n;++i)
#define repn(i,n) for(int i=1;i<=n;++i)
#define LL long long
#define pii pair <int,int>
#define pdd pair <double,double>
#define fi first
#define se second
#define mpr make_pair
#define pb push_back

void fileio()
{
  #ifdef LGS
  freopen("in.txt","r",stdin);
  freopen("out.txt","w",stdout);
  #endif
}
void termin()
{
  #ifdef LGS
  std::cout<<"\n\nEXECUTION TERMINATED";
  #endif
  exit(0);
}

using namespace std;

const LL MOD=998244353;

LL qpow(LL x,LL a)
{
	LL res=x,ret=1;
	while(a>0)
	{
		if(a&1) (ret*=res)%=MOD;
		a>>=1;
		(res*=res)%=MOD;
	}
	return ret;
}

LL t,n,k,x,dp[4100][20][20][20];//dp[msk][k][x][lst]
LL dp2[20][30][20][20];//dp2[cnt][lensum][k][x]

LL rinv[100];
LL C(LL nn,LL mm)
{
  LL ret=1;for(LL i=nn;i>=nn-mm+1;--i) (ret*=i)%=MOD;
  repn(i,mm) (ret*=rinv[i])%=MOD;
  return ret;
}

int main()
{
  fileio();

  rep(i,95) rinv[i]=qpow(i,MOD-2);
  LL lim=12;
  dp[0][0][0][0]=1;
  rep(msk,1<<lim) rep(kk,lim) rep(xx,lim) rep(lst,lim) if(dp[msk][kk][xx][lst])
  {
    LL sz=__builtin_popcount(msk);
    if(msk==(1<<sz)-1&&msk>0) continue;
    LL val=dp[msk][kk][xx][lst],addk=0;
    for(int nxt=lim-1;nxt>=0;--nxt)
    {
      if(msk&(1<<nxt)) ++addk;
      else if(kk+addk<=11) (dp[msk|(1<<nxt)][kk+addk][xx+(int)(lst>nxt)][nxt]+=val)%=MOD;
    }
  }
  rep(i,1<<lim) rep(kk,lim) rep(xx,lim) repn(lst,lim) (dp[i][kk][xx][0]+=dp[i][kk][xx][lst])%=MOD;
  dp2[0][0][0][0]=1;
  rep(i,lim) rep(j,25) rep(kk,lim) rep(xx,lim) if(dp2[i][j][kk][xx])
  {
    LL val=dp2[i][j][kk][xx];
    for(int nxtlen=2;nxtlen<=lim&&j+nxtlen<=24;++nxtlen) rep(kkk,lim-kk) rep(xxx,lim-xx)
    {
      LL msk=(1<<nxtlen)-1;
      if(dp[msk][kkk][xxx][0]==0) continue;
      (dp2[i+1][j+nxtlen][kk+kkk][xx+xxx]+=val*dp[msk][kkk][xxx][0])%=MOD;
    }
  }

  cin>>t;
  rep(tn,t)
  {
    cin>>n>>k>>x;
    LL ans=0;
    rep(cnt,k+1) for(int sz=cnt+cnt;sz<=24;++sz)
    {
      LL val=dp2[cnt][sz][k][x];
      if(val==0||sz>n) continue;
      LL tot=n-sz,spas=cnt+1;
      LL mul=C(tot+spas-1,spas-1);
      (ans+=val*mul)%=MOD;
    }
    cout<<ans<<endl;
  }

  termin();
}
posted @ 2023-01-08 21:42  LegendStane  阅读(95)  评论(0)    收藏  举报