牛客2025多校 R6

牛客2025多校 R6

C:
题目大意:

image-20250806131124431

const int mod=998244353;

LL prod[500010];
LL k1[500010],k2[500010],k3[500010];

void init(){
	prod[0]=1;
	for (int i=1;i<=500000;i++) prod[i]=prod[i-1]*i%mod;
	
	for (int i=1;i<=500000;i++){
		k1[i]=(i*k1[i-1]%mod+prod[i-1])%mod;
		k2[i]=(i*k2[i-1]%mod+2*k1[i-1]%mod+prod[i-1])%mod;
		k3[i]=(i*k3[i-1]%mod+3*k2[i-1]%mod+3*k1[i-1]%mod+prod[i-1])%mod;
	}
	
}

void solve(){
	int n;
	cin>>n;
	cout<<k3[n]<<endl;
}

打表可以发现给定长度为 \(n\) 的排列,\(\sum f(P^\prime)\) 的结果为 \(\sum\limits_{k}(S_{n,k} \cdot k)\),那么 \(\sum f^3(P^\prime)=\sum\limits_{k}S_{n,k} \cdot k^3\)

其中 \(S_{n,k}\) 表示无符号第一类斯特林数,存在性质 \(S_{n,k}=(n-1)S_{n-1,k}+S_{n-1,k-1}\)

所以有这样的递推关系:

\[\sum f(P^\prime)=\sum\limits_{k}S_{n,k} \cdot k=(n-1)\cdot \sum\limits_{k}S_{n-1,k}\cdot k+\sum\limits_{k}S_{n-1,k-1}\cdot k\\ \because \sum\limits_{k}S_{n-1,k-1}\cdot k=\sum\limits_{j}S_{n-1,j}\cdot (j+1)=\sum\limits_{j}S_{n-1,j}\cdot j+\sum\limits_{j}S_{n-1,j}\\ \therefore j\to k: f(P^\prime)=n\cdot \sum\limits_{k}S_{n-1,k}\cdot k+\sum\limits_{k}S_{n-1,k-1} \]

同理可以依次递推出:

\[f^2(P^\prime)=\sum\limits_{k}S_{n,k}\cdot k^2=n\cdot\sum\limits_{k}S_{n-1,k}\cdot k^2+2\sum\limits_{k}S_{n-1,k}\cdot k+\sum\limits_{k}S_{n-1,k}\\ f^3(P^\prime)=\sum\limits_{k}S_{n,k}\cdot k^3=n\cdot\sum\limits_{k}S_{n-1,k}\cdot k^3+3\sum\limits_{k}S_{n-1,k}\cdot k^2+3\sum\limits_{k}S_{n-1,k}\cdot k+\sum\limits_{k}S_{n-1,k}\\ \]

根据性质 \(\sum S_{n,k}=n!\) 可以依次计算出这些项,预处理后 \(O(1)\) 回答

L:

题目大意:

image-20250806142856634

void solve(){
	int n,m;
	cin>>n>>m;
	vector<vector<int>> L(2*n+1);
	for (int i=0;i<m;i++){
		int l,r;
		cin>>l>>r;
		L[r].push_back(l);
	}
	priority_queue<int> q;
	int cnt=n;
	vector<char> ans(2*n+1);
	for (int i=2*n;i>=1;i--){
		for (auto it:L[i]) q.push(it);
		if (q.size()&&q.top()==i || i==cnt){
			if (i-1>2*(cnt-1)){
				cout<<-1<<'\n';
				return;
            }
			ans[i]='(';
			while (q.size()) q.pop();
			cnt--;
		}else ans[i]=')';
	}
	
	for (int i=1;i<=2*n;i++) cout<<ans[i];
	cout<<'\n';
}

需要构造一个字典序最小的序列,可以看作把序列 \((((\cdots)))\) 中的 '(' 移动到某个特定的位置上,只有在必要的时候移动 '(' ,可以保证最后构造的序列字典序一定最小

对于每个区间,记录下每个右端点对应的左端点的集合,从后往前移动指针,每次将指针对应的右端点的左端点集合全部加入优先队列

当走到一个点且这个点是优先队列的队头时,说明这个点必须填一个 '(' ,否则无法满足题意

这个点填了 '(' 后,包含这个点的所有区间都不需要再填 '(' 了,所以清空队列,设当前这个点的位置是 \(x\) ,这样的一个区间 \([l,r],x\in[l,r]\) 就不需要再考虑它填 '(' 的情况

充分必要性:\(r\) 端点在 \(x\) 之前已经考虑过,且 \(l\) 端点在优先队列内的次序小于 \(x\)

或者如果这个点的位置 \(x\) 等于还需要填 '(' 的数量,那么 \([1,x]\) 的这个区间必须全部填 '(' 否则不合法

并且如果 \(x\) 比两倍的还需要填 '(' 的数量还要多,那么一定不合法,因为这样的一段序列中 ')' 的数量大于 '('

K:

题目大意:

image-20250816145244527

void solve(){
	int n;
	cin>>n;
	vector<int> a(n+1,0);
	vector<int> d(n+1,0);
	for (int i=1;i<=n;i++) cin>>a[i];
	bool f=1;
	for (int i=1;i<n;i++)
		if (a[i]!=a[i+1]) f=0;
	if (f){
		cout<<0<<endl;
		return;
	}
	for (int i=1;i<=n;i++) d[i]=a[i]-a[i-1];
	int ans=0;
	for (int i=2;i<=n;i++) ans=gcd(ans,d[i]);
	
	auto check=[&](int x){
		int l=1,r=n;
		while (a[l]%x==0&&l<=n) l++;
		while (a[r]%x==0&&r>=1) r--;
		bool f=1;
		for (int i=l;i<r;i++) if (a[i]%x!=a[i+1]%x) f=0;
		return f;
	};
	
	for (int i=1;i<=sqrt(a[1]);i++){
		if (a[1]%i==0){
			if (check(i)) ans=max(ans,i);
			if (check(a[1]/i)) ans=max(ans,a[1]/i);
		}
	}
	for (int i=1;i<=sqrt(a[n]);i++){
		if (a[n]%i==0){
			if (check(i)) ans=max(ans,i);
			if (check(a[n]/i)) ans=max(ans,a[n]/i);
		}
	}
	cout<<ans<<endl;
}

根据欧几里得算法可以将多个数的 GCD 转化为差分数组,即

\[\mathrm{gcd}(a_1,a_2,\cdots,a_n)=\mathrm{gcd}(a_1,a_2-a_1\cdots,a_n-a_{n-1}) \]

考虑将所有数都加 \(k\) 的情况

\[\mathrm{gcd}(a_1+k,a_2+k,\cdots,a_n+k)=\mathrm{gcd}(a_1+k,a_2-a_1\cdots,a_n-a_{n-1}) \]

可以发现差分数组部分的 GCD 是没有变化的,所以为了使整体的 GCD 最大,我们使 \(a_1+k\) 凑为差分数组的倍数

那么最终可以得到的最大 GCD 为 \(\mathrm{gcd}(a_2-a_1\cdots,a_n-a_{n-1})\)

特别的,如果原数组中所有数都相同,显然可以达到正无穷

然后考虑部分区间加 \(k\) 的情况,两个端点 \(a_1,a_n\) 至少有一个不在区间内

实际上我们只用考虑区间 \([2,n],[1,n-1]\) 这两个部分都加 \(k\) 的操作:假如 \(a_1\) 不变,那么最终数组的 GCD 一定是 \(a_1\) 的某个因子,加入\(a_2\) 进来考虑不会使得答案变优

所以枚举 \(a_1\) 的每个因子 \(x\) ,对于每个因子我们需要通过区间加操作使得 \([2,n]\) 区间的 GCD 变成 \(x\)

维护两个端点 \(l,r\) 分别移动到 \([2,n]\) 区间内 \(a_i\) 不为 \(x\) 的倍数的两端

int l=1,r=n;
while (a[l]%x==0&&l<=n) l++;
while (a[r]%x==0&&r>=1) r--;

然后判断区间 \([l,r]\) 中所有的 \(a_i\) 是否都能通过加 \(k\) 的操作变为 \(x\) 的倍数(在模 \(x\) 的条件下是否相同)

bool f=1;
for (int i=l;i<r;i++) if (a[i]%x!=a[i+1]%x) f=0;

对于 \(a_n\) 不变的情况同理,最后答案取上述三种情况的最大值

D:

题目大意:

image-20250817204501606

const int mod=998244353;
 
LL ksm(LL a,LL b,LL p){
    LL res=1;
    while (b){
        if (b&1) res=res*a%p;
        a=a*a%p;
        b>>=1;
    }
    return res;
}
 
void solve(){
    LL n,m;
    cin>>n>>m;
    LL p=1;
    for (LL i=m+n*(n-1);i>n*(n-1);i--) p=p*(i%mod)%mod;
    LL d=1;
    for (LL i=1;i<=m;i++) d=d*(i%mod)%mod;
    LL ans=p*ksm(d,mod-2,mod)%mod;
    cout<<ans<<endl;
}

第四个条件可以变形得到 \(A_{i,l}-A_{i,j}\ge A_{k,l}-A_{k,j}\) ,对任意的 \(1\le i<k\le n,1\le j< l\le n\) 成立

定义对矩阵 \(A\) 进行按列差分得到的矩阵 \(D\) 满足 \(D_{i,j}=A_{i,j}-A_{i,j-1}\)

\[A_{i,l}-A_{i,j}\ge A_{k,l}-A_{k,j}\iff \sum_{\tau =l}^j D_{i,\tau} \ge \sum_{\tau =l}^j D_{i+1,\tau} \]

最小情况是 \(k=i+1,l=j+1\) ,那么对所有的 \(D_{i,j}\) 都有 \(D_{i,j}\ge D_{i+1,j}\)

根据题目的第三个条件 \(A_{i,j}\le A_{i,j+1}\),那么对所有的 \(D_{i,j}\) 都有 \(D_{i,j}\ge0\)

因为给定了 \(\forall i=[1,n],A_{i,1}=1\) ,所以通过唯一的 \(D\) 矩阵可以唯一确定矩阵 \(A\)

再根据题目的第一个条件 \(A_{i,j}\in [0,m]\) ,所以对所有的 \(D_{i,j}\) 都有 \(\forall i\in[1,n],\sum\limits_{j=2}^{n} D_{i,j} \le m\)

综上所述,对于矩阵 \(A\) ,我们都可以构造出他的一个唯一差分数组 \(D\) 满足性质:

  • \(D\) 中所有元素都大于等于 \(0\)
  • \(D\) 中每一行相加的和都不大于 \(m\)
  • \(D\) 中每一列上元素成非递增排列

再构造 \(D\) 的按列差分矩阵 \(D^\prime\) 满足 \(D^\prime_{i,j}=D_{i,j}-D_{i+1,j}\)\(D\)\(D^\prime\) 也是唯一对应的

满足性质:\(D^\prime_{i,j}\ge 0\)

\[\sum\limits_{i = 1}^n D^\prime_{i,j}=D_{1,j}\\ \sum\limits_{j = 2}^{n} \sum\limits_{i = 1}^n D^\prime_{i,j} = \sum\limits_{j = 2}^n D_{1,j} \le m \]

所以满足题目条件的原矩阵 \(A\) 的数量等于 \(D\) 的数目又等于 \(D^\prime\) 的数目

问题转化成计算满足矩阵所有元素和为 \(m\) 且元素都大于 \(0\) 的矩阵数目,求 \(n (n-1)\) 个非负整数的和小于 \(m\) 的方案数

设这 \(n\times (n-1)\) 个数为 \(x_i\)\(x_1+x_2+\cdots +x_{n(n-1)}=m\) ,等价于

\[(x_1+1)+(x_2+1)+\cdots +(x_{n(n+1)}+1) =m +n(n+1) \]

类似于隔板法的方式,每个部分 \(x_i+1\) 都至少存在 \(1\) 个球,那么总的方案数为 \(\binom{m+n(n+1)}{n(n+1)}=\binom{m+n(n+1)}{m}\)

时间复杂度为 \(O(m)\)

B:
题目大意:

image-20250821145251915

void solve(){
	int n,y,m;
	cin>>n>>y>>m;
	vector<vector<int>> a(n+1);
	vector<int> len(n+1);
	for (int i=1;i<=n;i++){
		cin>>len[i];
		a[i].resize(len[i]+1);
		for (int j=1;j<=len[i];j++) cin>>a[i][j];
	}
	
	auto judge=[&](int s,bool st)->bool{
		for (int i=1;i<=n;i++){
			LL sum=0;
			for (int j=1;j<=len[i];j++){
				if (a[i][j]>=s) return 1;
				sum=sum*s+a[i][j];
				sum=min(sum,(LL)1e9+1);
			}
			s=sum;
		}
		if (st)	return s<=y;
		return s<y;
	};
	
	int l=1,r=m+1;
	while (l+1!=r){
		int mid=l+r>>1;
		if (judge(mid,0)) l=mid;
		else r=mid;
	}
	int L=r;
	l=1,r=m+1;
	while (l+1!=r){
		int mid=l+r>>1;
		if (judge(mid,1)) l=mid;
		else r=mid;
	}
	int R=l;
	if (R<L) cout<<"-1 -1\n";
	else cout<<L<<' '<<R<<'\n';
}

发现每个 \(s\) 进行 \(n\) 次迭代后的结果是和 \(s\) 的大小成正相关的

\[Ls_0\le Rs_0\\ Ls_1=\sum_{i=1}^{k} a_i Ls_0^{k-i} \le Rs_1=\sum_{i=1}^{k} a_i Rs_0^{k-i} \]

所以对于给定的 \(m\) 满足条件的 \(s\) 的范围一定是一个区间 \([l,r]\) ,所以可以二分左右端点

\(l\) 是满足 \(s<y\) 的最后一个元素 \(+1\)\(r\) 是满足 \(s\le y\) 的最后一个元素

auto judge=[&](int s,bool st)->bool{
	for (int i=1;i<=n;i++){//进行n次迭代
		LL sum=0;
		for (int j=1;j<=len[i];j++){
			if (a[i][j]>=s) return 1;//判断是否合法
			sum=sum*s+a[i][j];
			if (sum>1e9){
                sum=1e9+1;
                break;
            }
		}
		s=sum;
	}
	if (st)	return s<=y;
	return s<y;
};

其中如果存在一个阶段导致 \(s\) 大于 \(\mathrm{max}(m)=1e9\) ,就可以不用继续往下进制转换了,而是维持一个极大值 \(1e9+1\)

因为能使得 \(s\) 变小的唯一途径就是下一轮迭代时 \(A_{i+1}\) 仅有一个元素,否则 \(s\) 仍然会继续增大

如果迭代结束时 \(s\)\(y\) 还要大(仍然维持这个极大值),那么这次二分的 \(s_0\) 一定不行,换句话说不在 \(m\) 范围内的 \(s\)judge 函数的判断没有影响,所以对可能超出整数范围内的大数维持一个相对较小的极大值即可

posted @ 2025-08-21 22:29  才瓯  阅读(10)  评论(0)    收藏  举报