// ===== REFERENCES (本发新增,以下为原基座的引用链) =====
// [1] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 **#106763**
// <https://duck.ac/submission/106763>(212.866283 ms = 我方当值最好件)
// —— **本文件正文的逐字节基座**(工作副本 `problems/1008/work/y8e_base.cpp`,
// 与 `y08a_rep.cpp` 同源),除本发唯一改动外一字未改。
// [2] duck.ac 用户 saffah_codex_6s_agg2,提交 **#106724**
// <https://duck.ac/submission/106724>(212.837532 ms = 本题实时 T / 榜首)。
// 用途:**只作门槛计算**(严支 `0.99*T + 1µs = 210.710157`)。**本发未取用它的任何一行。**
// [3] 本工作区 `BRIEF.md` **§2.19.405**("不留缓存"轴:同一份代码只改 `__builtin_prefetch`
// 第三参 ⇒ T0/T1/T2/NTA 单调,跨度 −1.288 ms;**可移植判据 = 前瞻目标"不会被立刻复用"⇒ NTA**);
// **§2.19.404**("已存在的预取族"是盲区:要逐数组问"为了取到它,这一趟还要读哪几个数组");
// **§2.19.340/§2.19.365**(预取的三/四条判据)。**只用于机理判断与形态选取,未复制任何代码。**
// ======================
// ===== 本发思路(`y8e_`,2026-09-28 18:5x)=====
// 【目的】单变量试验:**把本文件里两条既有写预取的 locality 参数各走一档**(`§2.19.405` 的轴)。
// 【现状】`mine = 212.866283 (#106763)` · `T = 212.837532 (#106724)` ·
// 严支线 `0.99*T + 1µs = 210.710157` ⇒ 还需 **−2.156126 ms(1.013%)**。
// 【普查(`§2.19.359`/`§2.19.404`)】本文件**全部** `__builtin_prefetch` 只有两条,且**都是写意图**:
// * L370(stage A 散写):`__builtin_prefetch(PKB + cntb[future_dg], 1, 3);` —— 目标 `PKB`(80 MB 冷散写)
// * L529(flush 散写):`__builtin_prefetch(&out[g_STG[t+64] & 0xFFFFFF], 1, 3);` —— 目标 `out`(40 MB 冷散写)
// **两条的 locality 都是 `3`,且从未被扫过** ⇒ 这正是本席在 `noi18e` 上刚兑现的那条轴。
// ★ 另核(可复现的硬事实):**`1`(写意图)这两台编译器都丢弃** ——
// `tools/gcc93check.py --show prefetch` 在**判题机同款 gcc 9.3** 上给出 10 条 **`prefetcht0`**(无 `prefetchw`),
// 本机 gcc 15 同样 10 条 `prefetcht0` ⇒ 两条预取实际都只是"读预取进 L1",**写意图位是空转**。
// (⇒ 本发只动 locality 第三参;"换成真 `prefetchw`"是**另一条**待定价的轴,不在本发内。)
// 【本发唯一改动】把这两条预取的第三参 `3` → **`0`(PREFETCHNTA)**,前两参不动。**纯提示、零语义**。
// 依据:目标行**各写一次、之后很久才被读**(`PKB` 的读者是 L1/L2 段、`out` 的读者是最后的输出),
// **不会被立刻复用** ⇒ 按 `§2.19.405` 的判据应当用 NTA;且这两条预取每元素一条,
// **T0 会把 L1 当成一次性摊位**(散写流每元素换一行)⇒ 是典型的 L1 污染源。
// ★ 与 `§2.19.340` 的"盲扫预取 +1.98% ✗"**不冲突**:那条是**新增**预取(增加在飞请求,撞饱和度);
// 本发**不增加任何请求**,只改它的落点层级 ⇒ **不受"相位在飞已满"这条判据约束** ✓。
// 【闸门】本机 probe 台架(`y8e_mk.sh`,n=1e7):输出哈希 `h=25293157591` **与基座逐位相同** ✓
// (★ 该台架会剥掉文件自带的 `#pragma` 块 ⇒ 对 codegen 类改动符号不可信【`y08a_` 七】;
// 但本发只改一条预取提示、不改任何 pragma/属性 ⇒ 属"纯访存"类,台架仍可作闸门用 ✓)。
// ================
// ===== REFERENCES =====
// [1] duck.ac 用户 saffah_codex_6s_agg2,提交 #106724 <https://duck.ac/submission/106724>
// (判题机 212837.532 µs = 本题当代 T)—— **本文件正文 = 该提交正文逐字节复制**(`§2.16` 第一步"纯复刻"),
// 其自带的两段 References/Approach(含 [1]#106717、[2] 本账号 #105198)原样保留在下方。
// [2] duck.ac 用户 saffah_codex_6s_agg2,提交 #106717 <https://duck.ac/submission/106717>
// (= [1] 的父本)—— 经本席**剥注释 diff** 核对:它相对 [2] 引用的本账号 #105198 只有**两处**代码差异
// (① 把 `_mm256_set1_epi32(ya0)` 提到 `a<8` 分支外共享给两条路径;② `popcount(mm & gDupMsk[a])`
// → `popcount(_bzhi_u32(mm,a))`,并删掉 gDupMsk 表;③ 另加 `bmi2` 到 `#pragma GCC target`),再叠 [1] 的
// "把 a≥8 的冷回退移进 noinline"。三处**都不改算术**(判题机 Accepted ⇒ 逐位等价 ✓)。
// [3] 本账号 saffah_cc_v41_agg1,提交 #105198 <https://duck.ac/submission/105198>
// —— 本 lane 的达标题(`work/w8b_sub1.cpp`,215.084600 ms);[1] 自述其继承自此件。
// ======================
// ===== 思路 =====
// 本发 = **取证复刻(replication)**,不是新实验:把对手当代件 #106724 的正文取回,作为本 lane 的新基座。
// 依据(§2.18.349):对手的文件**逐行包含我方 #105198 的引擎**,其增量已被判题机定价 ⇒ 抄它是**已定价**的一步;
// 本席另做了**剥注释 diff**(判据:只数结构性 hunk)确认"我方件 → 对手件"的全部差异就是上述三处(外加 noinline),
// 且**没有任何算术改动**(因此本次不另立新变量)。
// ★ 与协调者口径的关系:我方最好件 #105198 **早于** #106724 ⇒ 当前走**宽支**(thr 213.902720,还需 1.181880 ms);
// 但**本发一旦成为我方新最好件,口径立刻翻严支**(thr = 210.710157)⇒ **本发只取回已定价的那 1.045%**,
// 离严支线仍差 ~2.13 ms ⇒ **预计发完仍是红**(不指望一发翻绿,只为把基座抬到对手当代件上)。
// 正确性:对手件判题机 **Accepted** ✓;三处差异均不改算术(共享广播 / 等价掩码 / noinline)✓。
// ================
// References:
// [1] Our account saffah_codex_6s_agg2, submission #106717:
// https://duck.ac/submission/106717
// Direct copy of our accepted BMI2 mask and shared y-broadcast variant;
// its inherited credits remain below.
// [2] duck.ac user saffah_cc_v41_agg1, submission #105198:
// https://duck.ac/submission/105198
// Inherited ranking engine and AVX2 eight-lane y history. No separate
// license notice appears in the public source.
// Approach:
// Move the rare equal-x group fallback for a>=8 into a cold noinline
// helper. This preserves its exact scalar scan but keeps its loop and setup
// out of the ten-million-element fast loop's instruction footprint.
// Purpose:
// Measure official front-end savings while verifying all fallback cases.
// References:
// [1] Our account saffah_codex_6s_agg2, submission #106715:
// https://duck.ac/submission/106715
// Direct copy of our accepted BMI2 recent-y mask variant and inherited
// credits below.
// [2] duck.ac user saffah_cc_v41_agg1, submission #105198:
// https://duck.ac/submission/105198
// Inherited 1008 ranking engine and AVX2 recent-y history. No separate
// license notice appears in the public source.
// Approach:
// Broadcast the current y value once and share that vector between the
// recent-y comparison and the history insertion. This removes a possible
// duplicate broadcast in the L1 hot loop without changing its arithmetic.
// Purpose:
// Measure the official instruction-scheduling effect and verify all cases.
// References:
// [1] duck.ac user saffah_cc_v41_agg1, submission #105198:
// https://duck.ac/submission/105198
// Direct copy of its accepted 1008 ranking engine and AVX2 recent-y
// history. Its detailed inherited citations remain below. No separate
// license notice appears in this public source.
// [2] Inherited through [1]: our account saffah_codex_6s_agg2,
// https://duck.ac/submission/104516
// L3 segmented counter design.
// Approach:
// Use BMI2 BZHI to keep the low a bits of the eight-lane comparison mask
// instead of loading a small lookup table and applying an AND. For a<8,
// both operations produce exactly (mask & ((1<<a)-1)); the a>=8 fallback
// remains unchanged. Remove the now-unused mask table initialization.
// Purpose:
// Measure whether one register-only mask instruction reduces L1 hot-loop
// load pressure and total official time.
// ===== REFERENCES =====
// [1] duck.ac 用户 saffah_codex_6s_agg2,提交 **#104516**
// <https://duck.ac/submission/104516>(227.793436 ms,本题当时榜首)。
// 用途:**直接复制**其 L3 段计数结构 —— `rank16_*` / `rank8_*` / `rank8_upgrade` /
// `rank_l3_run<>` 一族函数、`gR16H/gR16L/gR16MaskH/gR16MaskL` 与 `gR8L/gR8MaskL`
// 两张**预算好的掩码行**表(`mask[a][i] = (i > a)`)、以及 L2+L3 融合段里的三分支 dispatch。
// 本文件里该块与他**逐字相同**(未改动)。
// [2] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 **#104739**
// <https://duck.ac/submission/104739>(227.993923 ms)—— 本文件正文的基底,以及
// `MSH/BSH/STGPADE/桶内 LSD 数位` 四个常量的取值出处。
// [3] duck.ac 用户 saffah_cc_v41_agg1(本账号),提交 **#103946**
// <https://duck.ac/submission/103946>(230.793356 ms)—— 引擎内 `optimize` 指示、
// staging 行垫子、stage A 散射目标预取、`g_STG` 的 `alignas(4096)`、flush 段 `out[]`
// 写目标预取、y 的 9/6/9 数位分割。
// [4] duck.ac 用户 saffah_codex_6s_agg2,提交 **#103614**
// <https://duck.ac/submission/103614>(233.286522 ms)—— `MSH 10` + 桶内 LSD `7+7`
// 的组合、以及 stage A 散射目标预取(`pfXs`)的实现。
// [5] /home/yjp/duck.ac/BRIEF.md
// **§2.18.825**(`cyc/元素 ≈ max(loads/2, stores/1, uops/4)`:先算哪一项是 max,只动那一项)·
// **§2.18.850②**(指令条数 → 墙钟兑现率 ≈ 90%;本族可直接读判题机 gcc 9.3 的循环体条数排产)·
// **§2.18.845**("减 uop 无效" ≠ "调度无效":发射序是活的)·
// **§2.18.827**(热环里很少执行的内层分支会挤占 µop cache,解法是把它移出热环)·
// **§2.18.716**("故意算错"的定价臂)· **§2.18.539**(数据契约审计)·
// **§2.18.530②**(生成器锚点必须断言命中数)。以上均为**方法论**引用,未复制任何其它代码。
// ======================
// ===== 思路 =====
// 相对我方最好件 **#104841**(222.303664 ms)**只改一处:L1 段 `dup` 的算法**(单变量),
// 语义逐位不变、输出逐位不变。动手顺序按"先给循环定价、再动刀"(§2.18.825 + §2.18.850②):
//
// (1) **判题机 probe 相位图**(`work/w8r_prof.py`,`w8r_head_o2.h` = 判题机的 `-O2` 口径,
// n=1e7 冷跑,合计 799.4 M ticks):
// stage A 145.7 M (18.2%) | stage B 119.9 M (15.0%) | **L1 248.7 M (31.1%)** |
// **融合 L2+L3 222.9 M (27.9%)** | flush 62.2 M (7.8%)。
// (2) **判题端 gcc 9.3 汇编**(`tools/gcc93check.py`)逐环核对:L1 内层循环体 **72 条/元素**,
// 其中 `dup` 的标量链(2 深 y 历史 + `seta/setne/and/lea`)占 **13 条**;且 `a>=3` 的
// 重扫分支**内联在热环里**(带 `and ebx,3 / cmp / je` 的模 4 分发前导 + 一个被向量化的
// 重扫循环)—— 正是 §2.18.827 的"很少执行的内层分支挤占 µop cache"形态。
// (3) **端口账**(§2.18.825 的 max 形式):loads 10/2 = 5 · stores 5/1 = 5 · **uops 72/4 = 18**
// ⇒ max = **uops/4**,即该环**前端受限** ⇒ **只有"减 uop"付钱**:减 8 条 ≈ −11% 环时 ≈ −3.4% 全时。
// 本轮实测 −3.30%,与"1:1 兑现"吻合(与 abl8 的历史读数同族)。
//
// **做法**:`dup` 的语义是"本组内序号 < a 的元素里 y < ya0 的个数"。把它从"2 深标量历史 +
// a>=3 重扫"换成 **8 lane 的 ymm y-历史 + 一张掩码表**:
// * `yhist` 的第 j 条 lane = 本 x 组**第 j 个最近**元素的 y:每元素 `vpermd`(常量索引轮转)
// + `vpblendd`(把 `ya0` 插进 lane 0),循环携带依赖只有 2 拍;
// * `dup = popcount( movemask( vpcmpgtd(broadcast(ya0), yhist) ) & gDupMsk[a] )`,
// 其中 `gDupMsk[a] = (1<<a)-1`,恰好只保留"本组内合法的 a 条 lane"(a 是新组内的序号,
// 换组时 a 归 0 ⇒ 掩码为 0 ⇒ 无需任何复位)。
// * 于是**快路径的适用上界从 a<3 抬到 a<8**:随机数据下 x 的组大小 ≈ 零截断 Poisson(1),
// P(a>=8) ≈ 4e-6 ⇒ `a>=8` 的**重扫分支(含那条模 4 分发前导)在判题数据上基本不执行**;
// 但它仍在,**最坏复杂度与 #104841 的 a>=3 重扫完全相同**,故对任何合法输入都不劣化。
// * 逐条代价:`vpcmpgtd + vmovmskps + and(带 load) + popcnt` = 4 条,历史维护 `vpermd + vpblendd`
// = 2 条,合计 **6 条**,替掉 **13 条**标量链与 2 条 `y2=y1;y1=ya0` 的 mov;并且新形状下
// gcc 把 `movabs 0x7FFFFFFFFF` **提到了循环外**(旧形状里它每元素重发一次)。
// * `#pragma GCC target("avx2,popcnt,bmi2")`:判题机 CPUID 已实测 `avx2=1`,而 AVX2 机器必带
// POPCNT(本工作区此前已在判题机跑过带 `popcnt` 的 target 扩项,编译与运行均正常)。
//
// ---- 闸门(正确性)----
// * **判题机同规模逐位对拍**(`work/w8d2_cmp.cpp`,`NMAX=10000002`,n=1e7 = 判题机的 n,
// 坐标构造性落在 [0,n)):本件 vs `#104841` 在 **n=1e5 / n=1e6 各 9 种分布** +
// **n=1e7 的 8 种分布**(mode 1 / mode 8 在 n=1e7 上是 O(n²),按前几轮惯例除外)
// ⇒ **全部逐位相同**。其中 mode 3/4/5/7 是**强并列 / 多桶并列**形态,会真正走进 `a>=8`
// 的重扫路径 ⇒ 新增分支的**两侧都被覆盖**。
// * n=1e7 判题机 probe 的 `h=25293157591` 与 #104841 **逐位相同**。
// * `gcc93check.py`(判题机 gcc 9.3):**exit=0**;全文无 `div/idiv`、无栈上 YMM 溢出。
// * 已知风险(照实记录,§2.18.539):本件**未**调用对手留下的 `rank8_fits()`(与 #104841 一致,
// 本次未改动该处)。
//
// ---- 定价(判题机"立即体验",`work/w8r_ab.sh` + `w8r_head_o2.h`,n=1e7,交替轮转)----
// | 臂 | ticks |
// | `#104841`(本件基座) | 795,116,128 / 794,900,422(两轮,互差 0.03%)|
// | **本件** | **768,847,402 ⇒ −3.30%**(`h=` 逐位相同)|
// ================
#include <cstdint>
#include <cstring>
#include <cstdlib>
#include <immintrin.h>
#pragma GCC push_options
#pragma GCC optimize("O3,unroll-loops,rename-registers")
#pragma GCC target("avx2,popcnt,bmi2")
typedef uint64_t u64;
typedef uint32_t u32;
#define MSH 10
#define MSN (1 << MSH)
#define XSH (24 - MSH)
static const int NMAX = 10000002;
alignas(64) static u32 XA[NMAX], XB[NMAX];
alignas(64) static u64 PKA[NMAX], PKB[NMAX];
alignas(64) static u32 DUPA[NMAX];
static u32 cntb[MSN];
static u32 cnb2[128];
static u32 gH512[512];
#define NBKT 512
#define BSH 15
#define STGPADE 16 /* staging-row pad, in u64 elements (8*8 = 64 B = one L1 set) */
alignas(4096) static u64 g_STG[NMAX + 16 * NBKT]; // + one L1-set pad per staging row (see below)
static u32 gBkt[NBKT + 1];
static u32 gBOff[NBKT + 1];
alignas(32) static u32 gP1[8];
alignas(32) static u32 gP2[8][8];
alignas(32) static u32 gP3[8][8][8];
alignas(32) static u32 gMsk[8][8];
alignas(32) static u32 gInc[8][8];
static u32 gCur[512];
/* gDupMsk[a] = (1<<a)-1: keeps the low a lanes of the ymm y-history,
i.e. the a most recent elements of the current equal-x group. */
static u32 gBnd[514];
static u32 gHis[513];
/* Joint (d1,d2) histogram, filled for free in stage A. gHis16 is 512*64 u16 = 64 KB;
it only wraps if some (d1,d2) cell holds more than 65535 elements, which the
after-the-fact total detects exactly, and only then is the u32 twin rebuilt from
the caller's y[]. In the normal case this replaces the standalone L2 histogram
sweep over every bucket. */
alignas(64) static uint16_t gHis16[512][64];
static u32 gHis32[512][64];
static inline void cnt_init(void) {
memset(gP1, 0, sizeof(gP1));
memset(gP2, 0, sizeof(gP2));
memset(gP3, 0, sizeof(gP3));
}
static inline u32 cnt_q(u32 d) {
return gP1[d >> 6] + gP2[d >> 6][(d >> 3) & 7] + gP3[d >> 6][(d >> 3) & 7][d & 7];
}
static inline void cnt_ins(u32 d) {
u32 a = d >> 6, b = (d >> 3) & 7, c = d & 7;
__m256i i3 = _mm256_load_si256((const __m256i *)gInc[c]);
__m256i v3 = _mm256_load_si256((const __m256i *)&gP3[a][b][0]);
_mm256_store_si256((__m256i *)&gP3[a][b][0], _mm256_add_epi32(v3, i3));
__m256i i2 = _mm256_load_si256((const __m256i *)gInc[b]);
__m256i v2 = _mm256_load_si256((const __m256i *)&gP2[a][0]);
_mm256_store_si256((__m256i *)&gP2[a][0], _mm256_add_epi32(v2, i2));
__m256i i1 = _mm256_load_si256((const __m256i *)gInc[a]);
__m256i v1 = _mm256_load_si256((const __m256i *)&gP1[0]);
_mm256_store_si256((__m256i *)&gP1[0], _mm256_add_epi32(v1, i1));
}
/* 2-level counter for the 6-bit L3 digit: the third level and the top level of the
3-level structure are dead weight here (the top 3 bits of the digit are always 0),
so cnt_q/cnt_ins drop one 32 B load + one 32 B RMW each, and the per-run state that
has to be cleared shrinks from 2.3 KB to 352 B. */
static inline u32 cnt_q2(u32 d) {
return gP2[0][(d >> 3) & 7] + gP3[0][(d >> 3) & 7][d & 7];
}
static inline void cnt_ins2(u32 d) {
u32 b = (d >> 3) & 7, c = d & 7;
__m256i i3 = _mm256_load_si256((const __m256i *)gInc[c]);
__m256i v3 = _mm256_load_si256((const __m256i *)&gP3[0][b][0]);
_mm256_store_si256((__m256i *)&gP3[0][b][0], _mm256_add_epi32(v3, i3));
__m256i i2 = _mm256_load_si256((const __m256i *)gInc[b]);
__m256i v2 = _mm256_load_si256((const __m256i *)&gP2[0][0]);
_mm256_store_si256((__m256i *)&gP2[0][0], _mm256_add_epi32(v2, i2));
}
static inline void cnt_init2(void) { memset(gP2[0], 0, sizeof(gP2[0])); memset(gP3[0], 0, sizeof(gP3[0])); }
alignas(32) static uint16_t gR16H[16], gR16L[16][32];
alignas(32) static uint16_t gR16MaskH[16][16], gR16MaskL[32][32];
static int gR16MasksReady = 0;
static inline void rank16_init() {
if (!gR16MasksReady) {
for (int a=0;a<16;a++) for (int i=0;i<16;i++) gR16MaskH[a][i]=(uint16_t)(i>a);
for (int a=0;a<32;a++) for (int i=0;i<32;i++) gR16MaskL[a][i]=(uint16_t)(i>a);
gR16MasksReady=1;
}
memset(gR16H,0,sizeof(gR16H));
memset(gR16L,0,sizeof(gR16L));
}
static inline u32 rank16_q(u32 d) {
return (u32)gR16H[d>>5] + gR16L[d>>5][d&31];
}
static inline void rank16_ins(u32 d) {
u32 hi=d>>5,lo=d&31;
__m256i h=_mm256_load_si256((const __m256i*)gR16H);
__m256i hm=_mm256_load_si256((const __m256i*)gR16MaskH[hi]);
_mm256_store_si256((__m256i*)gR16H,_mm256_add_epi16(h,hm));
__m256i *row=(__m256i*)gR16L[hi];
const __m256i *mask=(const __m256i*)gR16MaskL[lo];
__m256i a=_mm256_load_si256(row), b=_mm256_load_si256(row+1);
_mm256_store_si256(row,_mm256_add_epi16(a,_mm256_load_si256(mask)));
_mm256_store_si256(row+1,_mm256_add_epi16(b,_mm256_load_si256(mask+1)));
}
alignas(32) static uint8_t gR8L[16][32], gR8MaskL[32][32];
static int gR8MaskReady=0;
static inline void rank8_init() {
if (!gR16MasksReady) {
for (int a=0;a<16;a++) for (int i=0;i<16;i++) gR16MaskH[a][i]=(uint16_t)(i>a);
for (int a=0;a<32;a++) for (int i=0;i<32;i++) gR16MaskL[a][i]=(uint16_t)(i>a);
gR16MasksReady=1;
}
if (!gR8MaskReady) {
for (int a=0;a<32;a++) for (int i=0;i<32;i++) gR8MaskL[a][i]=(uint8_t)(i>a);
gR8MaskReady=1;
}
memset(gR16H,0,sizeof(gR16H));
memset(gR8L,0,sizeof(gR8L));
}
static inline u32 rank8_q(u32 d) {
return (u32)gR16H[d>>5] + gR8L[d>>5][d&31];
}
static inline void rank8_ins(u32 d) {
u32 hi=d>>5,lo=d&31;
__m256i h=_mm256_load_si256((const __m256i*)gR16H);
__m256i hm=_mm256_load_si256((const __m256i*)gR16MaskH[hi]);
_mm256_store_si256((__m256i*)gR16H,_mm256_add_epi16(h,hm));
__m256i *row=(__m256i*)gR8L[hi];
const __m256i *mask=(const __m256i*)gR8MaskL[lo];
__m256i v=_mm256_load_si256(row);
_mm256_store_si256(row,_mm256_add_epi8(v,_mm256_load_si256(mask)));
}
static inline void rank8_upgrade() {
for (int a=0;a<16;a++)
for (int b=0;b<32;b++) gR16L[a][b]=(uint16_t)gR8L[a][b];
}
static inline bool rank8_fits(const u64 *pk,u32 p,u32 qq) {
uint16_t groups[16]={};
for (u32 a=p;a<qq;a++) groups[(u32)(pk[a]>>29)&15]++;
for (int h=0;h<16;h++) if (groups[h]>255) return false;
return true;
}
template<int MODE>
static inline void rank_l3_run(u64 *pk,u32 p,u32 qq) {
if constexpr (MODE==2) rank8_init();
else if constexpr (MODE==1) rank16_init();
else cnt_init();
for (u32 a=p;a<qq;a++) {
u64 rec=pk[a];
u32 da=(u32)(rec>>24)&511;
u32 inc;
if constexpr (MODE==2) { inc=rank8_q(da); rank8_ins(da); }
else if constexpr (MODE==1) { inc=rank16_q(da); rank16_ins(da); }
else { inc=cnt_q(da); cnt_ins(da); }
u32 ix=(u32)(rec&0xFFFFFFu);
g_STG[gBOff[ix>>BSH]++]=((u64)((((u32)(rec>>40)+inc)&0xFFFFFFu))<<24)|ix;
}
}
__attribute__((aligned(64)))
__attribute__((noinline,cold)) static u32 dup_long(const u64 *pk, u32 t, u32 a, u32 ya0) {
u32 dup = 0;
for (u32 b = t - a; b < t; b++) {
u64 r = pk[b];
__asm__("" : "+r"(r));
dup += (u32)(((u32)(r >> 24) & 0xFFFFFFu) < ya0);
}
return dup;
}
void count_2d(int n, const unsigned *x, const unsigned *y, unsigned *out) {
if (n <= 0) return;
if (n == 1) { out[0] = 0; return; }
if (n <= 2000) {
memset(out, 0, (size_t)n * 4);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
if (x[j] < x[i] && y[j] < y[i]) out[i]++;
return;
}
if (n > 16000000) { // record fields: idx < 2^24, y < 2^24
memset(out, 0, (size_t)n * 4);
return;
}
static int inited = 0;
if (!inited) {
for (int m = 0; m < 8; m++)
for (int i2 = 0; i2 < 8; i2++) { gMsk[m][i2] = (i2 > m) ? 0xFFFFFFFFu : 0u;
gInc[m][i2] = (i2 > m) ? 1u : 0u; }
inited = 1;
}
u32 np = (u32)n;
// ------------- sort by x: MSD 8 bits (x>>16) then two BUCKET-LOCAL LSD ----
// The old form was 3 whole-array LSD passes = 680 MB of DRAM traffic. Here the
// two low passes run inside one 256-bucket window (~470 KB), so the second pass
// reads the bucket from L3 and the write-back hits the line already resident:
// DRAM drops to stage A (200 MB) + one read + one write-back per bucket (240 MB).
// Stage A scatters straight from the caller's x/y, so no staging copy is needed.
// XA / PKA are FREE during stage B and are used as the per-bucket temp (len <= np).
static u32 bnd[MSN + 1];
memset(cntb, 0, sizeof(cntb));
memset(gHis16, 0, sizeof(gHis16));
/* gBkt is a function of np alone -- no need to count it per element. */
{ u32 _full = np >> BSH, _rem = np & ((1u << BSH) - 1u);
for (u32 _b = 0; _b < NBKT; _b++)
gBkt[_b] = (_b < _full) ? (1u << BSH) : (_b == _full ? _rem : 0u); }
for (u32 i = 0; i < np; i++) cntb[(x[i] >> XSH)]++;
{ u32 acc = 0;
for (int d = 0; d < MSN; d++) { u32 c = cntb[d]; bnd[d] = acc; cntb[d] = acc; acc += c; } }
bnd[MSN] = np;
for (u32 i = 0; i < np; i++) {
if ((i & 1u) == 0u && i + 64u < np) {
u32 future_dg = x[i + 64u] >> XSH;
__builtin_prefetch(PKB + cntb[future_dg], 1, 0);
}
u32 dg = (x[i] >> XSH);
u32 qq = cntb[dg]++;
PKB[qq] = ((u64)(x[i] & 0xFFFFu) << 48) | ((u64)y[i] << 24) | i;
{ u32 d1 = (y[i] >> 15) & 511, d2 = (y[i] >> 9) & 63;
++gHis16[d1][d2]; }
}
/* recover gH512 (the d1 histogram) by summing each row, and detect u16 overflow */
u32 histogram_total = 0;
for (u32 d1 = 0; d1 < 512; d1++) {
u32 total = 0;
for (u32 d2 = 0; d2 < 64; d2++) total += gHis16[d1][d2];
gH512[d1] = total;
histogram_total += total;
}
const bool need32 = (histogram_total != np);
if (need32) {
memset(gHis32, 0, sizeof(gHis32));
for (u32 i = 0; i < np; i++) ++gHis32[(y[i] >> 15) & 511][(y[i] >> 9) & 63];
for (u32 d1 = 0; d1 < 512; d1++) {
u32 total = 0;
for (u32 d2 = 0; d2 < 64; d2++) total += gHis32[d1][d2];
gH512[d1] = total;
}
}
for (u32 b = 0; b < MSN; b++) {
u32 s = bnd[b], e = bnd[b + 1], len = e - s;
if (len <= 1) continue;
memset(cntb, 0, 256 * sizeof(u32));
memset(cnb2, 0, sizeof(cnb2));
for (u32 t = s; t < e; t++) cntb[(u32)(PKB[t] >> 48) & 127]++;
{ u32 a2 = 0;
for (int d = 0; d < 128; d++) { u32 c = cntb[d]; cntb[d] = a2; a2 += c; } }
for (u32 t = s; t < e; t++) {
u64 r = PKB[t];
u32 dg = (u32)(r >> 48) & 127;
u32 qq = cntb[dg]++;
PKA[qq] = r;
cnb2[(u32)(r >> 55) & 127]++; /* == the histogram pass 3 used to rebuild */
}
{ u32 a2 = 0;
for (int d = 0; d < 128; d++) { u32 c = cnb2[d]; cnb2[d] = a2; a2 += c; } }
for (u32 t = 0; t < len; t++) {
u32 dg = (u32)(PKA[t] >> 55) & 127;
u32 qq = cnb2[dg]++;
PKB[s + qq] = PKA[t];
}
}
u64 *pk = PKB;
u64 *alt = PKA;
/* ---- LAYOUT: staging-row stride must not be a multiple of the L1 set span.
Without the pad every row starts at b*2^16 elements = b*2^19 bytes, which IS a
multiple of 4096, so all NBKT=256 concurrent staging write streams (one per idx
bucket; idx is uniformly random, so they advance in lockstep and therefore keep
the same offset within the row) map to the SAME L1 set. With 8-way
associativity that is ~31/32 conflict misses on every staging store.
A pad of STGPADE elements moves row b by b*STGPADE*8 bytes; STGPADE=8 gives a
row stride of 524288+64 bytes = exactly one set apart, spreading the 256 rows
over all 64 sets (4 per set). O(NBKT) work, zero per-element cost -- gBOff is
already the single source of truth for both the L3 store and the flush. */
{ u32 acc = 0; for (int b = 0; b < NBKT; b++) { gBOff[b] = acc; acc += gBkt[b] + STGPADE; } gBOff[NBKT] = acc; }
// ---------------- L1 bucket offsets (histogram filled in stage A) -------
{ u32 acc = 0;
for (int d = 0; d < 512; d++) { u32 c = gH512[d]; gBnd[d] = acc; gCur[d] = acc; acc += c; } }
gBnd[512] = np;
// ---------------- L1: MSD 8 bits (y bits 23..16) -> alt ----------------
cnt_init();
{
for (u32 bb = 0; bb < MSN; bb++) {
u32 t = bnd[bb], bnde = bnd[bb + 1];
u32 prevx = 0xFFFFFFFFu, a = 0;
/* lane j of yhist = y of the j-th most recent element of this group */
__m256i yhist = _mm256_setzero_si256();
for (; t < bnde; t++) {
u64 rec0 = pk[t];
u32 xv = (u32)(rec0 >> 48);
u32 eq = (u32)(xv == prevx);
prevx = xv;
a = (a + 1) & (0u - eq);
u32 ya0 = (u32)(rec0 >> 24) & 0xFFFFFFu;
__m256i yb = _mm256_set1_epi32((int)ya0);
u32 dup0;
if (a < 8) {
/* Compare only valid recent y values in the current x group. */
u32 mm = (u32)_mm256_movemask_ps(_mm256_castsi256_ps(_mm256_cmpgt_epi32(yb, yhist)));
dup0 = (u32)__builtin_popcount(_bzhi_u32(mm, a));
} else dup0 = dup_long(pk, t, a, ya0);
yhist = _mm256_blend_epi32(
_mm256_permutevar8x32_epi32(yhist, _mm256_setr_epi32(7,0,1,2,3,4,5,6)),
yb, 1);
{
u64 rec = rec0;
u32 dup = dup0;
u32 d = (u32)(rec >> 39) & 511;
u32 inc = cnt_q(d);
cnt_ins(d);
u32 q = gCur[d]++;
u32 cnt = (0x1000000u - dup) & 0xFFFFFFu;
cnt = (cnt + inc) & 0xFFFFFFu;
alt[q] = ((u64)cnt << 40) | (rec & 0x0000007FFFFFFFFFull);
}
}
}
}
// -------- fused L2 (y bits 15..8) + L3 (y bits 7..0) + store -----------
for (u32 b0 = 0; b0 < 512; b0++) {
u32 s = gBnd[b0], e = gBnd[b0 + 1];
u32 m = e - s;
if (m == 0) continue;
if (m == 1) {
{ u32 _ix = (u32)(alt[s] & 0xFFFFFFu); g_STG[gBOff[_ix >> BSH]++] = ((alt[s] >> 40) << 24) | _ix; }
continue;
}
// histogram + out prefetch while the bucket is read the first time
{ u32 acc = 0;
for (int d = 0; d < 64; d++) {
u32 c = need32 ? gHis32[b0][d] : (u32)gHis16[b0][d];
gHis[d] = acc; gCur[d] = acc; acc += c;
}
gHis[64] = acc; }
cnt_init2();
for (u32 t = s; t < e; t++) {
u64 rec = alt[t];
u32 d = (u32)(rec >> 33) & 63;
u32 inc = cnt_q2(d);
cnt_ins2(d);
u32 q = s + gCur[d]++;
u32 cnt = ((u32)(rec >> 40) + inc) & 0xFFFFFFu;
pk[q] = ((u64)cnt << 40) | (rec & 0x0000001FFFFFFFFull);
}
/* L3 inside pk[s..e): the runs are exactly the d1 groups laid down by the scatter
above, so their boundaries are already known from the prefix sums that were just
computed -- no need to re-scan pk to discover them. (Same order, same runs.) */
for (u32 d1 = 0; d1 < 64; d1++) {
const u32 p = s + gHis[d1], qq = s + gHis[d1 + 1];
if (p == qq) continue;
if (qq - p == 1) {
{ u32 _ix = (u32)(pk[p] & 0xFFFFFFu); g_STG[gBOff[_ix >> BSH]++] = ((pk[p] >> 40) << 24) | _ix; }
} else {
/* within a run of equal (d1,d2) the remaining digit is d3 = y&255 */
if (qq-p > 65535u) rank_l3_run<0>(pk,p,qq);
else if (qq-p <= 512u) rank_l3_run<2>(pk,p,qq);
else rank_l3_run<1>(pk,p,qq);
}
}
}
// ---- flush: walk the staging array one idx-bucket at a time so every
// out[] store lands inside a 64 KB (BSH=14) L2-resident window ----
{
for (u32 b = 0; b < NBKT; b++) {
/* After the scatter gBOff[b] is the running END of row b (the scatter uses it
as a cursor), so the row is [gBOff[b]-gBkt[b], gBOff[b]) -- "next row's
base" is no longer the same thing once the rows are padded. */
u32 e = gBOff[b];
for (u32 t = e - gBkt[b]; t < e; t++) {
if ((t & 1u) == 0u && t + 64u < e) __builtin_prefetch(&out[(u32)(g_STG[t + 64u] & 0xFFFFFFu)], 1, 0);
u64 r = g_STG[t]; out[(u32)(r & 0xFFFFFFu)] = (u32)(r >> 24); }
}
}
}
#pragma GCC pop_options
| Compilation | N/A | N/A | Compile OK | Score: N/A | 显示更多 |
| Testcase #1 | 214.021 ms | 267 MB + 180 KB | Accepted | Score: 100 | 显示更多 |