Educational Codeforces Round 186 C-F 个人题解
Educational Codeforces Round 186
C - Production of Snowmen
给定三个分别有 \(n\) 个元素的环 \(a,b,c\),现在要选择一个三元组 \((i,j,k),1\le i,j,k\le n\),使得 \((a_i,b_j,c_k),(a_{i+1},b_{j+1},c_{k+1}),\cdots,(a_{i+n-1},b_{j+n-1},c_{k+n-1})\) 都满足 \(a<b<c\),问合法的三元组个数。
\(1\le n\le 5\times 10^3\)。
先考虑只有两个环的情况怎么做。
先断环为链,不难想到先 \(O(n^2)\) 枚举起点再 \(O(n)\) 比较,总共 \(O(n^3)\)。
但会发现有很多重复计算的东西。由于这玩意类似滑动窗口,考虑换一下枚举的东西,我们先枚举两个起点 \(i,j\) 的距离 \(dis\),然后再枚举 \(i\)。你会发现当我们的 \(i\) 往后挪一位时,\(j\) 也同步挪一位,此时刚好有一对数进入了我们的滑动窗口,有一对数出去了,那我们只需要比较出去的这对和进来的这对就行了。这样下来时间复杂度是 \(O(n^2)\) 的。
于是我们可以求出来对于每个 \(j\),合法的二元组 \((j,k),b_j<c_k\) 的个数,这里设为 \(p_j\)。我们对 \(a\) 环和 \(b\) 环施以同样的比较方式,如果有两个起点 \(i,j\) 合法,我们为答案加上 \(p_j\) 即可。
总时间复杂度 \(O(n^2)\)。
ll T;
ll n,a[N*2],b[N*2],c[N*2];
ll p[N*2];
void solve(){
cin>>n;
for(ll i=1;i<=n;i++) p[i]=0;
for(ll i=1;i<=n;i++){ cin>>a[i]; a[i+n]=a[i]; }
for(ll i=1;i<=n;i++){ cin>>b[i]; b[i+n]=b[i]; }
for(ll i=1;i<=n;i++){ cin>>c[i]; c[i+n]=c[i]; }
for(ll dis=0;dis<n;dis++){
ll cnt=0;
for(ll i=1;i<=n;i++){
ll j=i+dis;
if(b[i]>=c[j]) cnt++;
}
if(!cnt) p[1]++;
for(ll i=2,j=dis+2;i<=n;i++,j++){
if(b[i-1]>=c[j-1]) cnt--;
if(j>n) j-=n;
if(b[i+n-1]>=c[j+n-1]) cnt++;
if(!cnt) p[i]++;
}
}
for(ll i=1;i<=n;i++) p[i+n]=p[i];
ll ans=0;
for(ll dis=0;dis<n;dis++){
ll cnt=0;
for(ll i=1;i<=n;i++){
ll j=i+dis;
if(a[i]>=b[j]) cnt++;
}
if(!cnt) ans+=p[dis+1];
for(ll i=2,j=dis+2;i<=n;i++,j++){
if(a[i-1]>=b[j-1]) cnt--;
if(j>n) j-=n;
if(a[i+n-1]>=b[j+n-1]) cnt++;
if(!cnt) ans+=p[j];
}
}
cout<<ans<<"\n";
}
D - Christmas Tree Decoration
现在有 \(n+1\) 个从 \(0\) 编号到 \(n\) 的盒子,每个盒子里有 \(a_i\) 个装饰品。有 \(n\) 个人,按照排列 \(p\) 的顺序,从 \(p_1\) 到 \(p_n\) 依次轮流拿装饰品挂上去,第 \(i\) 个人有两种选择:
- 盒子 \(0\) 里选一个礼物挂上去;
- 盒子 \(i\) 里选一个礼物挂上去。
如果没有出现一个人没有礼物可挂的情况,这个排列就是合法的,问合法的排列数,答案模 \(998244353\)。
\(1\le T\le 5000,1\le n\le 50\)。
你发现 \(0\) 号盒子可以看作一个备用盒子嘛,低保金属于是。
于是发现,每次等到第 \(i\) 个人对应的盒子里没有了再去拿 \(0\) 号盒子里的东西,一定不劣,相当于按需所取。于是题意转化为,求轮空次数 \(\le a_0\) 的排列数。
首先什么时候轮空次数最少?你会发现一定是 \(p_i\) 按照 \(a_{p_i}\) 从大到小的顺序排列,例如 3 3 2 1 1,你会发现这样子后面的 2 1 1 就可以少轮空一次。此时总轮空次数为
其中 \(maxa=\max a\)。
因此我们先把 \(a_1\sim a_n\) 排个序,这样不影响对答案的计数。
然后你会发现,每当有一个小于 \(maxa\) 的元素被丢到 \(maxa\) 的前面去,如 1 3 3 2 1 或 3 2 3 1 1,轮空次数就会加一,因此我们枚举轮空次数,设轮空次数最小值为上文的 \(p\),小于 \(maxa\) 的元素个数为 \(nmx\),等于 \(maxa\) 的元素个数为 \(mx\),则方案数可以推出来:
意思是:
- 最右边的 \(maxa\) 元素之前的所有元素可以乱排;
- 最右边的 \(maxa\) 元素有 \(mx\) 种选法;
- 右边的数可以乱排。
时间复杂度 \(O(Tn)\)。
void solve(){
cin>>n>>a0;
fac[0]=1;
for(ll i=1;i<=n;i++){
cin>>a[i];
fac[i]=fac[i-1]*i%P;
}
finv[n]=qpow(fac[n],P-2);
for(ll i=n-1;i>=0;i--) finv[i]=finv[i+1]*(i+1)%P;
sort(a+1,a+n+1,cmp);
ll mxcnt=0,nmxcnt=0,bas=0;
for(ll i=1;i<=n;i++){
if(a[i]==a[1]) mxcnt++;
else nmxcnt++,bas+=a[1]-1-a[i];
}
ll ans=0;
for(ll i=0;i+bas<=a0&&i<=nmxcnt;i++) ans=(ans+C(i,nmxcnt)*mxcnt%P*fac[mxcnt-1+i]%P*fac[nmxcnt-i]%P)%P;
cout<<ans<<"\n";
}
E - New Year's Gifts
看错题了。
小 M 有 \(n\) 个朋友,他要给他的每个朋友送一份新年礼物,他已经给他们分别选好了礼物,价值为 \(y_i\)。
同时,现在有 \(m\) 个免费的礼盒,每个礼盒颜值为 \(x_i\),一个礼物可以选择包装,也可以不包装。
一个朋友会开心,以下条件满足其一即可:
- 礼物价值至少 \(z_i(z_i>y_i)\);
- 礼盒颜值至少 \(x_i\)。
现在小 \(M\) 手上有 \(k\) 个金币,问最多能让多少朋友开心。
\(1\le n,m\le 2\times 10^5,\sum\limits_{i=1}^{n}y_i \le k\le 10^{15}\)。
先把底款 \(y_i\) 付了,操作一转化成加钱,加 \(z_i'=z_i-y_i\)。
首先这个礼盒居然是免费的,不用白不用,把这些礼盒分配给 \(z_i'\) 最大的那几个一定不劣,用 multiset 维护最小的大于等于其 \(x_i\) 的礼盒即可。剩下的钱 \(k'\) 从小到大给最小的几个 \(z_i'\) 加钱就行了。
ll n,m,k,vis[N];
multiset<ll> box;
struct Node{
ll x,z;
}a[N];
bool cmp(Node X,Node Y){
return X.z<Y.z;
}
void solve(){
box.clear();
cin>>n>>m>>k;
for(ll i=1;i<=m;i++){
ll pp; cin>>pp;
box.insert(pp);
}
for(ll i=1;i<=n;i++){
ll x,y,z; cin>>x>>y>>z;
z-=y,k-=y;
a[i]=(Node){x,z};
vis[i]=0;
}
sort(a+1,a+n+1,cmp);
ll ans=0;
for(ll i=n;i>=1;i--){
auto it=box.lower_bound(a[i].x);
if(it==box.end()) continue;
ans++,vis[i]=1;
box.erase(it);
}
for(ll i=1;i<=n;i++){
if(vis[i]) continue;
if(k>=a[i].z){
ans++;
k-=a[i].z;
}
}
cout<<ans<<"\n";
}
F - Christmas Reindeer
给定 \(n\) 只鹿,每只鹿能力值为 \(2^{c_i}\),有 \(m\) 次三种询问:
- 添加一只能力值为 \(2^x\) 的鹿;
- 移除一只能力值为 \(2^x\) 的鹿(\(x\) 均 \(\le 60\));
- 定义一群鹿的承载量如下,将它们的能力值从大到小排序,其总承载力为 \(S_0+\lfloor\frac {S_1}{2^1}\rfloor+\lfloor\frac {S_2}{2^2}\rfloor+\cdots+\lfloor\frac {S_{|S|}}{2^{|S|}}\rfloor\),问你有多少种选择方案使得鹿的承载力大于等于 \(x(1\le x\le 1\times 10^{18})\)。
\(n,m\le 3\times 10^5\)
首先你拿一个桶去存能力值,这里记作 \(\{cnt_i \}\)。
接着你考虑一种能力值的累计方案 \(r_i\) 使得其承载力刚好等于 \(x\),这里 \(r_i\) 表示能力值为 \(i\) 的有多少条鹿。
你会发现,只要字典序大于等于这玩意的累计方案,不都可以吗??因此我们对这个计数就行了,考虑枚举啥,自然想到枚举第一个大于 \(r_i\) 的位置 \(i\),此时这个累计方案被分为了三段。
\(\text{Part A}\):位置在 \(i\) 之前的。
随便选,设 \(sum_i\) 为 \(\sum\limits_{i=1}^n cnt_i\),则有 \(a=2^{sum_{i-1}}\)。
\(\text{Part B}\):位置 \(i\)。
此时要求这个位置选的鹿的个数必须大于 \(r_i\)。则有
但是这样有个问题,就是会枚举两次 \(\log V\),复杂度要炸。
因此换一下枚举的东西,正难则反,一个很巧妙的转化,就可以把时间复杂度通过 \(\sum r_i\) 弄到均摊的 \(\log V\)。
哦耶。
\(\text{Part C}\):位置在 \(i\) 之后的。
必须和 \(r_i\) 一致。
预处理后缀即可免去再套一层 \(\log V\)。
答案是 \(abc\)。
总复杂度 \(O(n\log V)\)。
ll qpow(ll a,ll k){
ll res=1;
while(k){
if(k&1) res=res*a%P;
a=a*a%P,k>>=1;
}
return res;
}
ll fac[N<<1],finv[N<<1];
ll C(ll a,ll b){
if(a>b) return 0;
return fac[b]*finv[a]%P*finv[b-a]%P;
}
ll T,n,m;
ll tong[70],r[70];
ll suf[70];
void solve(){
memset(tong,0,sizeof(tong));
cin>>n>>m;
for(ll i=1;i<=n;i++){
ll x; cin>>x;
tong[x]++;
}
while(m--){
ll op,x; cin>>op>>x;
if(op==1) tong[x]++;
else if(op==2) tong[x]--;
else{
for(ll i=0;i<=60;i++) r[i]=0;
ll tmp=0;
while(x) r[tmp++]=x&1,x>>=1;
tmp=0;
for(ll i=60;i>=0;i--) if(r[i]) r[i]=0,r[i+tmp]++,tmp++;
ll ans=0,sum=0;
suf[61]=1;
for(ll i=60;i>=0;i--) suf[i]=suf[i+1]*C(r[i],tong[i])%P;
for(ll i=0;i<=60;i++){
ll a=qpow(2,sum);
ll b=qpow(2,tong[i]);
for(ll j=0;j<=r[i];j++){
b=(b-C(j,tong[i])+P)%P;
}
ll c=suf[i+1];
ans=(ans+a*b%P*c%P)%P;
sum+=tong[i];
}
ll ans2=1;
for(ll i=0;i<=60;i++) ans2=ans2*C(r[i],tong[i])%P;
ans=(ans+ans2)%P;
cout<<ans<<"\n";
}
}
}

浙公网安备 33010602011771号