专栏文章
题解:CF2084E Blossom
CF2084E题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mink42ap
- 此快照首次捕获于
- 2025/12/02 03:43 3 个月前
- 此快照最后确认于
- 2025/12/02 03:43 3 个月前
简要题意
给定一个长度为 的“部分排列”数组 (元素来自 ,其中若干位置为 表示缺失,且所有非 元素互不相同),将所有 用缺失的数补成合法排列后,对每个形成的排列计算其所有非空子段的 mex 之和,并把这些和值对所有可能填充方案求和,结果对 取模输出(多测试;,每个用例 ,,非 的 两两不同,且所有用例的 之和 )。
题解
经过一些思考,会发现钦定一个区间的 mex 等于 不如钦定大于等于 (因为后者不用担心 mex 更大的问题,好处理),这样也可以求出答案,因为有 。
接下来考虑:做到大于等于一个 mex 值要填的数字个数是固定的。例如要做到 , 出现过且 未出现过,则一定是找区间内两个 填成 (要求1)、而且这个子区间必须包含 三个数字(要求二)。符合条件的子区间对答案的贡献是 ,之中 是子区间内 个数、 是原数组内 个数、 是大于等于目标 mex 值所至少需要的 个数(上例中为 ),式子的含义是在 个 中选择 个用来填缺失的数字、其余的 随意填写。问题变成了如何快速求这样的子区间个数。
定义 表示满足要求 的包含 个 的子区间个数,在最开始令 等于所有包含 个 的子区间个数。为了便于处理要求 ,我们按 mex 从小到大来处理,定义 是所有小于 mex 的已出现数字中最靠左与最靠右的下标,考虑到 时若 在原数组有出现则用其更新 ,更新 时可以通过暴力删除不再合法的子区间的方式来更新 数组,由于每个区间只会被删一次,所以这样做的复杂度正确。
C#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define per(i,a,n) for(int i=n;i>=a;i--)
#define pb push_back
#define SZ(v) ((int)v.size())
#define fs first
#define sc second
#define all(x) (x.begin()),(x.end())
typedef long long ll;
typedef double db;
typedef pair<int,int> pii;
const ll mod=1e9+7;
ll fac[5010],inv[5010];
ll qp(ll a,ll b,ll mod=mod){
ll ans=1;
while(b){
if(b&1) ans=ans*a%mod;
a=a*a%mod;b>>=1;
}return ans;
}
ll getInv(ll x,ll p=mod){
return qp(x,p-2,p);
}
ll getC(ll n,ll m,ll p=mod){
return fac[n]*inv[m]%p*inv[n-m]%p;
}
// 动机:做到大于等于一个mex值要填的数字个数是固定的。
// 例如要做到mex>=5,[1,2,4] 出现过且 [0,3] 未出现过,则一定是找两个 -1 填成 [0,3]、而且这个子区间必须包含 [1,2,4] 三个数字
// s[i] : 包含了当前的 [l,r] 作为子区间、且恰有 i 个 -1 的子区间个数
ll T,n,a[5010],pos[5010],pre[5010],s[5010];
int main(){
std::ios::sync_with_stdio(false);
cin.tie(0);
fac[0]=inv[0]=1;
rep(i,1,5005){
fac[i]=fac[i-1]*i%mod;
inv[i]=getInv(fac[i]);
}
cin>>T;
while(T--){
cin>>n;
memset(pos,0,sizeof(pos));
memset(s,0,sizeof(s));
rep(i,1,n){
cin>>a[i];
pos[a[i]]=i;
pre[i]=pre[i-1]+(a[i]==-1);
}
rep(i,1,n){
rep(j,i,n){
s[pre[j]-pre[i-1]]++;
}
}
int l,r;
l=r=-1; // [l,r] 是包含了 [1,mex-1] 中已确定位置的所有数的最小区间,因此要做到 mex 的区间一定要包含 [l,r]
ll ans=0,c=0; // c 表示 <mex 的有多少个全数组都未出现
rep(mex,1,n+1){
if(pos[mex-1]){
if(l==-1){ // 特判第一次加入出现过的数,将跨过他的区间贡献都减掉
rep(i,1,pos[mex-1]-1){
rep(j,i,pos[mex-1]-1){
s[pre[j]-pre[i-1]]--;
}
}
rep(i,pos[mex-1]+1,n){
rep(j,i,n){
s[pre[j]-pre[i-1]]--;
}
}
l=r=pos[mex-1];
}
else{
if(pos[mex-1]<l){
rep(i,pos[mex-1]+1,l){
rep(j,r,n){
s[pre[j]-pre[i-1]]--;
}
}
l=pos[mex-1];
}
else if(pos[mex-1]>r){
rep(i,1,l){
rep(j,r,pos[mex-1]-1){
s[pre[j]-pre[i-1]]--;
}
}
r=pos[mex-1];
}
}
}
else{
c++;
}
rep(k,c,pre[n]){
ans=(ans+s[k]*getC(k,c)%mod*fac[c]%mod*fac[pre[n]-c])%mod;
}
}
cout<<ans<<"\n";
}
return 0;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...