牛客2025多校 R6
牛客2025多校 R6
C:
题目大意:
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 S_{n,k}=n!\) 可以依次计算出这些项,预处理后 \(O(1)\) 回答
L:
题目大意:
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:
题目大意:
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 转化为差分数组,即
考虑将所有数都加 \(k\) 的情况
可以发现差分数组部分的 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:
题目大意:
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}\)
最小情况是 \(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\)
所以满足题目条件的原矩阵 \(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_i+1\) 都至少存在 \(1\) 个球,那么总的方案数为 \(\binom{m+n(n+1)}{n(n+1)}=\binom{m+n(n+1)}{m}\)
时间复杂度为 \(O(m)\)
B:
题目大意:
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\) 的大小成正相关的
所以对于给定的 \(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
函数的判断没有影响,所以对可能超出整数范围内的大数维持一个相对较小的极大值即可