Atcoder Beginner Contest 200 E - Patisserie ABC 2 题解

传送门

当想了半个多小时三维坐标系的菜比看到了组合数学题解


题意

\(1\leq i, j, k\leq N\)\(N^3\) 个三元组 \((i, j, k)\) 进行排序,排序时以总和为第一关键字,第一维为第二关键字,第二维为第三关键字,从小到大排。

询问第 \(K\) 个三元组


分析

如果用三维坐标系描述,不难想到,三元组分布在长宽高均为 \(N\) 的正方体上;将其按 \(x+y+z=C\) 进行切片,就能得到第一关键字;同理分析得到第二第三。

如果具有极其恐怖的空间想象能力,可以试试这个方法。反正我是不行

考虑 \(i+j+k=S\) 的方案数:

等效于 \(S\) 个小球塞入 \(3\) 个盒子,每个盒子非空且容积为 \(n\) ,问方案数。

不考虑 \(n\) 的限制,则方案数用隔板法得 \(\dbinom{S-1}{2}\)

现考虑有一个数超过 \(n\) 的限制,我们对它容斥:先分配给这个数 \(n\) ,再用隔板法分配得 \(\dbinom{S-n-1}{2}\);超过 \(n\) 的那个数有 \(\dbinom{3}{1}\) 种选法。

同理容斥两个数、三个数超过 \(n\) 限制的情况,得到最后方案数为:

\(\displaystyle \sum_{i=0}^3(-1)^i\dbinom{3}{i}\cdot \dbinom{S-i\cdot n-1}{2}\)

由于组合数下方的数字都较小,直接暴力展开可求,复杂度 \(O(1)\)

因为层数最高为 \(3n\) ,直接暴力枚举过去,找到在第几层即可,复杂度为 \(O(n)\)


求出第一关键字后,我们得到了 \(i+j+k=S\) 的答案,然后考虑求 \(i\)

我们直接从 \(1\)\(n\) 枚举 \(i\) ,则此时 \(j+k=S-i\) 的方案数:

等效于 \(S-i\) 个小球塞入 \(2\) 个盒子,每个盒子非空且容积为 \(n\) ,问方案数。

同上得到为: \(\displaystyle \sum_{j=0}^2(-1)^j\dbinom{2}{j}\cdot \dbinom{S-i-j\cdot n-1}{1}\)

同理直接展开,然后暴力枚举,复杂度为 \(O(n)\) 即可确定 \(i\)


确定 \(i\)\(S\) 后,我们直到 \(j+k=S-i\) ,则 \(j_{\min}=\max(S-i-n, 1)\)

当我们筛除上面的方案数,确定 \((i, j, k)\)\(j+k=S-i\) 的第 \(K\) 个后,则 \(j_K=j_{\min}+K-1\)

然后用 \(k=S-i-j_K\) 求出 \(k\) 输出即可。


代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN=3e6+10;
ll n, k, sum[MAXN];
inline ll calc3(ll k){
    ll res=(k-1)*(k-2)/2;
    k-=n;
    if(k<=0) return res;
    res-=(k-1)*(k-2)/2*3;
    k-=n;
    if(k<=0) return res;
    res+=(k-1)*(k-2)/2*3;
    k-=n;
    if(k<=0) return res;
    return res-(k-1)*(k-2)/2;
}
inline ll calc2(ll k){
    ll res=k-1;
    k-=n;
    if(k<=0) return res;
    res-=(k-1)*2;
    k-=n;
    if(k<=0) return res;
    return res+(k-1);
}
inline void work(){
    ll flr=3;
    while(sum[flr]<k)
        k-=sum[flr++];
    for(int i=1;i<=n;++i){
        ll res=calc2(flr-i);
        if(res<k){
            k-=res;
            continue;
        }
        ll x=max(flr-i-n, 1ll)+(k-1);
        cout<<i<<" "<<x<<" "<<flr-i-x;
        break;
    }
}
inline void pre(){
    cin>>n>>k;
    for(int i=1;i<=3*n;++i) sum[i]=calc3(i);
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    pre();
    work();
    cout.flush();
    return 0;
}
posted @ 2021-05-29 23:36  JustinRochester  阅读(132)  评论(0编辑  收藏  举报