CF2140E2 Prime Gaming (Hard Version)[Hard-]

前言

题目传送门

题意

因为题面很形式化,所以来个比较符合人类直觉的题面。

你有 \(n\) 个物品,每个物品有个贡献 \(w_i\),现在两个人要进行有趣的游戏。

\(k\) 个位置 \(c_i\),每次操作可以将当前序列从左往右第 \(c_i\) 个位置的物品从序列中直接挖走。只剩一个物品时游戏结束。

第一个人希望最后一个物品贡献尽量大,第二个人希望尽量少。

然后每个贡献 \(w_i\) 的取值范围为 \([1,m]\),求所有可能的物品序列对应的最后一个物品贡献之和。

解题思路

假设我们拿着把野牛,直接大脚步不搜这题前置简单版,直接突破这题,那么该如何在无提示下做出此题?

\(n \leq 20\) 摆明了这是指数算法题,我们用一个比较暴力的思路去想的话,首先暴力这个物品序列情况,设物品序列为 \(P\),然后去模拟决策过程。发现只有当游戏结束时状态贡献确定,即倒数第一轮,于是我们倒着做一个关于最大最小值的对抗动态规划,大胆设一个状态 \(dp_{i,p,0/1}\) 表示进行到倒数第 \(i\) 轮,当前物品序列为 \(p\)(你可以认为这是状压),\(0/1\) 表示当前决策的入是第一个人还是第二个人。转移时暴力枚举挖掉 \(p\) 的哪个位置,如果第一个人决策就取最大值,第二个人决策就取最小值。因为第一个人先手,所以最后答案是 \(dp_{P,n,0}\)

发现上面那个答案其实是可以最后枚举 \(P\) 计算 \(\sum dp_{P,n,0}\) 把dp从暴力枚举物品中提出来,优化掉了一个指数。

因为 \(n \leq 20\),所以大概是一个 \(2^n\) 的算法,考虑怎样将上面那个做法做到 \(2^n\)

发现如果硬是想知道每种状态的最后给到的贡献,那 \(p\) 的状态是必然爆掉的,但是如果物品贡献只有两种,那就完全可以做到 \(2^n\) 的。

只有两种物品贡献等价于 \(0,1\),考虑 \(0,1\) 状态代表什么,似乎对于这题的来说,因为刚才似乎都躲不过一个数值比较过程,所以 \(0,1\) 可以表示为一个大小关系。

对于贡献类累加情况,注意到 \(m \leq 10^6\),启发我们拆贡献计算,即计算有多少情况满足贡献为 \(i\)

因为通过上面内容我们知道,可以提前处理动态规划,这是个可达性问题,表示是否可以取到 \(1\) 的贡献。

先贴代码吧,因为我不想贴柿子:

//用了滚动怕MLE
dp[1][0][0]=dp[1][0][1]=0,dp[1][1][0]=dp[1][1][1]=1;
//0:Alice 1:Bob 
for(int i=2;i<=n;i++){
	for(int S=0;S<(1<<i);S++){
		dp[i&1][S][0]=0,dp[i&1][S][1]=1;
		for(int j=1;j<=k;j++){
			int p=c[j];
			if(p>=i) break;
			int low=(S&((1<<p)-1));
			int high=(S>>p+1);
			int s=low|(high<<p);
			chkmax(dp[i&1][S][0],dp[i-1&1][s][1]);
			chkmin(dp[i&1][S][1],dp[i-1&1][s][0]);
		}
	}
}

然后枚举贡献 \(i\),我们先暴力枚举出一个大小关系序列 \(P\)\(0\) 表示填一个小于 \(i\) 的数,\(1\) 表示填一个不小于 \(i\) 的数)。填小于 \(i\) 可以填 \(i-1\) 种数,填不小于 \(i\) 的数可以填 \(m-i+1\) 种。于是 \(dp_{n,P,0}\) 可以贡献 \((i-1)^{cnt0}(m-i+1)^{cnt1}i\)\(cnt0,cnt1\) 表示小于/不小于 \(i\) 的数的个数),前提是 \(dp_{n,P,0}\) 可达。但是发现上面这个东西会算重,于是先算出 \((i-1)^{cnt0}(m-i+1)^{cnt1}dp_{n,P,0}\),这是贡献 \(\geq i\) 的情况数,减去 \(\geq i+1\) 的情况数再乘上 \(i\) 即可。

如果知道了 \(i\),发现上面这个贡献似乎只会和 \(cnt0,cnt1\) 有关,即 \(P\) 中的 \(0,1\) 个数,于是我们按 \(dp_{n,P,0}\)\(P\)\(1\) 的个数丢到一个桶里即可。

给代码看看吧:

for(int S=0;S<(1<<n);S++){ //S就是P
  if(dp[n&1][S][0]) sum[cnt[S]]++; //sum是桶,cnt是记录二进制下1的个数
}
for(int i=1;i<=m;i++){
	ans[i]=0; //计算不小于i的情况
	for(int j=0;j<=n;j++) pls(ans[i],1ll*qpow(i-1,n-j)*qpow(m-i+1,j)%MOD*sum[j]%MOD); //qpow就是快速幂
}

然后这题就做完了,时间复杂度 \(O(nk2^n+nm)\),那个 \(2^n\) 可以用代码中的小优化跑得不满。

Code

#include<bits/stdc++.h>
#define ll long long
#define ull unsigned long long
#define uint unsigned int
#define i128 __int128
#define ld long double
#define fir first
#define sec second
#define pii pair<int,int>
#define pll pair<ll,ll>
#define ls(x) (x<<1)
#define rs(x) (x<<1|1)
#define lowbit(x) (x&-x)
using namespace std;
namespace FastIO{
	const int Size=1<<21;
	char ibuf[Size],obuf[Size],*p1=ibuf,*p2=ibuf,*p3=obuf;
	#define getchar() (p1==p2&&(p2=(p1=ibuf)+fread(ibuf,1,Size,stdin),p1==p2)?EOF:*p1++)
	#define putchar(x) (p3-obuf<Size?(*p3++=(x)):(fwrite(obuf,1,p3-obuf,stdout),p3=obuf,*p3++=(x)))
	inline void flush(){if(p3>obuf) fwrite(obuf,1,p3-obuf,stdout),p3=obuf;}
	template<class T>
	T read(T&x){
		x=0;bool f=false;char ch=getchar();
		while(!isdigit(ch)) f|=!(ch^'-'),ch=getchar();
		while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch&0xF),ch=getchar();
		x=(f?-x:x);return x;
	}template<class T>
	int reads(T*s){
		char ch=getchar();int len=0;
		while(ch==' '||ch=='\n'||ch=='\r') ch=getchar();
		while(ch!=' '&&ch!='\n'&&ch!=EOF&&ch!='\r') s[len++]=ch,ch=getchar();
		s[len]='\0';return len;
	}template<class T>
	T readd(T&x){
		x=0;bool f=false;char ch=getchar();
		while(!isdigit(ch)) f|=!(ch^'-'),ch=getchar();
		while(isdigit(ch)) x=x*10+(ch&0xF),ch=getchar();
		if(ch=='.'){ch=getchar();T d=1;while(isdigit(ch)) d*=0.1,x+=d*(ch&0xF),ch=getchar();}
		x=(f?-x:x);return x;
	}template<class T>
	void write(T x,char ch=' '){
		if(x<0) putchar('-'),x=-x;
		char tmp[41];int cnt=0;
		while(x>9) tmp[cnt++]=x%10+'0',x/=10;tmp[cnt++]=x+'0';
		while(cnt) putchar(tmp[--cnt]);putchar(ch);
	}template<class T>
	void writes(T x,int l=0,int r=-1){
		if(~r){for(int i=l;i<=r;i++) putchar(x[i]);}
		else{for(int i=l;x[i];i++) putchar(x[i]);}
	}template<class T>
	void writed(T x,int p=6,char ch=' '){
		if(x<0) putchar('-'),x=-x;
		T d=0.5;for(int i=0;i<p;i++) d*=0.1;x+=d;
		i128 g=(i128)(x);p?write(g,'.'):write(g,ch);
		if(p){T f=x-g;for(int i=0,d;i<p;i++) f*=10,d=(int)(f),putchar(d+'0'),f-=d;putchar(ch);}
	}
}
using namespace FastIO;
const int N=21,M=1e6+10,MOD=1e9+7;
bool dp[2][1<<N][2];
int cnt[1<<N],sum[N],ans[M],c[N],n,m,k,T,furry;
inline void chkmax(bool&x,bool y){x=(x<y?y:x);}
inline void chkmin(bool&x,bool y){x=(x<y?x:y);}
inline void pls(int&a,int b){a+=b;if(a>=MOD) a-=MOD;}
inline void sub(int&a,int b){a-=b;if(a<0) a+=MOD;}
ll qpow(ll a,ll b){ll res=1;while(b){if(b&1) res=res*a%MOD;a=a*a%MOD,b>>=1;}return res;}
void work(){
	read(n),read(m),read(k);
	for(int i=1;i<=k;i++) read(c[i]),c[i]--;
	if(m==1){writes("1\n");return;}
	dp[1][0][0]=dp[1][0][1]=0,dp[1][1][0]=dp[1][1][1]=1;
	//0:Alice 1:Bob 
	for(int i=2;i<=n;i++){
		for(int S=0;S<(1<<i);S++){
			dp[i&1][S][0]=0,dp[i&1][S][1]=1;
			for(int j=1;j<=k;j++){
				int p=c[j];
				if(p>=i) break;
				int low=(S&((1<<p)-1));
				int high=(S>>p+1);
				int s=low|(high<<p);
				chkmax(dp[i&1][S][0],dp[i-1&1][s][1]);
				chkmin(dp[i&1][S][1],dp[i-1&1][s][0]);
			}
		}
	}
	for(int i=0;i<=n;i++) sum[i]=0;
	for(int S=0;S<(1<<n);S++){
		if(dp[n&1][S][0]) sum[cnt[S]]++;
	}
	furry=0;
	for(int i=1;i<=m;i++){
		ans[i]=0;
		for(int j=0;j<=n;j++) pls(ans[i],1ll*qpow(i-1,n-j)*qpow(m-i+1,j)%MOD*sum[j]%MOD);
	}
	ans[m+1]=0;
	for(int i=1;i<=m;i++){
		sub(ans[i],ans[i+1]);
		pls(furry,1ll*ans[i]*i%MOD);
	}
	write(furry,'\n');
}
int main(){
	for(int i=1;i<(1<<20);i++) cnt[i]=cnt[i>>1]+(i&1);
	read(T);
	while(T--) work();
	flush();
	return 0;
}

其实这篇题解其实还是模拟一个思路过程。

posted @ 2025-12-14 22:37  AstraeusGleam  阅读(1)  评论(0)    收藏  举报