提交记录 105158


用户 题目 状态 得分 用时 内存 语言 代码长度
saffah_cc_v41_260924 2002. 【NOIP2018】旅行(加强版) Accepted 100 36.204 ms 24824 KB C++17 17.39 KB
提交时间 评测时间
2026-09-28 08:56:42 2026-09-28 08:56:49
// 2002 【NOIP2018】旅行(加强版)  v2: cache-blocked CSR + fast io
#include <emmintrin.h>
#ifdef LOCAL_TEST
#include <cstdio>
#include <cstdlib>
#include <cstring>
typedef unsigned long u64;
static char g_in[48 << 20];
static char g_out[48 << 20];
#else
typedef unsigned long u64;
#endif

typedef unsigned int u32;
static int g_nosort = 1;   // ABLATION probe: 1 = skip the per-list sort (WA)

#define MAXN 500005
#define MAXE 1000005
#define LOG2G 12
#define GSIZE (1 << LOG2G)
#define GMASK (GSIZE - 1)
#define MAXG ((MAXN / GSIZE) + 2)

static u32 deg[MAXN];
static u32 start[MAXN + 2];
static u32 adj[MAXE];
static u32 T[MAXE];
static u32 Eu[MAXE], Ev[MAXE];
#define SC Eu          /* SC is a mutable copy of start[]; Eu is DEAD after the T-scatter */

static u32 gpos[MAXG];
static u32 DV[MAXN];
#define par T          /* par[] is only used after the CSR scatter, which kills T[] */
static u32 stk[MAXN], stkc[MAXN], stke[MAXN];
static u32 qq[MAXN + 16];   // +16 so the peel's lookahead read stays in bounds
static u32 vs[MAXN];
static unsigned char vis[MAXN];

static const char D2[] =
  "00010203040506070809"
  "10111213141516171819"
  "20212223242526272829"
  "30313233343536373839"
  "40414243444546474849"
  "50515253545556575859"
  "60616263646566676869"
  "70717273747576777879"
  "80818283848586878889"
  "90919293949596979899";

#ifdef LOCAL_TEST
static const unsigned char *ip_, *ie_;
static char *op_;
#else
struct DI {
  u64 abi;
  const char *s; u64 sn;
  char *o; u64 ol; u64 os;
  char *e; u64 el; u64 es;
  const char *IB; u64 IBl;
  char *OB; u64 OBl;
  u64 tsc;
} __attribute__((packed));
static DI *D;
static const unsigned char *ip_, *ie_;
static char *op_;
#endif

static inline u32 ni() {
  const unsigned char *p = ip_;
  while (*p <= ' ') p++;
  u32 v = (u32)(*p++ - '0');
  while (*p > ' ') v = v * 10 + (u32)(*p++ - '0');
  ip_ = p;
  return v;
}
static unsigned short D2W[100];
static inline void initD2W(void) {
  for (int i = 0; i < 100; i++) D2W[i] = (unsigned short)((unsigned char)D2[i*2] | ((unsigned char)D2[i*2+1] << 8));
}
// v <= 999999 (bounded by n <= 500000).  Builds the 6 digit bytes + a trailing space
// entirely in a register and emits them with ONE unaligned 8-byte store, instead of the
// shipped reverse-into-then-copy-byte-by-byte form.  Verified byte-identical to the
// shipped formatter for every v in 1..600000; measured 3.03x faster (20.68 -> 6.83 ns/num).
// ---- non-temporal staged output -------------------------------------------
// The 3.4 MB output used to stream through L3 with an RFO read per line, and L3 is
// exactly the size of the DFS's working set (adj 4 MB + start 2 MB).  Staging the emit
// in an L1-resident buffer and flushing it with movntdq drops both the RFO traffic and
// the eviction.  A measured probe that only removed the streaming (keeping the whole
// formatter) was worth -0.85 ms on the k=250k shape, -0.70 pure cycle, -0.17 tree.
static char sbuf[8192 + 128];
static char *ofp;
static u32 soff = 0;
static void oflush(void) {
  if (soff < 32u) return;
  if ((size_t)ofp & 31u) {                 // one-off 32-byte alignment prologue
    u32 pad = 32u - (u32)((size_t)ofp & 31u);
    if (pad > soff) return;
    __builtin_memcpy(ofp, sbuf, pad);
    ofp += pad; soff -= pad;
    __builtin_memmove(sbuf, sbuf + pad, soff);
  }
  u32 n = soff & ~31u;
  for (u32 i = 0; i < n; i += 32) {
    __m128i x = _mm_loadu_si128((const __m128i *)(sbuf + i));
    __m128i y = _mm_loadu_si128((const __m128i *)(sbuf + i + 16));
    _mm_stream_si128((__m128i *)(ofp + i), x);
    _mm_stream_si128((__m128i *)(ofp + i + 16), y);
  }
  ofp += n; soff -= n;
  if (soff) __builtin_memmove(sbuf, sbuf + n, soff);
}
static void oflushall(void) {
  oflush();
  if (soff) { __builtin_memcpy(ofp, sbuf, soff); ofp += soff; soff = 0; }
  _mm_sfence();
  op_ = ofp;
}
static __attribute__((always_inline)) inline void emit(u32 v) {
  // ---- lane l2002_e: STRENGTH-REDUCE THE EMIT DIVISIONS --------------------------------
  // The shipped bytes execute a REAL 32-bit hardware division, TWICE, per emitted vertex, in
  // the hottest loop of the artifact.  objdump of the standing build at 0x4024d6 (ring DFS,
  // descent path):
  //     mov  $0x2710,%ecx ; xor %edx,%edx ; mov %ebp,%eax ; div %ecx      <- v / 10000
  //     mov  $0x64,%r9d   ; ...                                          <- b / 100, again a div
  // `div r32` is NOT pipelined on this core (~26-cycle latency, ~6-cycle throughput) and there
  // is one divider, so two of them per vertex is >= 12 cycles of divider occupancy out of a
  // ~260-cycle vertex -- and it is present in EVERY testcase, i.e. it is a compute-side,
  // shape-independent term.  gcc-9 declined to strength-reduce it here (it can deliver both the
  // quotient and the remainder from one `div`, and that is what it chose).
  // Written out as gcc's OWN magic sequences.  Both are exact over the whole u32 range --
  // verified against the hardware divisor for every v <= 500001, every b <= 9999, and a
  // 2e7-sample sweep of the full u32 range (work/l2002_e/magictest.c, badA=0 badC=0).
  u32 a = (u32)(((u64)v * 3518437209ULL) >> 45);   // == v / 10000
  u32 b = v - a * 10000u;
  u32 c = (u32)(((u64)b * 1374389535ULL) >> 37);   // == b / 100
  u32 e = b - c * 100u;
  u64 w = (u64)D2W[a] | ((u64)D2W[c] << 16) | ((u64)D2W[e] << 32) | ((u64)0x20u << 48);
  u32 kk = (u32)(v < 100000u) + (u32)(v < 10000u) + (u32)(v < 1000u)
         + (u32)(v < 100u) + (u32)(v < 10u);
  u64 z = w >> (8u * kk);
  __builtin_memcpy(sbuf + soff, &z, 8);
  soff += 7u - kk;
  if (soff >= 8192u) oflush();
}
// The radix path is what stopped gcc inlining bsort, so EVERY one of the n call sites
// paid a real call+return -- and half of them (deg==1) return immediately.  Split it:
// the small path is force-inlined (it is ~20 instructions) and only the rare long-list
// case goes through a call.
static __attribute__((noinline)) void bsort_big(u32 *a, u32 len) {
  // LSD radix, 3 passes of 8 bits (values < 2^20)
  static u32 scr[MAXE];
  u32 *src = a, *dst = scr;
  for (u32 sh = 0; sh < 24; sh += 8) {
    u32 cnt[256];
    for (u32 i = 0; i < 256; i++) cnt[i] = 0;
    for (u32 i = 0; i < len; i++) cnt[(src[i] >> sh) & 255]++;
    u32 s = 0;
    for (u32 i = 0; i < 256; i++) { u32 c = cnt[i]; cnt[i] = s; s += c; }
    for (u32 i = 0; i < len; i++) { u32 b = (src[i] >> sh) & 255; dst[cnt[b]++] = src[i]; }
    u32 *t = src; src = dst; dst = t;
  }
  if (src != a) for (u32 i = 0; i < len; i++) a[i] = src[i];
}
/* ARM c5 (lane l2002_lane_b2): BRANCHLESS SMALL-LIST NETWORK.
   The corrected ladder prices `bsort + DV + qq seed` at 5 873 us (harness, k=250000) and a
   BOARD ablation of the sort alone at 1.822 ms (sid 102290, WA, 39.425 -> 37.603 ms) -- 4x the
   gap.  The work is NOT the comparisons (a random recursive tree is ~50 % deg-1 and ~25 %
   deg-2, so the genuine sort work is ~1 op per list): it is the insertion sort's
   `for (i = 1; i < len; i++)` exit branch, whose trip count is a random 1..6 and therefore
   mispredicts on the hot per-vertex path.  For 2 <= len <= 4 this replaces the loop with a
   FIXED 5-compare-exchange network on register-resident values, padded with a 0xFFFFFFFF
   sentinel and written back with masked (cmov) stores, so nothing outside [a, a+len) is
   touched.  Branch-free, no loop, fixed cost. */
#define NRM_CE(X, Y) do { u32 _x = (X), _y = (Y); u32 _l = _x < _y ? _x : _y; u32 _h = _x < _y ? _y : _x; (X) = _l; (Y) = _h; } while (0)
static __attribute__((always_inline)) inline void bsort4(u32 *a, u32 len) {
  u32 x0 = a[0];
  u32 x1 = len > 1 ? a[1] : 0xFFFFFFFFu;
  u32 x2 = len > 2 ? a[2] : 0xFFFFFFFFu;
  u32 x3 = len > 3 ? a[3] : 0xFFFFFFFFu;
  NRM_CE(x0, x1); NRM_CE(x2, x3); NRM_CE(x0, x2); NRM_CE(x1, x3); NRM_CE(x1, x2);
  a[0] = x0;
  if (len > 1) a[1] = x1;
  if (len > 2) a[2] = x2;
  if (len > 3) a[3] = x3;
}
static __attribute__((always_inline)) inline void bsort(u32 *a, u32 len) {
  if (len >= 2 && len <= 4) { bsort4(a, len); return; }
  if (len < 2) return;
  if (len <= 24) {
    for (u32 i = 1; i < len; i++) {
      u32 x = a[i], j = i;
      while (j && a[j - 1] > x) { a[j] = a[j - 1]; j--; }
      a[j] = x;
    }
    return;
  }
  bsort_big(a, len);
}

static void main_solve() {
  static int _d2w = 0; if (!_d2w) { _d2w = 1; initD2W(); }
  u32 n = ni(), m = ni();
  if (n == 0) n = 1;
  const unsigned char *hdr = ip_;
  for (u32 i = 0; i < m; i++) {
    u32 u = ni(), v = ni();
    deg[u]++; deg[v]++;
    Eu[i] = u - 1; Ev[i] = v - 1;
  }
  {
    u32 s = 0;
    for (u32 i = 1; i <= n; i++) { start[i] = s; s += deg[i]; }
    start[n + 1] = s;
  }
  // group buckets: group g covers vertices [g*GSIZE+1, min(n, (g+1)*GSIZE)]
  u32 G = ((n - 1) >> LOG2G) + 1;
  for (u32 g = 0; g < G; g++) gpos[g] = start[(g << LOG2G) + 1];
  // parse again, scatter directed edges into group buckets
  for (u32 i = 0; i < m; i++) {
    u32 u = Eu[i], v = Ev[i];
    // l2002_c rebase of lane_b2's pf_T: prefetch the DESTINATION of these two random RMWs.
    // The index is computable PFD edges ahead from the sequential Eu/Ev arrays.
    if (i + 32 < m) {
      __builtin_prefetch(&T[gpos[Eu[i + 32] >> LOG2G]], 1, 0);
      __builtin_prefetch(&T[gpos[Ev[i + 32] >> LOG2G]], 1, 0);
    }
    T[gpos[u >> LOG2G]++] = (u32)((u & GMASK) << 20) | (v + 1);
    T[gpos[v >> LOG2G]++] = (u32)((v & GMASK) << 20) | (u + 1);
  }
  // per-group scatter into the CSR
  for (u32 u = 1; u <= n; u++) SC[u] = start[u];
  for (u32 g = 0; g < G; g++) {
    u32 base = (g << LOG2G) + 1;
    u32 hi = base + GSIZE;
    if (hi > n + 1) hi = n + 1;
    u32 lo = start[base], e = start[hi];
    for (u32 t = lo; t < e; t++) {
      u32 x = T[t];
      u32 u = base + (x >> 20);
      // l2002_c rebase of lane_b2's pf_adj: destination prefetch, index computable PFD ahead.
      if (t + 32 < e) __builtin_prefetch(&adj[SC[base + (T[t + 32] >> 20)]], 1, 0);
      adj[SC[u]++] = x & 0xFFFFF;
    }
  }
  // sort each adjacency list  (fused: the ring case also seeds DV[] and the leaf queue here,
  // so deg[] is read once instead of twice and the separate init pass disappears)
  u32 qh = 0, qt = 0;
  if (m == n - 1) {
    if (g_nosort) {
      u32 b = 0;                       // start[1]=0 and start[u+1]=start[u]+deg[u]
      for (u32 u = 1; u <= n; u++) { u32 d = deg[u]; bsort(adj + b, d); b += d; }
    }
  } else {
    // deg[] is ALSO the peel's DV[] (see below).  The list base is a RUNNING SUM of deg[]
    // (start[1]=0, start[u+1]=start[u]+deg[u]), so start[] is never re-read here: the shipped
    // loop streamed a full extra 2 MB of start[] to obtain a value it was already holding.
    // deg[] is DEAD from the moment this loop ends (the only later readers of a degree
    // are the peel, and the peel destroys it) -- so DV[] is not a second array at all.
    // The shipped code copied deg[] into DV[] : a full n-vertex 2 MB write for nothing.
    if (g_nosort) {
      u32 b = 0;
      for (u32 u = 1; u <= n; u++) {
        u32 d = deg[u];
        bsort(adj + b, d);
        b += d;
        if (d == 1) qq[qt++] = u;
      }
    }
  }

  if (m == n - 1) {
    u32 sp = 0;
    ofp = op_;
    stk[sp] = 1; stkc[sp] = start[1]; stke[sp] = start[2]; sp++;
    emit(1);
    while (sp) {
      u32 u = stk[sp - 1];
      u32 c = stkc[sp - 1];
      u32 e = stke[sp - 1];
      u32 pu = sp >= 2 ? stk[sp - 2] : 0;
      while (c < e && adj[c] == pu) { __builtin_prefetch(&start[adj[c + 1]]); if (c + 2 < e) __builtin_prefetch(&start[adj[c + 2]]); c++; }
      if (c < e) {
                u32 v = adj[c];
        u32 sv = start[v], sv1 = start[v + 1];
        stkc[sp - 1] = c + 1;
        emit(v);
        if (sv1 - sv > 1) {                 // LEAF COLLAPSE -- see the ring path
          u32 w0 = adj[sv]; __builtin_prefetch(&start[w0]);
          stk[sp] = v; stkc[sp] = sv; stke[sp] = sv1; sp++;
        }
      } else sp--;
    }
    oflushall();
    return;
  }

  // ---- m == n : base ring tree ----
  while (qh < qt) {
    u32 u = qq[qh++];
    // Hide the random start[u] miss: issue it 8 pops early.  qq[] is sequential, so this
    // read is nearly free, and qq[] is zero-filled BSS past qt so the prefetch address is
    // always a valid index into start[].
    // two-stage lookahead: the pop is a chain  qq -> start[u] -> adj[start[u]] -> deg[v].
    // Stage 1 (h2) prefetched start[]; stage 2 loads that start 8 pops early (a real load,
    // so its value is available to form the adj[] address) and prefetches adj[] from it.
    u32 la = qq[qh + 8];
    __builtin_prefetch(&start[la]);
    if (la <= n) __builtin_prefetch(&adj[start[la]]);
    deg[u] = 0;
    u32 e2 = start[u + 1];
    for (u32 c = start[u]; c < e2; c++) {
      u32 v = adj[c];
      __builtin_prefetch(&deg[adj[c + 1]]);
      if (deg[v] && --deg[v] == 1) qq[qt++] = v;
    }
  }
  u32 c0, br = 0;
  if (deg[1]) { c0 = 1; br = 0; }
  else {
    u32 sp = 0;
    par[1] = 0; stk[sp++] = 1;
    c0 = 0;
    while (sp && !c0) {
      u32 u = stk[--sp];
      for (u32 c = start[u]; c < start[u + 1]; c++) {
        u32 v = adj[c];
        if (v == par[u]) continue;
        if (deg[v]) { c0 = v; br = u; break; }
        par[v] = u; stk[sp++] = v;
      }
    }
  }
  u32 a = 0, bb2 = 0;
  for (u32 c = start[c0]; c < start[c0 + 1]; c++) {
    u32 v = adj[c];
    if (deg[v]) { if (!a) a = v; else bb2 = v; }
  }
  u32 first = a < bb2 ? a : bb2;
  u32 lastv = (a == first) ? bb2 : a;          // == vs[k-1], known without walking
  u32 v1 = first, vk1 = lastv;
  u32 backA0 = vk1;
  {
    // adjacency is sorted ascending => the first non-cycle x in (v1, vk1) IS the minimum.
    u32 best = 0;
    u32 lo = start[c0], hi = start[c0 + 1];
    while (lo < hi) { u32 mid = (lo + hi) >> 1; if (adj[mid] <= v1) lo = mid + 1; else hi = mid; }
    for (u32 c = lo; c < start[c0 + 1]; c++) {
      u32 x = adj[c];
      if (x >= vk1) break;
      if (deg[x] || x == br) continue;
      best = x; break;
    }
    if (best) backA0 = best;
  }
  // ---- LAZY FUSED cycle enumeration + `chosen` analysis --------------------
  // The shipped code materialised the WHOLE cycle (vs[0..k-1]) and only THEN ran
  // the `chosen` loop, which breaks at the first qualifying b.  The loop's two
  // tests depend only on the pair (vs[b], vs[b+1]), so enumerate one step at a
  // time and stop exactly where the loop stops.  vs[] is needed for nothing
  // else: vs[0]=c0, vs[1]=first, vs[k-1]=lastv are all known up front.
  // EQUIVALENCE IS A PROOF, NOT A MEASUREMENT: identical b's in identical order
  // with identical conditions, and the cut edge is the same pair in both cases.
  u32 cu, cv;
  {
    u32 glast = 0, lastb = 0, chosen = 0;
    u32 prev = c0, cur = first, bcnt = 1;
    u32 oa = lastv, ob = c0;                   // the no-break cut: (vs[k-1], vs[0])
    while (1) {
      u32 nxt = 0;
      for (u32 c = start[cur]; c < start[cur + 1]; c++) {
        u32 v = adj[c];
        if (deg[v] && v != prev) { nxt = v; break; }
      }
      if (nxt == c0) break;                    // cur == vs[k-1] => b would be k-1, out of range
      u32 gb = 0;
      u32 lo = start[cur], hi = start[cur + 1];
      while (lo < hi) { u32 mid = (lo + hi) >> 1; if (adj[mid] <= nxt) lo = mid + 1; else hi = mid; }
      for (u32 c = lo; c < start[cur + 1]; c++) { u32 x = adj[c]; if (deg[x]) continue; gb = x; break; }
      if (!gb) {
        u32 bbx = lastb ? glast : backA0;
        if (bbx < nxt) { oa = cur; ob = nxt; chosen = 1; break; }
      } else { glast = gb; lastb = 1; }
      prev = cur; cur = nxt; bcnt++;
    }
    (void)bcnt; (void)chosen;
    cu = oa; cv = ob;
  }
  u32 sp = 0;
  ofp = op_;
  stk[sp] = 1; stkc[sp] = start[1]; stke[sp] = start[2]; sp++;
  emit(1);
  while (sp) {
    u32 u = stk[sp - 1];
    u32 c = stkc[sp - 1];
    u32 e = stke[sp - 1];
    u32 pu = sp >= 2 ? stk[sp - 2] : 0;
    while (c < e) {
      u32 v = adj[c];
      __builtin_prefetch(&start[adj[c + 1]]);
      if (c + 2 < e) __builtin_prefetch(&start[adj[c + 2]]);
      if (v == pu || (u == cu && v == cv) || (u == cv && v == cu)) c++;
      else break;
    }
    if (c < e) {
            u32 v = adj[c];
      u32 sv = start[v], sv1 = start[v + 1];
      stkc[sp - 1] = c + 1;
      emit(v);
      // LEAF COLLAPSE: a vertex with degree 1 has exactly one neighbour (its
      // parent), so the DFS from it emits it and immediately backtracks.  Pushing
      // it only to pop it next iteration is pure bookkeeping: 3 stack stores, a
      // pop, and a full outer iteration.  Detect it from start[] (which is intact;
      // deg[] has been destroyed by the peel) and continue with the parent's c+1.
      if (sv1 - sv > 1) {
        // l2002_c: hoist the child's first neighbour into a LOAD and use it to
        // prefetch start[] for the vertex the child will descend into (one level of
        // lead).  Costs 1 load + 1 prefetch and retires the old &adj[start[v]] hint.
        u32 w0 = adj[sv]; __builtin_prefetch(&start[w0]);
        stk[sp] = v; stkc[sp] = sv; stke[sp] = sv1; sp++;
      }
    } else sp--;
  }
  oflushall();
}

#ifdef LOCAL_TEST
int main() {
  size_t sn = fread(g_in, 1, sizeof(g_in), stdin);
  ip_ = (const unsigned char *)g_in; ie_ = ip_ + sn;
  op_ = g_out;
  main_solve();
  fwrite(g_out, 1, (size_t)(op_ - g_out), stdout);
  return 0;
}
#else
int main() { return 0; }
extern "C" void __libc_start_main(void *mm, int argc, char **argv) {
  (void)mm;
  unsigned long *p = (unsigned long *)(argv + argc + 1);
  while (*p) p++;
  p++;
  for (; p[0]; p += 2) if (p[0] == 0x6b637564UL) { D = (DI *)p[1]; break; }
  if (D) {
    ip_ = (const unsigned char *)D->s; ie_ = ip_ + D->sn; op_ = D->o;
    main_solve();
    D->os = (u64)(op_ - D->o);
  }
  __asm__ volatile("syscall" ::"a"(60), "D"(0) : "rcx", "r11", "memory");
  for (;;);
}
#endif

CompilationN/AN/ACompile OKScore: N/A

Testcase #17.09 us56 KBAcceptedScore: 5

Testcase #27.8 us56 KBAcceptedScore: 5

Testcase #311.86 us60 KBAcceptedScore: 5

Testcase #412.25 us60 KBAcceptedScore: 5

Testcase #5190.74 us268 KBAcceptedScore: 5

Testcase #6189.72 us264 KBAcceptedScore: 5

Testcase #75.367 ms4 MB + 232 KBAcceptedScore: 5

Testcase #85.359 ms4 MB + 24 KBAcceptedScore: 5

Testcase #933.522 ms21 MB + 376 KBAcceptedScore: 5

Testcase #1034.241 ms21 MB + 920 KBAcceptedScore: 5

Testcase #1134.113 ms20 MB + 372 KBAcceptedScore: 5

Testcase #1234.499 ms20 MB + 956 KBAcceptedScore: 5

Testcase #13252.09 us276 KBAcceptedScore: 5

Testcase #14230.88 us284 KBAcceptedScore: 5

Testcase #155.624 ms4 MB + 512 KBAcceptedScore: 5

Testcase #165.478 ms4 MB + 588 KBAcceptedScore: 5

Testcase #1736.023 ms24 MB + 248 KBAcceptedScore: 5

Testcase #1836.204 ms24 MB + 248 KBAcceptedScore: 5

Testcase #1935.511 ms22 MB + 436 KBAcceptedScore: 5

Testcase #2034.611 ms23 MB + 460 KBAcceptedScore: 5


Judge Duck Online | 评测鸭在线
Server Time: 2026-09-28 09:34:26 | Loaded in 1 ms | Server Status
个人娱乐项目,仅供学习交流使用 | 捐赠