2019 年百度之星·程序设计大赛 - 初赛三—min

题目链接->->->min

分析:

min(api,api+1)≤min(api+1,api+2)

一开始博主根据上述条件胡了好几个假结论.....

上述条件可以转化为条件T:把a中数加入到一个空数组中,从前往后,如果当前加入的数是当前所剩数中最小的数,那么加入的下一个数可以任意,否则加入的下一个数必须是所剩数中的最小的数,或者换句话来说,不能够连续加入的两个数都不是当前剩余的最小数。这样做我们可以得到一个数组,注意其中的数是带标号的。

那么这样可以很容易想到dp,但是由于有后效性被ka掉了。

逆向考虑:从小到大加入每一个数。假设当前要加入数x,那么对于当前的数列来说,x只能被加在数列末尾,或者满足条件的中间位置。这个满足条件是什么?就是对于当前数列中的相邻两个数,如果他们都满足根据我上述所说的条件T中的都是当前剩余的数中的最小值(依据条件T中的构造方式),那么数x就可以被插入到这两个数之间。

那么我们可以用dp维护当前有i个可插入位置的方案数。

博主的思维非常诡异,所以选取了上述内容作为博主自己对这道题的看法,而且其中的分析过程有点......

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e3+10;
const ll mod=998244353;
int fac[N],inv[N],a[N],b[N],f[2][N];
int T,n,x;
int add(int x,int y){return x+y>=mod?x+y-mod:x+y;}
int dec(int x,int y){return x-y<0?x-y+mod:x-y;}
int mul(int x,int y){return (ll)x*y%mod;}
void init(){
	fac[0]=1;
	for (int i=1;i<N;i++) fac[i]=mul(fac[i-1],i);
	inv[0]=inv[1]=1;
	for (int i=2;i<N;i++) inv[i]=mul((mod-mod/i),inv[mod%i]);
	for (int i=2;i<N;i++) inv[i]=mul(inv[i-1],inv[i]);
}
int C(int x,int y){
	if (x<0||y<0||x<y) return 0;
	return mul(mul(fac[x],inv[y]),inv[x-y]);
}
int main(){
	scanf("%d",&T);
	init();
	while (T--){
		scanf("%d",&n);
		memset(a,0,sizeof(a)); 
		for (int i=1;i<=n;i++){
			scanf("%d",&x); a[x]++;
		}
		int cnt=0;
		for (int i=1;i<=n;i++) 
		 if (a[i]>=1) b[++cnt]=a[i];
		for (int i=0;i<=n;i++) f[1][i]=f[0][i]=0;
		f[1][b[1]]=fac[b[1]];
		int s=b[1];
//		for (int i=1;i<=n;i++) printf("%d\n",b[i]);
		for (int i=2;i<=cnt;i++) {
			for (int j=0;j<=s+b[i];j++) f[i&1][j]=0;  
			for (int j=0;j<=b[i];j++) 
			 for (int k=max(2*j-b[i],0);k<=s;k++) 
			 if (f[i&1^1][k])
			  f[i&1][k-j+b[i]-j]=add(f[i&1][k+b[i]-2*j],mul(f[i&1^1][k],mul(mul(fac[b[i]-j],fac[j]),mul(C(b[i],j),C(k,j)))));  
			s+=b[i];  
//			for (int j=0;j<=s;j++) printf("%d\n",f[i&1][j]);
		}
		int ans=0;
		for (int i=0;i<=n;i++) ans=add(ans,f[cnt&1][i]);
		printf("%d\n",ans);
	}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值