[题解]Fortnight Round 1

题目列表 / 官方题解

这场的题目真的炒鸡优质,给命题组一个大大的赞

终于补完啦 ><

T1 初遇

首先我们特判 \(a=n\)\(b=n\) 或者 \(a+b=n\) 的情况为 No

接下来,考虑两种操作方式:

  • 不断将右边的值赋给左边的值。
  • 不断将左边的值赋给右边的值。

\(a\ne b\) 时,如果进行了 \(k\) 次操作,则第一种方式的左值、第二种方式的右值分别是 \(a+k(a+b)\)\(b+k(a+b)\)

由于 \(a\ne b\),所以最多会有一种方式取到 \(n\)。因此答案恒为 Yes

\(a=b\) 时,进行一次操作后即可转化为 \(a\ne b\),答案为 Yes。除非 \(3\times a=n\),即第一次操作后出现 \(a+b=n\) 的情况,此时答案为 No

时间复杂度 \(O(T)\)

点击查看代码 - R237420041
#include<bits/stdc++.h>
#define int long long
using namespace std;
int t,n,a,b;
inline bool check(){
	if(a==n||b==n||a+b==n) return 0;
	if(a==b&&3*a==n) return 0;
	return 1;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>t;
	while(t--){
		cin>>a>>b>>n;
		cout<<(check()?"Yes\n":"No\n");
	}
	return 0;
}

T2 邀请

注意到 \(n\) 只有 \(20\),考虑将 \(n\times m\)\(S,T\) 转置为 \(m\times n\)\(S',T'\),并将第二维压成一个整数。

先考虑不带 \(X\) 限制的问题,此时如果 \(P_i=j\),则必有 \(S'_i=T'_j\)。故有解当且仅当 \(\forall x,cs_x=ct_x\),其中 \(cs_x,ct_x\) 分别为 \(x\)\(S',T'\) 中的出现次数。且有解时的答案是 \(\sum\limits_{x} cs_x!\)

考虑加上 \(X\) 的限制,此时的情况相当于对若干个 \(S'_i\) 取反,于是仅需规定 \(S\) 取反后的字符串与 \(S\) 相同,即它们共用一个 \(cs/ct\) 值即可。

时间复杂度 \(O(nm)\)

点击查看代码 - R237543718
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=22,M=1e5+2,P=1e9+7,NN=(1<<20);
int n,m,ss[M],tt[M],cs[NN],ct[NN],fac[M],mask,ans=1;
string a;
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	for(int i=fac[0]=1;i<=m;i++) fac[i]=fac[i-1]*i%P;
	if(!n){
		for(int i=1;i<=m;i++) (ans<<=1)%=P;
		cout<<ans*fac[m]%P<<"\n";
		return 0;
	}
	for(int i=0;i<n;i++){
		cin>>a;
		for(int j=0;j<m;j++) ss[j]=(ss[j]<<1)|(a[j]&1);
	}
	for(int i=0;i<n;i++){
		cin>>a;
		for(int j=0;j<m;j++) tt[j]=(tt[j]<<1)|(a[j]&1);
	}
	mask=(1<<n)-1;
	for(int i=0;i<m;i++) cs[min(ss[i],mask^ss[i])]++;
	for(int i=0;i<m;i++) ct[min(tt[i],mask^tt[i])]++;
	for(int i=0;i<=mask;i++){
		if(cs[i]!=ct[i]) cout<<"0\n",exit(0);
		ans=ans*fac[cs[i]]%P;
	}
	cout<<ans<<"\n";
	return 0;
}

T3 送礼

神题。

为了方便,我们先将 \(n\) 自增 \(1\),转化为求:

\[\sum\limits_{i=0}^{n-1} \sum\limits_{i=0}^{n-1} (i\oplus j)^4 \]

变形一下:

\[\sum\limits_{x=0}^{n-1} x^4 \left(\sum\limits_{i=0}^{n-1} \sum\limits_{i=0}^{n-1} [i\oplus j=x]\right) \]

令括号中的内容为 \(cnt_x\),接下来考虑对 \(cnt\) 的贡献情况。

不难发现,当 \(n=2^k\) 时,每个 \(cnt_x\) 都是 \(n\)。这启发我们将 \(n\) 按二进制分段。


我们将 \(n\) 分为 \(k\) 段:\(2^{x_1}+2^{x_2}+\dots+2^{x_k}\),其中 \(x_1>x_2>\dots>x_k\)

显然,第 \(p\) 段的后 \(x_p\) 位构成一个 \([0,2^{x_p})\) 的排列,其他位取值固定。

先考虑段内的贡献。对于第 \(p\) 段,在 \(i\) 确定的情况下,\(i\oplus j\) 的所有取值构成一个 \([0,2^{x_p})\) 的排列。因此对 \([0,2^{x_p})\)\(cnt\) 各造成 \(2^{x_p}\) 的贡献。

换句话说,对答案的贡献就是 \(2^{x_p}\times \sum\limits_{v=1}^{x_p-1} v^4\),后面那坨可以利用公式(放在后面)快速计算。

再考虑段之间的贡献。对于 \(p,q\) 这两段(不妨设 \(x_p>x_q\)),若 \(i,j\) 分别在段 \(p,q\) 中选择,则在只考虑后 \(x_p\) 位的情况下,对 \([0,2^{x_p})\) 中的每个 \(cnt\) 都会有 \(2^{x_q}\) 的贡献。

若去除前 \(x_p\) 位的限制,则其他位置在 \(i,j\) 中都是固定的,自然其异或值也是固定的。可以发现其值为 \(2^{x_p}\)。所以 \(cnt\) 中产生贡献的位置需要额外加上 \(2^{x_p}\)

至于 \(i,j\) 分别在 \(q,p\) 中选择的情况,将贡献乘上 \(2\) 即可。

总之此情况下,对 \([2^{x_p},2^{x_p+1})\) 中的每个 \(cnt\) 都会有 \(2^{x_q+1}\) 的贡献。对答案的贡献同上。

用前缀和的思想可以优化掉枚举 \(i,j\) 的过程。

时间复杂度 \(O(len)\)

四次方求和公式:

\[\sum\limits_{i=1}^x x^4=\frac{x^5}{5}+\frac{x^4}{2}+\frac{x^3}{3}-\frac{x}{30} \]

点击查看代码 - R237378068
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=5e6+5,P=998244353;
int n,pw[N],ans,iv2,iv3,iv5,iv30;
bitset<N> a;
inline int qp(int x,int n){int a=1;while(n){if(n&1) a=a*x%P;x=x*x%P,n>>=1;}return a;}
inline int sum(int x){return ((x*x%P*x%P*x%P*x%P*iv5+x*x%P*x%P*x%P*iv2+x*x%P*x%P*iv3-x*iv30)%P+P)%P;}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	iv2=qp(2,P-2),iv3=qp(3,P-2),iv5=qp(5,P-2),iv30=qp(30,P-2);
	cin>>n>>a;
	if(a.count()==n) a=0,a[n++]=1;//进行 +1 操作
	else{
		for(int i=0;i<n;i++){
			if(a[i]) a[i]=0;
			else{a[i]=1;break;}
		}
	}
	pw[0]=1;for(int i=1;i<=n+1;i++) pw[i]=(pw[i-1]<<1)%P;
	for(int i=0,cur=0;i<n;i++){
		if(a[i]){
			(ans+=sum(pw[i]-1)*pw[i])%=P;//块内部的
			(ans+=(sum(pw[i+1]-1)-sum(pw[i]-1)+P)*cur)%=P;//与更小块之间的
			(cur+=pw[i+1])%=P;
		}
	}
	cout<<ans<<"\n";
	return 0;
}

T4 热恋

转化一下题意,即求将 \(2n\) 个数分成 \(2\) 个大小为 \(n\) 的集合,使得两者元素乘积之和被 \(k\) 整除的方案数,再乘上 \((n!)^2\)

此题入手点在于 \(k\le 2n\),这意味着至少有一个集合的乘积已经被 \(k\) 整除,故另一个集合的乘积也必须被 \(k\) 整除。

考虑 DP。令 \(f[i][j][x][y]\) 为前 \(i\) 个数选了 \(j\) 个进入第一个集合,其他进入第二个集合,第一个集合乘积为 \(x\),第二个集合乘积为 \(y\)(乘积均为模 \(k\) 意义下的)。

则有转移:

//R237429959
f[0][0][1][1]=1;
for(int i=1;i<=tn;i++)//tn=2*n
	for(int j=0;j<=n&&j<=i;j++)
		for(int x=0;x<k;x++)
			for(int y=0;y<k;y++){
				if(j) (f[i][j][x*i%k][y]+=f[i-1][j-1][x][y])%=P;
				if(j<i) (f[i][j][x][y*i%k]+=f[i-1][j][x][y])%=P;
			}

最后 \(f[2n][n][0][0]\times (n!)^2\) 即为答案。

时间复杂度 \(O(n^2k^2)\),期望得分 \(50\text{pts}\)


我们发现,\(x,y\) 的维度可以仅记录与 \(k\)\(\gcd\) 值。

于是将所有 \(k\) 的因数离散化存起来,在 DP 时将取模改成取与 \(k\)\(\gcd\) 即可。

可能需要滚动数组优化一下。

由于 \(k\) 的因数个数与 \(O(\sqrt{k})\) 同阶,所以时间复杂度 \(O(n^2 k)\),期望得分 \(70\text{pts}\)

//R237450297
for(int i=tn*tn;i;i--) g[i]=__gcd(i,k);
for(int i=1;i<=k;i++) if(k%i==0) num[i]=++idx,rev[idx]=i;
f[0][0][1][1]=1;
for(int i=1;i<=tn;i++){
	for(int j=0;j<=n&&j<=i;j++) for(int x=1;x<=idx;x++) for(int y=1;y<=idx;y++)
		f[i&1][j][x][y]=0;
	for(int j=0;j<=n&&j<=i;j++){
		for(int x=1;x<=idx;x++){
			for(int y=1;y<=idx;y++){
				if(j) (f[i&1][j][num[g[rev[x]*i]]][y]+=f[(i&1)^1][j-1][x][y])%=P;
				if(j<i) (f[i&1][j][x][num[g[rev[y]*i]]]+=f[(i&1)^1][j][x][y])%=P;
			}
		}
	}
}

我们很神奇地发现,DP 过程与 \(i\) 的枚举顺序是无关的。

也就是说,我们可以视作 \(i\) 先从 \(k\) 开始遍历。这样必然有一个集合的与 \(k\)\(\gcd\) 值等于 \(k\)

于是我们就可以砍掉第四维,其他正常进行,最后将答案 \(\times 2\)(如果 \(k\ne 1\) 的话)即可。

时间复杂度 \(O(n^2\sqrt k)\),可以通过。

点击查看代码 - R237454326
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2002,K=N<<1,P=998244353;
int n,tn,k,f[2][N][46],ans,g[K*K],num[K],idx,rev[K];
signed main(){
	cin>>n>>k,tn=n<<1;
	for(int i=tn*tn;i;i--) g[i]=__gcd(i,k);
	for(int i=1;i<=k;i++) if(k%i==0) num[i]=++idx,rev[idx]=i;
	f[1][0][1]=f[1][1][1]=1;
	for(int i=2;i<=tn;i++){
		for(int j=0;j<=n&&j<=i;j++) for(int x=1;x<=idx;x++)
			f[i&1][j][x]=0;
		for(int j=0;j<=n&&j<=i;j++){
			if(j&&i!=k) for(int x=1;x<=idx;x++)
				(f[i&1][j][num[g[rev[x]*i]]]+=f[(i&1)^1][j-1][x])%=P;
			if(j<i) for(int x=1;x<=idx;x++)
				(f[i&1][j][x]+=f[(i&1)^1][j][x])%=P;
		}
	}
	ans=f[tn&1][n][idx];
	for(int i=2;i<=n;i++) (ans*=i*i)%=P;
	if(k==1) cout<<ans<<"\n";
	else cout<<ans*2%P<<"\n";
	return 0;
}
posted @ 2025-09-28 17:33  Sinktank  阅读(45)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.