"蔚来杯"2022牛客暑期多校训练营7 部分题题解

J Melborp Elcissalc

首先观察这个题,我们发现它需要一个区间的和是k的倍数,区间和自然而然的就想到了前缀和,两个前缀和做差是k的倍数,说明这两个前缀和对k同余,并且我们观察它允许我们填入的数字,0-k-1,这不就是对于k的余数系吗?这更加证实了我们的猜想.考虑前缀和的余数是k,我们之后需要填入一个数字,发现我们可取的范围内,之后的前缀和的范围也是0-k-1.说明不同的前缀和和原始填入的序列是一一对应的。
那么接下来我们转化题意:还是一个长度为n的序列,每个元素我们填入0-k-1,相同的元素之间两两造成1的贡献,问最后贡献为k的方案数?
这个问题,我们发现位置已经没那么重要了,重要的是元素的值.
那么f[i][j][k]表示0-i的元素我们已经放完,并且有j个位置已经用过,当前贡献为k的方案数.
为什么这样设状态呢?因为对于一个数字i,我们并不关心它放在哪些位置,而是关注它有几个。
所以就抛弃了之前我们前i个怎么怎么样的思维定式,而是按照放数值的思想去DP。
考虑转移,考虑枚举当前i放了几个。
\(f[i][j][k]+=C(n-(j-l),l)*f[i-1][j-l][k-cost[l]](0=<l<=j)\)
复杂度O(64^4),卡卡常,应该能过吧...

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=70,M=5010,P=998244353;
int f[N][N][M],n,K,t,jc[N],jc_inv[N];
inline int power(int x,int y)
{
	int ans=1;
	while(y)
	{
		if(y&1) ans=(ll)ans*x%P;
		y>>=1;
		x=(ll)x*x%P;
	}
	return ans%P;
}
inline int C(int n,int m){return (ll)jc[n]*jc_inv[m]%P*jc_inv[n-m]%P;}
int main()
{
//	freopen("1.in","r",stdin);
	scanf("%d%d%d",&n,&K,&t);
	jc[0]=jc_inv[0]=1;
	for(int i=1;i<=n;++i) jc[i]=(ll)jc[i-1]*i%P;
	for(int i=1;i<=n;++i) jc_inv[i]=power(jc[i],P-2);
	for(int i=0;i<K;++i)
	{
		for(int j=0;j<=n;++j)
		{	
			if(i==0)
			{
				int k=j+1;
				k=k*(k-1)/2;
				if(k>t) continue;
				f[i][j][k]=C(n,j);
			}
			else
			{
				for(int k=0;k<=min(t,j*(j+1)/2);++k)//当前贡献 
				{
					for(int l=0;l<=j&&l*(l-1)/2<=k;++l)//枚举当前i放了几个位置. 
					{
						f[i][j][k]=(f[i][j][k]+(ll)C(n-j+l,l)*f[i-1][j-l][k-l*(l-1)/2])%P;
					}
				}
			}
		}
	}
	printf("%d\n",f[K-1][n][t]);
	return 0;
}

K Great Party

在写这个博弈论的题之前,我们先复习一下简单的NIM问题。
简单NIM游戏,先手必胜当且仅当\(a_1xora_2xor...xora_n!=0\)
证明如下:首先全部石子被取光是一个必败,这时显然全部异或为0.
之后对于其余的任何一个局面,设x为其异或和,其最高位1是k。则至少有一个\(a_i\),则它的第k位也是1.则我们一定可以构造出一个值\(a_ixorx\)。并且这个值小于\(a_i\),我们只需要取走\(a_i-a_ixorx\),使得第\(i\)对石子剩余\(a_ixorx\),这样的话,剩下的局面全部书异或和即为\(xxora_ixora_ixorx=0\).换句话说,在任何一个全部异或和不为0的情况下,我们总能使得一个操作之后,剩下数异或和为0.
之后我们来看这个题,对于这种博弈论的问题,我们可以从小往上慢慢推,先从边界条件出发,若
只有一堆石子,先手必胜
当有两堆石子,若两堆石子个数相同,则先手在哪一堆拿多少个,后手就可以在另一堆拿多少个赖死先手.若两堆石子个数不同,则先手可以拿去个数多的那一堆石子若干个,使得后手面对两队石子个数相同的局面,这时先手必胜。
总结当有两堆石子时,个数相同,先手必败,个数不同,先手必胜
当有三堆石子时,先手可以对个数最大的石子操作,然后弥补剩下的个数小的那个,使得他们个数相同。所以三堆石子,先手必胜
当有四堆石子时,显然无论先手还是后手,他们都不会选择合并操作,因为一旦使用合并操作,那么另一个人就面临三堆石子必胜的局面,这是他们不愿意看到的。所以最后的局面一定是1111.这时轮到操作的人不得不使得堆数变成三堆。那么这样问题就变成了,剩下的石子随便取,剩下1111局面的必败。这不就是nim问题吗,我们只需要对原始的\(a_i\)都减1,之后根据NIM游戏结论即可。
找规律,这种博弈题,如果没有百分百的结论可以证明,还是找规律来的实在。
大胆预测,石子堆数为奇数,先手必胜。石子堆数为偶数,所有数-1之后异或和为0,先手必败,不为0,先手必胜。
回到问题本身,问题询问的是,[l,r]区间内有多少子区间,先手必胜,我们可以用所有子区间个数减去先手必败的区间个数。
先手必败的话,区间长度为偶数,且异或和为0,我们使用前缀和,则使得sum[r]=sum[l-1]。区间统计信息,可以使用莫队完成。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+10,M=3e6+10;
int n,Q,a[N],sum[N],cnt[M][2];
ll ans[N],now=0;
struct wy{int l,r,id;}q[N];
int block,num,belong[N];
inline void build()
{
    block=sqrt(n+1);num=(n+1)/block;
    if((n+1)%block) num++;
    for(int i=0;i<=n;++i) belong[i]=i/block+1;
}

inline bool cmp(wy a,wy b) 
{
    return belong[a.l]!=belong[b.l]?belong[a.l]<belong[b.l]:(belong[a.l]&1?a.r<b.r:a.r>b.r);
}
inline void add(int i)
{
	now+=cnt[sum[i]][i%2];
	cnt[sum[i]][i%2]++;
}
inline void del(int i)
{
	cnt[sum[i]][i%2]--;
	now-=cnt[sum[i]][i%2];
}
int main()
{
//	freopen("1.in","r",stdin);
	scanf("%d%d",&n,&Q);
	for(int i=1;i<=n;++i) 
	{
		int x;scanf("%d",&x);
		x--;sum[i]=sum[i-1]^x;
	}
	for(int i=1;i<=Q;++i)
	{
		int x,y;scanf("%d%d",&x,&y);
		q[i]={x-1,y,i};
	}
	build();
	sort(q+1,q+Q+1,cmp);
	int l=1,r=0;
	for(int i=1;i<=Q;++i)
	{
		int ql=q[i].l,qr=q[i].r,j=q[i].id;
		while(ql>l) del(l++);
		while(ql<l) add(--l);
		while(qr>r) add(++r);
		while(qr<r) del(r--);
		ans[j]=(ll)(1+qr-ql)*(qr-ql)/2-now;
	}
	for(int i=1;i<=Q;++i) printf("%lld\n",ans[i]);
	return 0;
}
posted @ 2022-08-10 10:17  逆天峰  阅读(22)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//