2021.11.17模拟赛部分题解

第K排列

solution:

这个题目乍一看暴搜,但是又一看数据太大了不能暴搜,但是最后仔细的看,发现其实当我们严谨的估价,其实就是一个暴搜
首先我们可以从后面去计算最大的后缀值,然后我们就可以从前往后dfs了,因为只用求字典序第 k k k大的答案,且 k k k小于1000,所以我们去暴力递归,然后暴力递归的时间复杂度其实只有 O ( n ∗ 4 ) O(n*4) O(n4),因为经过剪枝之后,每个位置只会错4次。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5+10;
const int M = 4e7+10;
int n,m,K,tot,mx,x[4]={3,1,0,2},c[5][5],id[27],sum[1005][5],tt;
char a[1005],b[1005],d[4];
int check(int step,int cnt,int lst){return cnt+sum[step][lst]>=m;}
void dfs(int step,int lst,int cnt){
	if(step>0&&!check(step-1,cnt,lst))return ;
	if(step==n){
		if(++tot==K){printf("%s\n",b);exit(0);}
		return;
	}if(a[step]!='?'){
		b[step]=a[step];
		dfs(step+1,id[a[step]-'A'],cnt+c[lst][id[a[step]-'A']]);
	}else for(int i=0;i<4;i++){
		int j=x[i];b[step]=d[j];
		dfs(step+1,j,cnt+c[lst][j]);
	}
}
int main(){
//	freopen("noip4.in","r",stdin);
//	freopen("noip.out","w",stdout);
	id['N'-'A']=0;id['O'-'A']=1;
	id['I'-'A']=2;id['P'-'A']=3;
	d[0]='N';d[1]='O';d[2]='I';d[3]='P';
	scanf("%d%d%d%s",&n,&m,&K,a);
	for(int i=0;i<4;i++)
		for(int j=0;j<4;j++)
			scanf("%d",&c[i][j]);
	for(int i=n-2;i>=0;i--){
		if(a[i]=='?'){
			if(a[i+1]=='?')for(int j=0;j<4;j++)for(int k=0;k<4;k++)
			sum[i][j]=max(sum[i][j],sum[i+1][k]+c[j][k]);
			else for(int j=0;j<4;j++)
			sum[i][j]=max(sum[i][j],sum[i+1][id[a[i+1]-'A']]+c[j][id[a[i+1]-'A']]);
		}else{
			if(a[i+1]=='?')for(int k=0;k<4;k++)
			sum[i][id[a[i]-'A']]=max(sum[i][id[a[i]-'A']],sum[i+1][k]+c[id[a[i]-'A']][k]);
			else sum[i][id[a[i]-'A']]=sum[i+1][id[a[i+1]-'A']]+c[id[a[i]-'A']][id[a[i+1]-'A']];
		}
	}dfs(0,4,0);puts("-1");return 0;
}

打拳

solution:

首先题意有亿点问题,就是我们要确保1能夺得冠军。
我们会发现不论怎样我们第 i i i轮挑战的对手一定是在 2 i − 1 2^{i-1} 2i1大小的一个集合中(集合互不重叠),且他既要是我们已经收买的人,还要是那 2 i − 1 2^{i-1} 2i1中分数最大的人。然后这 n n n个集合有 2 n 2^n 2n排列方式。
因为这个集合数目就只有 n n n个,那么考虑状压,因为我们还要选一个我们收买的人,且他要是那个集合中最大的,所以我们要升序枚举收买的人。
然后我们来想下一个条件,最长上升子序列必须 > = K >=K >=K所以继续状压,状态的含义就是当前的第 i i i个1所在的位置就是长度为 i i i的上升子序列最左端点的所在,因为是升序加点,所以新加进来的点是当前最大的,假设他左边有 k k k个1,那么他就是 k + 1 k+1 k+1的最右端点,所以我们把原序列中的第 k + 1 k+1 k+1的1删掉即可。
据此我们会发现上升子序列的状态其实是第一个枚举的集合的一个子集,所以时间复杂度是 O ( m ∗ 3 n ∗ n ) O(m*3^n*n) O(m3nn)

#pragma GCC optimize(3)
#pragma GCC optimize(2)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e3+10;;
int n,m,K,a[N];
ll mo,f[17][1<<10][1<<10],C[N][N],ans,fac[N];
void init(){
	fac[0]=1;
	for(int i=1;i<N;i++)fac[i]=1ll*i*fac[i-1]%mo;
	for(int i=0;i<N;i++)
	for(int j=0;j<=i;j++){
		if(i==j||!j)C[i][j]=1;
		else C[i][j]=C[i-1][j]+C[i-1][j-1],C[i][j]%=mo;
	}
}
signed main()
{
	scanf("%d%d%d%lld",&n,&m,&K,&mo);init();
	for(int i=1;i<=m;i++)scanf("%d",a+i);
	sort(a+1,a+1+m);
	f[0][0][0]=1;int S=(1<<n)-1;
	for(int i=0;i<m;i++)
		for(int j=0;j<S;j++)
			for(int k=j;;k=(k-1)&j){
				if(f[i][j][k]){
					f[i+1][j][k]+=f[i][j][k];f[i+1][j][k]%=mo;
					for(int l=0;l<n;l++){
						if((j>>l)&1)continue;
						int kk = k|(1<<l);
						for(int p=l+1;p<n;p++)
						if((kk>>p)&1){kk^=(1<<p);break;}
						ll &tmp=f[i+1][j|(1<<l)][kk];
						if(a[i+1]-2-j>=(1<<l)-1)
						tmp+=f[i][j][k]*C[a[i+1]-2-j][(1<<l)-1]%mo*fac[1<<l]%mo;tmp%=mo;
					}
				}if(!k)break;
			}
	for(int i=0;i<=S;i++){
		int cnt=0;
		for(int j=0;j<n;j++)if((i>>j)&1)cnt++;
		if(cnt>=K)ans+=f[m][S][i],ans%=mo;
	}printf("%lld\n",1ll*ans*(S+1)%mo);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值