10.20练习题目总结

[USACO 2025 US Open, Gold] Problem 1. Moo Decomposition

题目描述

你有一个由字符 M 和 O 组成的长字符串 S 和一个整数 K≥1。计算将 S 分解为子序列的方案数,使得每一个子序列均为 MOOOO....O其中恰有 K 个 O,答案对 1e9+7取模。

由于字符串非常长,其未被直接给出。你被给定一个整数L(1≤L≤1e18)和一个长为N 的字符串T(1≤N≤1e6)。字符串S 等于L 个字符串 T 的拼接。

题解

观察到由于题目限制’M‘后应跟k个’O‘所以若当前M去到下一个循环中找‘O’那必定当前循环内的‘O’就会被浪费掉从而导致后面循环中的'M'匹配不上'O'所以每一个循环中的方案不与下一个循环有关只需关注当前的循环求出方案数ans输出\(ans^L\)即可

正着求不是很好求因为当前‘M’到后一个‘M’中的‘O’只能当前的和之前的‘M’用维护更复杂。考虑从后往前枚举t[i]用变量cnt记录i~n之间的‘O’的个数每次遇到一个‘M’就可以让ans乘上\(C_{cnt}^k\)再让cnt-k这样保证后面去求时给当前的‘M’留下了k个‘O'若cnt<0表示无法完成题目的要求,组合数没有意义为0导致ans整体为0所以如果计算组合数的函数特判了就不用管。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=1e6+10;
const int mod=1e9+7;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll maxx=N-10;
ll n,m,k,ans,cnt,rt;
ll a[N],b[N],dp[N][3];
ll jie[N],ni[N];
ll pos[N][10];
ll read(){ll x;cin>>x;return x;}
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	jie[0]=1;
	for(int i=1;i<=maxx;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[maxx]=ppow(jie[maxx],mod-2);
	for(int i=maxx;i;i--)ni[i-1]=(ni[i]*i)%mod;
	cin>>k>>n>>m;
	string s;
	cin>>s;
	s=' '+s;
	ans=1;
	for(int i=n;i>=1;i--){
		if(s[i]=='O'){
			cnt++;
		}else{
			ans*=c(cnt,k);
			ans%=mod;
			cnt-=k;
		}
	}
	cout<<ppow(ans,m);
}

[ABC290] F - Maximum Diameter

题目描述

对于一个由正整数组成的长度为N的序列 \(X=(X_1,X2,…,X_N)\),我们定义函数f(X)如下:

  • 如果一个有N个顶点的树满足第ii个(1≤i≤N)(1≤i≤N)顶点的度恰好是\(X_i\)那么我们称这棵树是“好”的。如果存在“好”树,那么\(f(X)\)就是“好”树的最大直径;如果不存在,则f(X)=0

这里,两个顶点之间的距离是从一顶点到另一顶点必须经过的最少边数,而树的直径是两个顶点之间的最大距离。

找出所有可能的长度为N的正整数序列X的\(f(X)\)之和,模998244353。我们可以证明\(f(X)\)的和是一个有限值。

给定T个测试用例,找出每一个的答案。

题解

题意为求\(n\)个点的树所有可能直径的和

一个较为容易的思路,就是直径就是一棵树上的最长链这个链上两端的节点一定度数为一,其余度为2,可设有\(k\)个叶子节点即度数为1,其余的度数>1,所以这种方案的\(f(X)\)为\(n-k+1\)(n个节点-k个叶子包括直径两端上的所以+2为\(n-k+2\)但这是直径上的点数,直径为边数应再-1),现在我们要求的就是有\(k\)个叶子的树的个数,总边数为\(n-1\)总度数为\(2n-2\)减去每个叶子的一个度(k)和与非叶子相连的度数\((n-k)\)为\(n-2\),现在问题就变成了\(n-k\)个点分配\(n-2\)个度数,这等价与\(n-k\)盒子放\(n-2\)个小球的方案数为\(C_{n-3}^{n-k-1}\)

所以总答案就为\(\sum_{i=1}^nC_n^kC_{n-3}^{n-k-1}(n-k+1)\)

\[\begin{split} &\sum_{i=1}^nC_n^kC_{n-3}^{n-k-1}(n-k+1)\\ &=\sum_{i=1}^nC_n^kC_{n-3}^{n-k-1}(n-k-1+2)\\ &=\sum_{i=1}^nC_n^kC_{n-3}^{n-k-1}(n-k-1)+2\sum_{i=1}^nC_n^kC_{n-3}^{n-k-1}\\ &=\sum_{i=1}^nC_n^kC_{n-4}^{n-k-2}(n-3)+2C_{2n-3}^{n-1}\\ &=(n-3)\sum_{i=1}^nC_n^kC_{n-4}^{n-k-2}+2C_{2n-3}^{n-1}\\ &=(n-3)C_{2n-4}^{n-2}+2C_{2n-3}^{n-1}\\ \end{split} \]

代码其实很简单了

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=3e6+10;
const int mod=998244353;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll maxx=N-10;
ll n,m,k,ans,cnt,rt;
ll a[N],b[N],dp[N];
ll jie[N],ni[N];
ll read(){ll x;cin>>x;return x;}
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
int main(){
	jie[0]=1;
	for(int i=1;i<=maxx;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[maxx]=ppow(jie[maxx],mod-2);
	for(int i=maxx;i;i--)ni[i-1]=(ni[i]*i)%mod;
	cin>>n;
	while(n--){
		ll x;
		cin>>x;
		cout<<((x-3)*c(2*x-4,x-2)%mod+2*c(2*x-3,x-1)%mod)%mod<<'\n';
	}
}

[ABC240] G - Teleporting Takahashi

题目描述:

高桥君现在位于无限大的三维网格的格点 \((0, 0, 0)\)。

高桥君拥有通过瞬间移动从一个格点移动到另一个格点的能力。当他位于格点 \((x, y, z)\) 时,每进行一次瞬间移动,他可以移动到以下六个格点之一:\((x+1, y, z)\)、\((x-1, y, z)\)、\((x, y+1, z)\)、\((x, y-1, z)\)、\((x, y, z+1)\)、\((x, y, z-1)\)。请注意,他不能停留在原地。

请你计算,恰好经过 \(N\) 次瞬间移动后,高桥君位于格点 \((X, Y, Z)\) 的移动路径有多少种。

也就是说,求满足以下三个条件的整数三元组序列 \(\big((x_0, y_0, z_0), (x_1, y_1, z_1), (x_2, y_2, z_2), \ldots, (x_N, y_N, z_N)\big)\) 的个数:

  • \((x_0, y_0, z_0) = (0, 0, 0)\)
  • \((x_N, y_N, z_N) = (X, Y, Z)\)
  • 对于 \(i = 1, 2, \ldots, N\),有 \(|x_i - x_{i-1}| + |y_i - y_{i-1}| + |z_i - z_{i-1}| = 1\)

由于答案可能非常大,请输出答案对 \(998244353\) 取模的结果。

题解:

先特判若\(X+Y+Z\)的奇偶与n的奇偶是否相同,若不同说明最后走完n步后会余下一步,所以直接输0即可

再考虑如何解决问题,三维不好维护可以先考虑先把z这一维提前算出贡献,再考虑x和y的情况,

枚举i为z这一维最少要\(Z\)步,最多\(n-X-Y\)步(相当于x,y维不多走只z维多走),每次加两步,走超过\(Z\)再走回来

先从n步里选出\(Z+(i-Z)/2\)走正步,再从\(n-(Z+(i-Z)/2)\)中选\((i-Z)/2\)走反步方案数为

\[C_{n}^{Z+(i-Z)/2}*C_{n-(Z+(i-Z)/2)}^{(Z-i)/2} \]

再考虑x和y

令 \(u=x+y\),\(v=x−y\).在 2D 平面上走 ±1±1 沿坐标轴,等价于在 \((u,v)\)坐标下每步改变 (±1,±1)(±1,±1) 且两个分量同步变化。所以在 \((u,v)\)坐标下,每步 \(u\)变化±1±1,\(v\) 变化 ±1±1,且相互独立.因此 2D 问题分解为两个独立的 1D 问题。

设p为\(|X+Y|\),q为\(|X-Y|\),\(now=n-i\)方案数为

\[C_{now}^{(now+p)/2}C_{now}^{(now+q)/2} \]

代码:

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=1e7+10;
const int mod=998244353;
const ll inf=0x3f3f3f3f3f3f3f3f;
int maxx=3e6;
ll n,m,k,ans,cnt,rt,x,y,z;
ll a[N],b[N];
ll jie[N],ni[N];
ll read(){ll x;cin>>x;return x;}
struct zzz{
	ll x,y,v;
}e[N];
int cmp(zzz a,zzz b){
	return a.v<b.v;
}
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
ll cn1(int x){
	return (c(n,z+(x-z)/2)*c(n-z-(x-z)/2,(x-z)/2))%mod;
}
ll cn2(int now){
	int xx=abs(x+y),yy=abs(x-y);
	if(((now-xx)&1)||((now-yy)&1)) return 0;
	return (c(now,xx+(now-xx)/2)*c(now,yy+(now-yy)/2))%mod;
}
int main(){
//    freopen("fly.in","r",stdin);
//    freopen("fly.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
    cin>>n;
	jie[0]=1;
	for(int i=1;i<=n;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[n]=ppow(jie[n],mod-2);
	for(int i=n;i;i--)ni[i-1]=(ni[i]*i)%mod;
    cin>>x>>y>>z;
    x=abs(x);
    y=abs(y);
    z=abs(z);
    if((n-x-y-z)&1){
    	cout<<0<<endl;
    	return 0;
	}
	ll ans=0;
	for(int i=z;i<=n-x-y;i+=2){
		ans=(ans+(cn1(i)*cn2(n-i))%mod)%mod; 
	}
	cout<<ans;
}

[NOIP2025] 清仓甩卖

首先贪心为什么出错,在只有 1元和 2 元两种重量的情况下,贪心策略(按性价比降序购买)只有在一种情况下会出错:
当剩余钱数为 1 时,跳过了一个高价值的1元糖果\(u\),转而买了一个低价值的 1元糖果 \(v\)。如果之前已经买下的元1糖果\(z\)满足\(a_z + a_v < a_u\),那么把\(z\) 和 \(v\) 退掉换成 \(u\),会得到更优解。

我们要统计所有合法方案数。直接算“存在替换”的方案很复杂,我们转换思路,计算“绝不发生替换”的方案数,然后用总方案数减去它。

将所有糖果按原价 \(a_i\) 降序排序,枚举第一个被跳过的 2 元糖果 \(u\) 和 第一个买下的 1 元糖果 \(v\)。

以 \(u\) 为界,把前面的糖果分为:

  • 集合 \(A\):\(a_i \ge a_u\)。它们必定在 \(u\) 之前被考虑(无论 1元还是 2元)。

  • 集合 \(B\):\(a_i < a_u\) 且 \(2a_i > a_u\)。其中 1元的会在 \(u\) 前考虑,2 元的在 \(u\) 后考虑。

  • 集合 \(C\):\(2a_i \le a_u\)。它们必定在 \(u\) 之后被考虑。

在 \(u\) 之前的购买总花费必须恰好为\(m-1\)(因为要留 1 元给 \(v\))。
设 \(A\) 中选 \(k_A\) 个 2 元,\(B\) 中选 \(k_B\) 个 1 元,则必须满足:
\(k_A + k_B = m - 1 - N_A \quad \text{(其中 } N_A \text{ 为 } A \text{ 的大小)}\)

然后强制锁定危险的状态即满足上面的\(A1,A2\)和\(B1\)

替换发生的条件是 \(a_z < a_u - a_v\)。
令阈值 \(D = a_u - a_v\),定义危险集合 \(S = \{ i \in A \cup B \mid a_i < D \}\)。

只要\(S\)中有元素是 1元,它就会变成 \(z\),导致替换发生。

为了避免替换,必须强制\(S\)中的所有元素都取 2 元!

强制取 2 元后,对名额的消耗如下:

  • \(|A \cap S|\) 中的元素被迫变成 2 元,消耗了 \(|A \cap S|\)个 \(k_A\) 的名额。
  • \(|B \cap S|\) 中的元素被迫变成2 元,它们不再是1元,所以不消耗 \(k_B\) 的名额。

然后计算固定 \((u,v)\) 下的合法方案数

在 \(A\) 和 \(B\) 剩下的自由元素中,我们还需要选:

  • 总共需要的名额:\((m - 1 - N_A)\)
  • 因为 \(S\) 已经占用了 \(|A \cap S|\),所以剩余需要凑的名额为:\((m - 1 - N_A) - |A \cap S|\)。
  • 剩余自由元素总数为:\((N_A + N_B) - |S|\)。

所以 \(A\) 和 \(B\) 的分配方案数为:
\(C_{N_A + N_B - |S|}^{(m - 1 - N_A) - |A \cap S|}\)

最后就是\(v\) 之后的自由糖果
\(v\) 是 \(C\) 集合中第一个 1 元糖果。在它被买下后,钱就花光了。所以 \(v\) 之后的 \(C\) 集合中的糖果,无论定价是 1 还是 2 都不会被购买,也不影响前面的逻辑。
假设 \(v\) 之后有 \(c\) 个糖果,它们每个都有 1元或 2 元 2种选法,方案数要再乘以 \(2^c\)。

综上,固定 \((u,v)\) 的合法方案数为:
\(\left[ C_{N_A + N_B - |S|}^{(m - 1 - N_A) - |A \cap S|} \right] \times 2^c\)

把所有 \(u\) 和 \(v\) 的合法方案数加起来,这就是“贪心恰好正确”的方案数。
设总方案数为 \(2^n\)(每个糖果 (1) 或 (2) 两种可能)。
最终答案 = 总方案数 \(2^n\) - 所有 \((u,v)\) 对应的合法方案数之和。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
#define se second
#define fi first
using namespace std;
const int N=1e6+10;
const int mod=998244353;
const ll inf=0x3f3f3f3f3f3f3f3f;
ll maxx=N-10;
ll n,m,k,ans,cnt,rt;
ll a[N],b[N],po[N];
ll jie[N],ni[N];
ll read(){ll x;cin>>x;return x;}
ll ppow(ll n,ll m){ll s=1;while(m){if(m&1)s=s*n%mod;n=n*n%mod;m/=2;}return s;}
ll c(int n,int m){
	if(n<m||n<0||m<0)
		return 0;
	return jie[n]*ni[m]%mod*ni[n-m]%mod;
}
void solve(){
	cin>>n>>m;
	ll tot=0;
	for(int i=1;i<=n;i++)a[i]=read();
	sort(a+1,a+n+1,greater<ll>());
	for(int x=1;x<=n;x++){
		ll now=x+1;
		while(now<=n&&2*a[now]>a[x])now++;
		ll A=x-1,B=now-x-1,C=n-now+1;
		ll tmp=c(A+B,m-1-A);
		if(tmp==0)continue;
		vector<pair<ll,ll>>cnt;
		ll pra=1,prb=x+1;
		while(pra<=x-1&&prb<now){
			if(a[pra]>=a[prb]){
				cnt.pb({a[pra],0});
				pra++;
			}else{
				cnt.pb({a[prb],1});
				prb++;
			}
		}
		while(pra<=x-1){
			cnt.pb({a[pra],0});
			pra++;
		}
		while(prb<now){
			cnt.pb({a[prb],1});
			prb++;
		}
		ll ps=cnt.size()-1,na=0,nb=0;
		for(int i=0;i<=C;i++){
			ll v=0;
			if(i<C)v=a[now+i];
			ll D=a[x]-v;
			while(ps>=0&&cnt[ps].fi<D){
				if(cnt[ps].se==0)na++;
				else nb++;ps--;
			}
			ll g=c(A+B-na-nb,m-1-A-na);
			ll b=(tmp-g+mod)%mod;
			if(b>0){
				int nc=(i<C)?(C-1-i):0,nm=(b*po[nc])%mod;
				tot=(tot+nm)%mod;
			}
		}
	}
	ans=(po[n]-tot+mod)%mod;
	cout<<ans<<'\n';
}
int main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	jie[0]=1;
	for(int i=1;i<=maxx;i++)jie[i]=(jie[i-1]*i)%mod;
	ni[maxx]=ppow(jie[maxx],mod-2);
	for(int i=maxx;i;i--)ni[i-1]=(ni[i]*i)%mod;
	for(int i=0;i<=10000;i++){
		po[i]=ppow(2,i);
	}
	read();
	ll T=read();
	while(T--){
		solve();
	}
}
posted @ 2026-10-21 07:49  Build-xhz31  阅读(8)  评论(0)    收藏  举报