Atcoder Beginner Contest 200 E

传送门

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


题意

(1leq i, j, kleq 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)^idbinom{3}{i}cdot dbinom{S-icdot 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)^jdbinom{2}{j}cdot dbinom{S-i-jcdot 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;
}
原文地址:https://www.cnblogs.com/JustinRochester/p/14826783.html