do_while_true

一言(ヒトコト)

「题解」Codeforces 1671F Permutation Counting

计算长度为 \(n\) 的排列数,使得有 \(k\) 个逆序对,\(x\) 个下降对(对于一个 \(i\) 如果满足 \(p_i>p_{i+1}\) 则为一个下降对)。对 \(998244353\) 取模。

\(1 \le n \le 998244352,1 \le k \le 11,1 \le x \le 11\)

因为觉得特别有拓展意义,于是写的稍微意识流了一点!

计数问题的常见想法是,现在有很多方案,要以某种方式划分等价类,对于每个等价类先计算单独一个的方案数是多少,然后再计算这个等价类有多少种情况。

即,通过划分等价类的方式,把一个计数问题变成了对于每个等价类的计数问题。

对于这个题而言,观察一个排列的逆序对以及下降对,如果对于一个位置 \(i\) 满足其前缀 \(\max\) \(mx_i=i\),也就是前 \(i\) 个位置填着 \(1\sim i\) 这些数,从这个地方划分,前半部分和后半部分之间不会产生逆序对和下降对。例如 2 1 4 3,可以分成前面的 2 1 和后面的 4 3,考虑每个段的相对大小关系,2 1 4 3 的逆序对/下降对数相当于两个 2 1 的逆序对/下降对数的和。

这里的启发是,按照前缀 \(mx_i>i\) 来划分等价类,称之为“极长非降序段”。例如:2 1 4 3 不是,因为可以分成 2 14 3;而 2 4 3 1 是,除了最后一个位置以外其他所有位置都满足 \(mx_i>i\)

并且注意到,有些连续的 \(a_i=mx_i=i\) 的位置可以不用考虑,只考虑 \(mx_i>i\) 的那些极长连续段,它们的相对大小关系的不同会产生逆序对以及下降对的贡献,最后的方案数就是有一些小球放进一些盒子里面的方案数了。例如:2 1 3 4 6 51 3 2 4 6 5 是等价的,前者会产生贡献的是 2 16 5,后者会产生贡献的是 3 26 5,都是两个相对大小关系为 2 1 的段,他们是一个等价类,对于这个等价类的方案相当于在两个捆绑在一起的组之间以及两边的 \(3\) 个空隙里面放入 \(6-4=2\) 个不会产生贡献的 \(a_i=i\) 的数。

那么做法就很明朗了。

首先对极长非降序段的相对大小关系来 dp,注意之类要算上最后结尾的位置会 \(mx_{end}=end\),经过打表发现这样的段最长超过 \(12\) 就会爆掉 \(k\leq 11\) 的限制,那么只对长度 \(\leq 12\) 的 dp 即可。

对于每个长度 \(n\) 来说,\(f_{S,t1,t2,i}\) 表示已经选的集合为 \(S\),有 \(t1\) 个逆序对,有 \(t2\) 个下降对,最后结尾为 \(i\),每次找一个合法的可填的转移即可,时间复杂度是 \(\mathcal{O}(2^{12}\times 11^2\times 12^3)\)

for(n=2;n<=12;n++){
	for(int i=0;i<=all(n);i++)
		for(int j=0;j<=11;j++)
			for(int k=0;k<=11;k++)
				for(int l=0;l<=n;l++)
					f[i][j][k][l]=0;
	f[0][0][0][0]=1;
	for(int o=1;o<=n;o++){
		for(int S=0;S<=all(n);S++)
		for(int c1=0;c1<=11;c1++)
		for(int c2=0;c2<=11;c2++)
		for(int lst=0;lst<=n;lst++)
		if(f[S][c1][c2][lst]&&__builtin_popcount(S)+1==o){
			int mx=0;
			for(int i=1;i<=n;i++)
				if((S>>(i-1))&1)
					mx=i;
			for(int j=1;j<=n;j++)
				if(!((S>>(j-1))&1)){
					if(max(mx,j)>o||o==n){
						int t1=c1+__builtin_popcount(S&(all(n)-all(j)));
						int t2=c2+(lst>j);
						if(t1<=11&&t2<=11)cadd(f[S|(1<<(j-1))][t1][t2][j],f[S][c1][c2][lst]);
					}
				}
		}
	}
	for(int i=1;i<=11;i++)
		for(int j=1;j<=11;j++)
			for(int k=1;k<=n;k++)
				cadd(s[n][i][j],f[all(n)][i][j][k]);
}

然后将这些极长非降序段组合起来即可,由于最后算插入 \(a_i=i\) 的不会产生影响的数的时候,要知道有几个空隙可以插,要插几个数,所以将这些段组合起来的过程中需要记录用了几个段,用了几个数(这里实际上也在划分等价类,然后最后算每个等价类的方案数)。这里的 \(F_{a,b,c,d}\) 代表选了 \(a\) 个段,用了 \(b\) 个数,有 \(c\) 个逆序对,\(d\) 个下降对,组合起来的过程实际上是一个多维的背包。由于一个段可以在一个等价类中出现多次,所以这里先循环背包容量,再循环物品种类。这里 \(b\) 的上限都是 \(22\)\(a\) 则是 \(11\)(最坏情况是 \(11\)2 1

复杂度是 \(\mathcal{O}(22\times 11^5\times 12)\)

F[0][0][0][0]=1;
for(int a=0;a<=11;a++)
	for(int b=0;b<=22;b++)
		for(int c=0;c<=11;c++)
			for(int d=0;d<=11;d++)
				if(F[a][b][c][d])
					for(n=2;n<=12;n++)
						for(int i=1;i<=11;i++)
							for(int j=1;j<=11;j++)
								if(c+i<=11&&d+j<=11)
									cadd(F[a+1][b+n][c+i][d+j],1ll*F[a][b][c][d]*s[n][i][j]%mod);

最后每次处理询问的时候枚举用几个段,用了几个数,然后乘上一个组合数即可。

完整代码:

//orz meyi
#include<cstdio>
#include<vector>
#include<cstring>
#include<string>
#include<iostream>
#include<algorithm>
#include<ctime>
#include<map>
#define pb emplace_back
#define mp std::make_pair
#define fi first
#define se second
#define dbg(x) cerr<<"In Line "<< __LINE__<<" the "<<#x<<" = "<<x<<'\n';
#define dpi(x,y) cerr<<"In Line "<<__LINE__<<" the "<<#x<<" = "<<x<<" ; "<<"the "<<#y<<" = "<<y<<'\n';
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int>pii;
typedef pair<ll,int>pli;
typedef pair<ll,ll>pll;
typedef vector<int>vi;
typedef vector<ll>vll;
typedef vector<pii>vpii;
template<typename T>T cmax(T &x, T y){return x=x>y?x:y;}
template<typename T>T cmin(T &x, T y){return x=x<y?x:y;}
template<typename T>
T &read(T &r){
	r=0;bool w=0;char ch=getchar();
	while(ch<'0'||ch>'9')w=ch=='-'?1:0,ch=getchar();
	while(ch>='0'&&ch<='9')r=r*10+(ch^48),ch=getchar();
	return r=w?-r:r;
}
template<typename T1,typename... T2>
void read(T1 &x, T2& ...y){ read(x); read(y...); }
const int N=100010;
const int mod=998244353;
inline void cadd(int &x,int y){x=(x+y>=mod)?(x+y-mod):(x+y);}
int n=12,f[(1<<12)+10][12][12][13];
int s[13][12][12],F[13][23][12][12];
int inv[23];
int all(int x){
	return (1<<x)-1;
}
ll qpow(ll x,ll y){
	ll s=1;
	while(y){
		if(y&1)s=s*x%mod;
		x=x*x%mod;
		y>>=1;
	}
	return s;
} 
int C(int x,int y){
	ll s=1;
	for(int i=1;i<=y;i++){
		s=1ll*s*(x-i+1)%mod*inv[i]%mod;
	}
	return s;
}
void solve(){
	int x,y,z;read(x,y,z);
	int ans=0;
	for(int i=2;i<=min(22,x);i++){
		ll tmp=x-i+1;
		for(int j=1;j<=11;j++){
			cadd(ans,1ll*F[j][i][y][z]*tmp%mod);
			tmp=1ll*tmp*(x-i+j+1)%mod*inv[j+1]%mod;
		}
	}
	cout << ans << '\n';
}
signed main(){
	inv[0]=inv[1]=1;
	for(int i=2;i<=22;i++)inv[i]=qpow(i,mod-2);
	for(n=2;n<=12;n++){
		for(int i=0;i<=all(n);i++)
			for(int j=0;j<=11;j++)
				for(int k=0;k<=11;k++)
					for(int l=0;l<=n;l++)
						f[i][j][k][l]=0;
		f[0][0][0][0]=1;
		for(int o=1;o<=n;o++){
			for(int S=0;S<=all(n);S++)
			for(int c1=0;c1<=11;c1++)
			for(int c2=0;c2<=11;c2++)
			for(int lst=0;lst<=n;lst++)
			if(f[S][c1][c2][lst]&&__builtin_popcount(S)+1==o){
				int mx=0;
				for(int i=1;i<=n;i++)
					if((S>>(i-1))&1)
						mx=i;
				for(int j=1;j<=n;j++)
					if(!((S>>(j-1))&1)){
						if(max(mx,j)>o||o==n){
							int t1=c1+__builtin_popcount(S&(all(n)-all(j)));
							int t2=c2+(lst>j);
							if(t1<=11&&t2<=11)cadd(f[S|(1<<(j-1))][t1][t2][j],f[S][c1][c2][lst]);
						}
					}
			}
		}
		for(int i=1;i<=11;i++)
			for(int j=1;j<=11;j++)
				for(int k=1;k<=n;k++)
					cadd(s[n][i][j],f[all(n)][i][j][k]);
	}
	F[0][0][0][0]=1;
	for(int a=0;a<=11;a++)
		for(int b=0;b<=22;b++)
			for(int c=0;c<=11;c++)
				for(int d=0;d<=11;d++)
					if(F[a][b][c][d])
						for(n=2;n<=12;n++)
							for(int i=1;i<=11;i++)
								for(int j=1;j<=11;j++)
									if(c+i<=11&&d+j<=11)
										cadd(F[a+1][b+n][c+i][d+j],1ll*F[a][b][c][d]*s[n][i][j]%mod);
	int T;read(T);while(T--)solve();
	return 0;
}
posted @ 2022-04-23 14:01  do_while_true  阅读(170)  评论(0编辑  收藏  举报