1 solutions
-
0
组织者内部题解,含参考实现。比赛中不向选手展示。
PROD-PR-002 实操二 查询服务契约实现
业务背景与基本概念
接口契约明确输入合法范围、授权、筛选、排序和分页的先后。total是权限及业务筛选后、分页前的匹配数。一个用户可以合法存在但授权对象为0,此时是成功空结果,不是无此用户的FORBIDDEN。
公式、符号与业务口径
校验优先级:参数合法→用户存在→按对象权限过滤→按状态过滤→确定排序→分页。offset=(page−1)×size,取[offset,offset+size)。ID方向随ASC/DESC;AMOUNT方向随请求,但同金额ID永远升序。返回OK total returned。
样例逐步计算
A有X权限,请求ALL、金额降序、第一页2条。O3属于Y无权排除;O1、O2、O4属于X,total=3。金额100的O1和O2并列按ID升序,O4金额50后。首页取O1、O2,输出OK 3 2,随后O1 100、O2 100。
解题步骤与实现思路
授权映射查对象,过滤后排序截片。时间O(u+对象授权数+n log n),空间O(授权数+n)。
常见错误与边界
不能先分页再过滤权限,会遗漏并泄露总数。金额DESC不能把次排序ID也降序。参数错且用户不存在时返回INVALID_PARAM。超末页是正常0条,不是参数错误。真实HTTP鉴权和存储层安全另由平台负责,本题用标准输入模拟逻辑。
原始题面与输入输出
PROD-PR-002 实操二 查询服务契约实现 题型:综合;25分
业务与规则
- 请求字段user、status、sort、direction、page、size。status只允许ALL、PAID、CANCELLED;sort只允许ID、AMOUNT;direction只允许ASC、DESC;page≥1,1≤size≤100。否则输出ERROR INVALID_PARAM。
- user在授权用户表中不存在时输出ERROR FORBIDDEN;参数错误优先于用户不存在。已知用户可以有0个授权对象,此时正常返回空列表。
- 权限过滤后,按status过滤;total为分页之前的匹配行数。ID排序按字符串ASC或DESC;AMOUNT按金额指定方向,金额相同时始终按ID升序。
- page采用从1开始的页号,offset=(page-1)*size;超出末页返回正常状态且0条。每条数据id唯一,不需要幂等副作用处理。
输入 第一行user status sort direction page size。下一行u,为授权用户数;随后u行:user c object1 ... objectc。用户唯一、各自对象不重复。下一行n;随后n行:id object status amount,status为PAID或CANCELLED。
输出 错误时只输出对应ERROR行。成功第一行“OK total returned_count”;随后各行“id amount”,按排序分页后输出。
范围 0≤u,n≤100000;授权对象总数≤100000;金额0至1e12分;-100≤page≤1e9,-100≤size≤1000。编号均为大写字母和数字。建议3秒、256MB,按语言校准。输出精确匹配。
样例输入 A ALL AMOUNT DESC 1 2 2 A 1 X B 0 4 O1 X PAID 100 O2 X CANCELLED 100 O3 Y PAID 300 O4 X PAID 50
样例输出 OK 3 2 O1 100 O2 100
各语言如何对应算法
各份程序实现上文同一计算口径。读取阶段将字符串编号与数值分开处理;中间状态负责累计、集合去重或版本选择;输出阶段才做规定排序、分页及六位小数格式化。按“解题步骤”对照各文件业务段,通用读取、哈希和排序辅助函数可先跳过,再回头理解。
Python/PyPy完整参考实现
文件:
reference.py,与本题同文件夹。Python与PyPy共用本文件。字典保存按业务键的状态,集合负责去重;Python整数可自动扩展,精确小数边界题使用Decimal。
运行方法:
python reference.py;或pypy3 reference.py。测试数据通过标准输入送入,运行说明中的重定向不写入程序。import sys it=iter(sys.stdin.read().split()); user,status,sort,direction=next(it),next(it),next(it),next(it); page,size=int(next(it)),int(next(it)) u=int(next(it)); auth={} for _ in range(u): name=next(it); c=int(next(it)); auth[name]={next(it) for _ in range(c)} n=int(next(it)); records=[] for _ in range(n): ident,obj,st,amt=[next(it) for _ in range(4)]; records.append((ident,obj,st,int(amt))) if status not in {'ALL','PAID','CANCELLED'} or sort not in {'ID','AMOUNT'} or direction not in {'ASC','DESC'} or page<1 or not 1<=size<=100: print('ERROR INVALID_PARAM') elif user not in auth: print('ERROR FORBIDDEN') else: rows=[r for r in records if r[1] in auth[user] and (status=='ALL' or r[2]==status)] if sort=='ID': rows.sort(key=lambda r:r[0],reverse=direction=='DESC') else: rows.sort(key=lambda r:((r[3] if direction=='ASC' else -r[3]),r[0])) total=len(rows); result=rows[(page-1)*size:page*size] print('OK',total,len(result)) for ident,obj,st,amt in result: print(ident,amt)C完整参考实现
文件:
reference.c,与本题同文件夹。I为64位整数,F为浮点数,Group保存业务状态,Map按字符串键查找;main中的业务步骤可与上文逐项对应。
运行方法:
gcc -std=c17 -O2 reference.c -lm -o reference,然后运行reference。测试数据通过标准输入送入,运行说明中的重定向不写入程序。/* Native C17 reference. Read stdin, write stdout; no Python dependency. */ #include <ctype.h> #include <math.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef long long I; typedef double F; typedef struct { char *key; I v[8]; F f[8]; char *s[4]; int flag; } Group; typedef struct { Group *a; int count, cap; int *slots; int size; } Map; char ***L; int *lc, lines, n; char **h, ***r; Map groups; I iv(const char *s) { return strtoll(s, NULL, 10); } F fv(const char *s) { return strtod(s, NULL); } int eq(const char *a, const char *b) { return strcmp(a, b) == 0; } F divide(F a, F b) { return b ? a / b : 0; } char *dupstr(const char *s) { char *p = malloc(strlen(s) + 1); strcpy(p, s); return p; } uint64_t hash(const char *s) { uint64_t v = 1469598103934665603ULL; for (; *s; s++) v = (v ^ (unsigned char)*s) * 1099511628211ULL; return v; } void mapinit(Map *m) { m->size = 131071; m->slots = malloc(m->size * sizeof(int)); for (int i = 0; i < m->size; i++) m->slots[i] = -1; m->count = 0; m->cap = 16; m->a = calloc(m->cap, sizeof(Group)); } int lookup(Map *m, const char *k) { int p = hash(k) % m->size; while (m->slots[p] >= 0) { int i = m->slots[p]; if (eq(m->a[i].key, k)) return i; p = (p + 1) % m->size; } return -1; } int ensure(Map *m, const char *k) { int i = lookup(m, k); if (i >= 0) return i; if (m->count == m->cap) { m->cap *= 2; m->a = realloc(m->a, m->cap * sizeof(Group)); } i = m->count++; memset(&m->a[i], 0, sizeof(Group)); m->a[i].key = dupstr(k); int p = hash(k) % m->size; while (m->slots[p] >= 0) p = (p + 1) % m->size; m->slots[p] = i; return i; } Group *get(Map *m, const char *k) { int i = ensure(m, k); return &m->a[i]; } char *pairkey(const char *a, const char *b) { size_t z = strlen(a) + strlen(b) + 2; char *p = malloc(z); snprintf(p, z, "%s\037%s", a, b); return p; } char *numpair(I a, const char *b) { char q[40]; snprintf(q, sizeof(q), "%lld", a); return pairkey(q, b); } int cmpkey(const void *a, const void *b) { return strcmp((*(Group *const *)a)->key, (*(Group *const *)b)->key); } Group **ordered(Map *m, int (*cmp)(const void *, const void *)) { Group **a = malloc((m->count + 1) * sizeof(Group *)); for (int i = 0; i < m->count; i++) a[i] = &m->a[i]; qsort(a, m->count, sizeof(Group *), cmp ? cmp : cmpkey); return a; } int fields[8], modes[8], dirs[8], sortcount; void sorting(int count, int *a, int *b, int *c) { sortcount = count; for (int i = 0; i < count; i++) { fields[i] = a[i]; modes[i] = b[i]; dirs[i] = c[i]; } } int rowcmp(const void *a, const void *b) { int ia = *(const int *)a, ib = *(const int *)b; for (int j = 0; j < sortcount; j++) { int f = fields[j], v; if (modes[j]) { F x = fv(r[ia][f]), y = fv(r[ib][f]); v = (x > y) - (x < y); } else v = strcmp(r[ia][f], r[ib][f]); if (v) return dirs[j] * v; } return (ia > ib) - (ia < ib); } int *orderrows(void) { int *a = malloc((n + 1) * sizeof(int)); for (int i = 0; i < n; i++) a[i] = i; qsort(a, n, sizeof(int), rowcmp); return a; } int cmpnum(const void *a, const void *b) { I x = (*(Group *const *)a)->v[0], y = (*(Group *const *)b)->v[0]; return (x > y) - (x < y); } int cmpdaykey(const void *a, const void *b) { Group *x = *(Group *const *)a, *y = *(Group *const *)b; int c = (x->v[0] > y->v[0]) - (x->v[0] < y->v[0]); return c ? c : strcmp(x->s[0], y->s[0]); } int cmpregionmonth(const void *a, const void *b) { Group *x = *(Group *const *)a, *y = *(Group *const *)b; int c = strcmp(x->s[0], y->s[0]); return c ? c : (x->v[0] > y->v[0]) - (x->v[0] < y->v[0]); } int cmpint(const void *a, const void *b) { I x = *(const I *)a, y = *(const I *)b; return (x > y) - (x < y); } int cmpdistance(const void *a, const void *b) { Group *x = *(Group *const *)a, *y = *(Group *const *)b; int c = (x->f[0] > y->f[0]) - (x->f[0] < y->f[0]); if (c) return c; c = strcmp(x->s[0], y->s[0]); return c ? c : (x->v[0] > y->v[0]) - (x->v[0] < y->v[0]); } int cmprisk(const void *a, const void *b) { Group *x = *(Group *const *)a, *y = *(Group *const *)b; int c = (y->v[0] > x->v[0]) - (y->v[0] < x->v[0]); if (c) return c; c = (y->v[1] > x->v[1]) - (y->v[1] < x->v[1]); if (c) return c; c = strcmp(x->s[0], y->s[0]); return c ? c : (x->v[2] > y->v[2]) - (x->v[2] < y->v[2]); } int digitstr(const char *s) { for (; *s; s++) if (*s < '0' || *s > '9') return 0; return 1; } I fixed6(const char *s) { int neg = *s == '-'; if (*s == '-' || *s == '+') s++; I a = 0, b = 0; while (*s && *s != '.') a = a * 10 + (*s++ - '0'); if (*s == '.') s++; int i = 0; while (*s && i < 6) { b = b * 10 + (*s++ - '0'); i++; } while (i++ < 6) b *= 10; return (neg ? -1 : 1) * (a * 1000000 + b); } void load(void) { int cap = 32; L = malloc(cap * sizeof(char **)); lc = malloc(cap * sizeof(int)); lines = 0; while (1) { int z = 128, len = 0, c; char *s = malloc(z); while ((c = getchar()) != EOF && c != '\n') { if (len + 1 >= z) s = realloc(s, z *= 2); s[len++] = (char)c; } if (c == EOF && !len) { free(s); break; } s[len] = 0; int k = 16, q = 0; char **t = malloc(k * sizeof(char *)); char *p = s; while (*p) { while (*p && isspace((unsigned char)*p)) *p++ = 0; if (!*p) break; if (q == k) t = realloc(t, (k *= 2) * sizeof(char *)); t[q++] = p; while (*p && !isspace((unsigned char)*p)) p++; } if (q) { if (lines == cap) { cap *= 2; L = realloc(L, cap * sizeof(char **)); lc = realloc(lc, cap * sizeof(int)); } L[lines] = t; lc[lines++] = q; } else { free(t); free(s); } if (c == EOF) break; } h = L[0]; n = (int)iv(h[0]); r = L + 1; mapinit(&groups); } int main(void) { load(); char *user = h[0], *status = h[1], *sort = h[2], *dir = h[3]; I page = iv(h[4]), size = iv(h[5]); int u = (int)iv(L[1][0]), pos = 2, known = 0; Map auth; mapinit(&auth); for (int j = 0; j < u; j++, pos++) if (eq(L[pos][0], user)) { known = 1; int c = (int)iv(L[pos][1]); for (int z = 0; z < c; z++) get(&auth, L[pos][z + 2]); } n = (int)iv(L[pos++][0]); r = L + pos; if ((!eq(status, "ALL") && !eq(status, "PAID") && !eq(status, "CANCELLED")) || (!eq(sort, "ID") && !eq(sort, "AMOUNT")) || (!eq(dir, "ASC") && !eq(dir, "DESC")) || page < 1 || size < 1 || size > 100) { puts("ERROR INVALID_PARAM"); return 0; } if (!known) { puts("ERROR FORBIDDEN"); return 0; } int amount = eq(sort, "AMOUNT"), direction = eq(dir, "DESC") ? -1 : 1; if (amount) sorting(2, (int[]){3, 0}, (int[]){1, 0}, (int[]){direction, 1}); else sorting(1, (int[]){0}, (int[]){0}, (int[]){direction}); int *a = orderrows(), *b = malloc((n + 1) * sizeof(int)), total = 0; for (int j = 0; j < n; j++) { int i = a[j]; if (lookup(&auth, r[i][1]) >= 0 && (eq(status, "ALL") || eq(status, r[i][2]))) b[total++] = i; } I offset = (page - 1) * size, ret = offset >= total ? 0 : total - offset; if (ret > size) ret = size; printf("OK %d %lld\n", total, ret); for (I j = 0; j < ret; j++) { int i = b[offset + j]; printf("%s %lld\n", r[i][0], iv(r[i][3])); } return 0; }C++完整参考实现
文件:
reference.cpp,与本题同文件夹。沿用C版的显式数组及结构体便于对照,按C++17编译;内存转换和字符串常量已适配C++类型要求,排序参数使用initializer_list。
运行方法:
g++ -std=c++17 -O2 reference.cpp -o reference,然后运行reference。测试数据通过标准输入送入,运行说明中的重定向不写入程序。/* Native C++17 reference. C-style data structures retained for comparison. */ #include <ctype.h> #include <initializer_list> #include <math.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> typedef long long I; typedef double F; typedef struct { char *key; I v[8]; F f[8]; char *s[4]; int flag; } Group; typedef struct { Group *a; int count, cap; int *slots; int size; } Map; char ***L; int *lc, lines, n; char **h, ***r; Map groups; I iv(const char *s) { return strtoll(s, NULL, 10); } F fv(const char *s) { return strtod(s, NULL); } int eq(const char *a, const char *b) { return strcmp(a, b) == 0; } F divide(F a, F b) { return b ? a / b : 0; } char *dupstr(const char *s) { char *p = (char *)malloc(strlen(s) + 1); strcpy(p, s); return p; } uint64_t hash(const char *s) { uint64_t v = 1469598103934665603ULL; for (; *s; s++) v = (v ^ (unsigned char)*s) * 1099511628211ULL; return v; } void mapinit(Map *m) { m->size = 131071; m->slots = (int *)malloc(m->size * sizeof(int)); for (int i = 0; i < m->size; i++) m->slots[i] = -1; m->count = 0; m->cap = 16; m->a = (Group *)calloc(m->cap, sizeof(Group)); } int lookup(Map *m, const char *k) { int p = hash(k) % m->size; while (m->slots[p] >= 0) { int i = m->slots[p]; if (eq(m->a[i].key, k)) return i; p = (p + 1) % m->size; } return -1; } int ensure(Map *m, const char *k) { int i = lookup(m, k); if (i >= 0) return i; if (m->count == m->cap) { m->cap *= 2; m->a = (Group *)realloc(m->a, m->cap * sizeof(Group)); } i = m->count++; memset(&m->a[i], 0, sizeof(Group)); m->a[i].key = dupstr(k); int p = hash(k) % m->size; while (m->slots[p] >= 0) p = (p + 1) % m->size; m->slots[p] = i; return i; } Group *get(Map *m, const char *k) { int i = ensure(m, k); return &m->a[i]; } char *pairkey(const char *a, const char *b) { size_t z = strlen(a) + strlen(b) + 2; char *p = (char *)malloc(z); snprintf(p, z, "%s\037%s", a, b); return p; } char *numpair(I a, const char *b) { char q[40]; snprintf(q, sizeof(q), "%lld", a); return pairkey(q, b); } int cmpkey(const void *a, const void *b) { return strcmp((*(Group *const *)a)->key, (*(Group *const *)b)->key); } Group **ordered(Map *m, int (*cmp)(const void *, const void *)) { Group **a = (Group **)malloc((m->count + 1) * sizeof(Group *)); for (int i = 0; i < m->count; i++) a[i] = &m->a[i]; qsort(a, m->count, sizeof(Group *), cmp ? cmp : cmpkey); return a; } int fields[8], modes[8], dirs[8], sortcount; void sorting(int count, std::initializer_list<int> a, std::initializer_list<int> b, std::initializer_list<int> c) { sortcount = count; auto x = a.begin(), y = b.begin(), z = c.begin(); for (int i = 0; i < count; i++) { fields[i] = *x++; modes[i] = *y++; dirs[i] = *z++; } } int rowcmp(const void *a, const void *b) { int ia = *(const int *)a, ib = *(const int *)b; for (int j = 0; j < sortcount; j++) { int f = fields[j], v; if (modes[j]) { F x = fv(r[ia][f]), y = fv(r[ib][f]); v = (x > y) - (x < y); } else v = strcmp(r[ia][f], r[ib][f]); if (v) return dirs[j] * v; } return (ia > ib) - (ia < ib); } int *orderrows(void) { int *a = (int *)malloc((n + 1) * sizeof(int)); for (int i = 0; i < n; i++) a[i] = i; qsort(a, n, sizeof(int), rowcmp); return a; } int cmpnum(const void *a, const void *b) { I x = (*(Group *const *)a)->v[0], y = (*(Group *const *)b)->v[0]; return (x > y) - (x < y); } int cmpdaykey(const void *a, const void *b) { Group *x = *(Group *const *)a, *y = *(Group *const *)b; int c = (x->v[0] > y->v[0]) - (x->v[0] < y->v[0]); return c ? c : strcmp(x->s[0], y->s[0]); } int cmpregionmonth(const void *a, const void *b) { Group *x = *(Group *const *)a, *y = *(Group *const *)b; int c = strcmp(x->s[0], y->s[0]); return c ? c : (x->v[0] > y->v[0]) - (x->v[0] < y->v[0]); } int cmpint(const void *a, const void *b) { I x = *(const I *)a, y = *(const I *)b; return (x > y) - (x < y); } int cmpdistance(const void *a, const void *b) { Group *x = *(Group *const *)a, *y = *(Group *const *)b; int c = (x->f[0] > y->f[0]) - (x->f[0] < y->f[0]); if (c) return c; c = strcmp(x->s[0], y->s[0]); return c ? c : (x->v[0] > y->v[0]) - (x->v[0] < y->v[0]); } int cmprisk(const void *a, const void *b) { Group *x = *(Group *const *)a, *y = *(Group *const *)b; int c = (y->v[0] > x->v[0]) - (y->v[0] < x->v[0]); if (c) return c; c = (y->v[1] > x->v[1]) - (y->v[1] < x->v[1]); if (c) return c; c = strcmp(x->s[0], y->s[0]); return c ? c : (x->v[2] > y->v[2]) - (x->v[2] < y->v[2]); } int digitstr(const char *s) { for (; *s; s++) if (*s < '0' || *s > '9') return 0; return 1; } I fixed6(const char *s) { int neg = *s == '-'; if (*s == '-' || *s == '+') s++; I a = 0, b = 0; while (*s && *s != '.') a = a * 10 + (*s++ - '0'); if (*s == '.') s++; int i = 0; while (*s && i < 6) { b = b * 10 + (*s++ - '0'); i++; } while (i++ < 6) b *= 10; return (neg ? -1 : 1) * (a * 1000000 + b); } void load(void) { int cap = 32; L = (char ***)malloc(cap * sizeof(char **)); lc = (int *)malloc(cap * sizeof(int)); lines = 0; while (1) { int z = 128, len = 0, c; char *s = (char *)malloc(z); while ((c = getchar()) != EOF && c != '\n') { if (len + 1 >= z) s = (char *)realloc(s, z *= 2); s[len++] = (char)c; } if (c == EOF && !len) { free(s); break; } s[len] = 0; int k = 16, q = 0; char **t = (char **)malloc(k * sizeof(char *)); char *p = s; while (*p) { while (*p && isspace((unsigned char)*p)) *p++ = 0; if (!*p) break; if (q == k) t = (char **)realloc(t, (k *= 2) * sizeof(char *)); t[q++] = p; while (*p && !isspace((unsigned char)*p)) p++; } if (q) { if (lines == cap) { cap *= 2; L = (char ***)realloc(L, cap * sizeof(char **)); lc = (int *)realloc(lc, cap * sizeof(int)); } L[lines] = t; lc[lines++] = q; } else { free(t); free(s); } if (c == EOF) break; } h = L[0]; n = (int)iv(h[0]); r = L + 1; mapinit(&groups); } int main(void) { load(); char *user = h[0], *status = h[1], *sort = h[2], *dir = h[3]; I page = iv(h[4]), size = iv(h[5]); int u = (int)iv(L[1][0]), pos = 2, known = 0; Map auth; mapinit(&auth); for (int j = 0; j < u; j++, pos++) if (eq(L[pos][0], user)) { known = 1; int c = (int)iv(L[pos][1]); for (int z = 0; z < c; z++) get(&auth, L[pos][z + 2]); } n = (int)iv(L[pos++][0]); r = L + pos; if ((!eq(status, "ALL") && !eq(status, "PAID") && !eq(status, "CANCELLED")) || (!eq(sort, "ID") && !eq(sort, "AMOUNT")) || (!eq(dir, "ASC") && !eq(dir, "DESC")) || page < 1 || size < 1 || size > 100) { puts("ERROR INVALID_PARAM"); return 0; } if (!known) { puts("ERROR FORBIDDEN"); return 0; } int amount = eq(sort, "AMOUNT"), direction = eq(dir, "DESC") ? -1 : 1; if (amount) sorting(2, {3, 0}, {1, 0}, {direction, 1}); else sorting(1, {0}, {0}, {direction}); int *a = orderrows(), *b = (int *)malloc((n + 1) * sizeof(int)), total = 0; for (int j = 0; j < n; j++) { int i = a[j]; if (lookup(&auth, r[i][1]) >= 0 && (eq(status, "ALL") || eq(status, r[i][2]))) b[total++] = i; } I offset = (page - 1) * size, ret = offset >= total ? 0 : total - offset; if (ret > size) ret = size; printf("OK %d %lld\n", total, ret); for (I j = 0; j < ret; j++) { int i = b[offset + j]; printf("%s %lld\n", r[i][0], iv(r[i][3])); } return 0; }Java完整参考实现
文件:
Main.java,与本题同文件夹。Main为入口,solve为本题业务计算;long保存整数和金额,double计算普通比率,TreeMap按字符串键有序输出。精确乘积用BigInteger,不能改用int累计金额。
运行方法:
javac -encoding UTF-8 Main.java,然后java Main。测试数据通过标准输入送入,运行说明中的重定向不写入程序。import java.io.*; import java.math.*; import java.util.*; public class Main { static List<String[]> L = new ArrayList<>(), r; static String[] h; static int n; static StringBuilder output = new StringBuilder(); static class G { long[] v = new long[8]; double[] f = new double[8]; String[] s = new String[4]; } static TreeMap<String, G> groups = new TreeMap<>(); static G g(Map<String, G> m, String key) { return m.computeIfAbsent(key, k -> new G()); } static long I(String s) { return Long.parseLong(s); } static double N(String s) { return Double.parseDouble(s); } static String f(double x) { return String.format(Locale.ROOT, "%.6f", x); } static double div(double a, double b) { return b == 0 ? 0 : a / b; } static void out(Object... values) { for (int j = 0; j < values.length; j++) { if (j > 0) output.append(' '); output.append(values[j]); } output.append('\n'); } static List<String[]> rows(int start, int count) { return new ArrayList<>(L.subList(start, start + count)); } static String key(String a, String b) { return a + "\u001f" + b; } static BigInteger fixed(String s) { return new BigDecimal(s).movePointRight(6).toBigIntegerExact(); } static void sortRows(List<String[]> rows, int[] fields, boolean[] numeric, int[] dirs) { rows.sort((a, b) -> { for (int j = 0; j < fields.length; j++) { int p = fields[j]; int c = numeric[j] ? Double.compare(N(a[p]), N(b[p])) : a[p].compareTo(b[p]); if (c != 0) return c * dirs[j]; } return 0; }); } static void solve() throws Exception { String user = h[0], status = h[1], sort = h[2], direction = h[3]; long page = I(h[4]), size = I(h[5]); int users = (int) I(L.get(1)[0]), pos = 2; Map<String, Set<String>> auth = new HashMap<>(); for (int i = 0; i < users; i++) { String[] x = L.get(pos++); auth.put(x[0], new HashSet<>(Arrays.asList(x).subList(2, x.length))); } n = (int) I(L.get(pos++)[0]); r = rows(pos, n); if (!Set.of("ALL", "PAID", "CANCELLED").contains(status) || !Set.of("ID", "AMOUNT").contains(sort) || !Set.of("ASC", "DESC").contains(direction) || page < 1 || size < 1 || size > 100) { out("ERROR", "INVALID_PARAM"); return; } if (!auth.containsKey(user)) { out("ERROR", "FORBIDDEN"); return; } r.removeIf( x -> !auth.get(user).contains(x[1]) || (!status.equals("ALL") && !x[2].equals(status))); int dir = direction.equals("DESC") ? -1 : 1; if (sort.equals("AMOUNT")) sortRows(r, new int[] {3, 0}, new boolean[] {true, false}, new int[] {dir, 1}); else sortRows(r, new int[] {0}, new boolean[] {false}, new int[] {dir}); long offset = (page - 1) * size; int returned = offset >= r.size() ? 0 : (int) Math.min(size, r.size() - offset); out("OK", r.size(), returned); for (int j = 0; j < returned; j++) { String[] x = r.get((int) offset + j); out(x[0], I(x[3])); } } public static void main(String[] args) throws Exception { BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); String line; while ((line = reader.readLine()) != null) if (!line.trim().isEmpty()) L.add(line.trim().split("\\s+")); h = L.get(0); n = h[0].matches("[0-9]+") ? Integer.parseInt(h[0]) : 0; r = rows(1, Math.min(n, L.size() - 1)); solve(); System.out.print(output); } }Kotlin完整参考实现
文件:
Main.kt,与本题同文件夹。main读取输入,solve执行本题步骤。val表示不可重新赋值,var表示可变状态;LongArray保存整数,DoubleArray保存比率,TreeMap固定输出顺序。!!仅用于题面保证已存在的数据。
运行方法:
kotlinc Main.kt -jvm-target 17 -include-runtime -d main.jar,然后java -jar main.jar。测试数据通过标准输入送入,运行说明中的重定向不写入程序。import java.math.BigDecimal import java.math.BigInteger import java.util.Locale import java.util.TreeMap data class G( val v: LongArray = LongArray(8), val f: DoubleArray = DoubleArray(8), val s: Array<String?> = arrayOfNulls(4), ) lateinit var L: List<Array<String>> lateinit var h: Array<String> lateinit var r: MutableList<Array<String>> var n = 0 val output = StringBuilder() val groups = TreeMap<String, G>() fun g(m: MutableMap<String, G>, key: String): G = m.getOrPut(key) { G() } fun I(s: String): Long = s.toLong() fun N(s: String): Double = s.toDouble() fun f(x: Number): String = String.format(Locale.ROOT, "%.6f", x.toDouble()) fun div(a: Number, b: Number): Double = if (b.toDouble() == 0.0) 0.0 else a.toDouble() / b.toDouble() fun out(vararg values: Any?) { output.append(values.joinToString(" ")).append('\n') } fun rows(start: Int, count: Int): MutableList<Array<String>> = L.subList(start, start + count).toMutableList() fun key(a: String, b: String): String = a + "\u001f" + b fun fixed(s: String): BigInteger = BigDecimal(s).movePointRight(6).toBigIntegerExact() fun sortRows( rows: MutableList<Array<String>>, fields: IntArray, numeric: BooleanArray, dirs: IntArray, ) { rows.sortWith( Comparator { a, b -> var result = 0 for (j in fields.indices) { val p = fields[j] val c = if (numeric[j]) N(a[p]).compareTo(N(b[p])) else a[p].compareTo(b[p]) if (c != 0) { result = c * dirs[j] break } } result } ) } fun solve() { val user = h[0] val status = h[1] val sort = h[2] val direction = h[3] val page = I(h[4]) val size = I(h[5]) val users = I(L[1][0]).toInt() var pos = 2 val auth = HashMap<String, Set<String>>() repeat(users) { val x = L[pos++] auth[x[0]] = x.drop(2).toSet() } n = I(L[pos++][0]).toInt() r = rows(pos, n) if ( status !in setOf("ALL", "PAID", "CANCELLED") || sort !in setOf("ID", "AMOUNT") || direction !in setOf("ASC", "DESC") || page < 1 || size < 1 || size > 100 ) { out("ERROR", "INVALID_PARAM") return } if (user !in auth) { out("ERROR", "FORBIDDEN") return } r.removeAll { it[1] !in auth[user]!! || (status != "ALL" && it[2] != status) } val dir = if (direction == "DESC") -1 else 1 if (sort == "AMOUNT") sortRows(r, intArrayOf(3, 0), booleanArrayOf(true, false), intArrayOf(dir, 1)) else sortRows(r, intArrayOf(0), booleanArrayOf(false), intArrayOf(dir)) val offset = (page - 1) * size val returned = if (offset >= r.size) 0 else minOf(size, r.size - offset).toInt() out("OK", r.size, returned) for (j in 0 until returned) { val x = r[offset.toInt() + j] out(x[0], I(x[3])) } } fun main() { L = System.`in` .bufferedReader() .lineSequence() .filter { it.isNotBlank() } .map { it.trim().split(Regex("\\s+")).toTypedArray() } .toList() h = L[0] n = h[0].toIntOrNull() ?: 0 r = rows(1, minOf(n, L.size - 1)) solve() print(output) }JavaScript/Node.js完整参考实现
文件:
reference.js,与本题同文件夹。在Node.js运行,Map和Set保存状态;Number计算普通数值,超过安全整数范围的金额累计及精确乘积使用BigInt。
运行方法:
node reference.js。测试数据通过标准输入送入,运行说明中的重定向不写入程序。// Node.js reference. All task code runs natively in JavaScript. const fs = require('fs'); const lines = fs.readFileSync(0, 'utf8').trim().split(/\r?\n/).filter(x => x.trim()).map(x => x .trim().split(/\s+/)); const h = lines[0]; let n = Number(h[0]), r = lines.slice(1, 1 + n); const N = Number, cmp = (a, b) => a < b ? -1 : a > b ? 1 : 0; const f = x => Number(x).toFixed(6), div = (a, b) => b ? a / b : 0; const out = []; const emit = (...a) => out.push(a.join(' ')); const grouped = new Map(); const get = (k, init = () => [0, 0]) => { if (!grouped.has(k)) grouped.set(k, init()); return grouped.get(k); }; const keys = m => [...m.keys()].sort(cmp); const pair = (a, b) => JSON.stringify([a, b]); function fixed6(s) { let neg = s[0] === '-'; s = s.replace(/^[+-]/, ''); let [a, b = ''] = s.split('.'); return (neg ? -1n : 1n) * (BigInt(a) * 1000000n + BigInt((b + '000000').slice(0, 6))); } const [user, status, sort, direction, ps, ss] = h, page = N(ps), size = N(ss); let pos = 2; const u = N(lines[1][0]), auth = new Map(); for (let j = 0; j < u; j++) { const [name, c, ...objects] = lines[pos++]; auth.set(name, new Set(objects)); } n = N(lines[pos++][0]); r = lines.slice(pos, pos + n); if (!['ALL', 'PAID', 'CANCELLED'].includes(status) || !['ID', 'AMOUNT'].includes(sort) || !['ASC', 'DESC' ].includes(direction) || page < 1 || size < 1 || size > 100) emit('ERROR', 'INVALID_PARAM'); else if (!auth.has(user)) emit('ERROR', 'FORBIDDEN'); else { const dir = direction === 'DESC' ? -1 : 1, a = r.filter(x => auth.get(user).has(x[1]) && (status === 'ALL' || x[2] === status)); a.sort((a, b) => sort === 'ID' ? dir * cmp(a[0], b[0]) : dir * cmp(N(a[3]), N(b[3])) || cmp(a[0], b[0])); const sub = a.slice((page - 1) * size, page * size); emit('OK', a.length, sub.length); for (const [id, o, s, a] of sub) emit(id, N(a)); } process.stdout.write(out.join("\n") + (out.length ? "\n" : ""));Bash(GNU awk)完整参考实现
文件:
reference.sh,与本题同文件夹。Bash负责启动和环境检查,GNU awk在同一脚本中解析数据并完成本题算法。C[行,列]从1计数,num按十进制转换,i输出整数,f输出六位小数;-M启用GMP/MPFR,PREC=256。不是普通awk兼容脚本,必须满足运行依赖。
运行方法:
bash reference.sh(需支持-M的GNU awk)。测试数据通过标准输入送入,运行说明中的重定向不写入程序。#!/usr/bin/env bash # Requires GNU awk with GMP/MPFR (-M). No Python, Java or Node.js subprocess. set -euo pipefail export LC_ALL=C if ! command -v gawk >/dev/null 2>&1; then printf "%s\n" "GNU awk (gawk) with -M support is required" >&2 exit 1 fi exec gawk -M -v PREC=256 ' BEGIN { OFS=" "; OFMT="%.0f"; CONVFMT="%.30g" } NF>0 { lines++; nf[lines]=NF; for(j=1;j<=NF;j++) C[lines,j]=$j } function num(s) { return s+0 } function eq(a,b) { return (a "")== (b "") } function cmpstr(a,b) { a=a "";b=b "";return a<b?-1:a>b?1:0 } function f(x) { return sprintf("%.6f",x) } function i(x) { return sprintf("%.0f",x) } function div(a,b) { a+=0;b+=0;return b!=0?a/b:0 } function abs(x) { return x<0?-x:x } function min(a,b) { return a<b?a:b } function max(a,b) { return a>b?a:b } function floor(x, t) { t=int(x);return t>x?t-1:t } function fixed(s, a,negative,b) { negative=substr(s,1,1)=="-";sub(/^[+-]/,"",s);split(s,a,/[.]/) b=substr(a[2] "000000",1,6) return (negative?-1:1)*(num(a[1])*1000000+num(b)) } function pair(a,b) { return (a "") SUBSEP (b "") } function group(k) { groups[k]=1 } function sorted_groups() { return asorti(groups,idx,"@ind_str_asc") } function compare_rows(a,av,b,bv, j,p,x,y,c) { for(j=1;j<=sort_count;j++) { p=fields[j];x=C[base+a-1,p];y=C[base+b-1,p] if(modes[j]){x=num(x);y=num(y);c=x<y?-1:x>y?1:0}else c=cmpstr(x,y) if(c)return dirs[j]*c } return (a+0)<(b+0)?-1:(a+0)>(b+0)?1:0 } function order_rows(fs,ms,ds, j) { sort_count=split(fs,fields," ");split(ms,modes," ");split(ds,dirs," ") delete order;for(j=1;j<=n;j++)order[j]=j return asorti(order,idx,"compare_rows") } function compare_neighbors(a,av,b,bv, c) { c=dist[a]<dist[b]?-1:dist[a]>dist[b]?1:0;if(c)return c c=cmpstr(C[base+a-1,1],C[base+b-1,1]);return c?c:(a+0)-(b+0) } function compare_day_key(a,av,b,bv, c) { c=values[a,1]<values[b,1]?-1:values[a,1]>values[b,1]?1:0 return c?c:cmpstr(strings[a,1],strings[b,1]) } function compare_region_month(a,av,b,bv, c) { c=cmpstr(strings[a,1],strings[b,1]);return c?c:values[a,1]-values[b,1] } function compare_risk(a,av,b,bv, c) { c=values[b,1]-values[a,1];if(c)return c c=values[b,2]-values[a,2];if(c)return c c=cmpstr(strings[a,1],strings[b,1]);return c?c:(a+0)-(b+0) } END { for(j=1;j<=nf[1];j++)h[j]=C[1,j] n=num(h[1]);base=2 user=h[1];status=h[2];sort=h[3];direction=h[4];page=num(h[5]);size=num(h[6]);users=num(C[2,1]);pos=3;for(j=1;j<=users;j++){name=C[pos,1];known[name]=1;for(z=3;z<=nf[pos];z++)auth[pair(name,C[pos,z])]=1;pos++};n=num(C[pos++,1]);base=pos if((status!="ALL"&&status!="PAID"&&status!="CANCELLED")||(sort!="ID"&&sort!="AMOUNT")||(direction!="ASC"&&direction!="DESC")||page<1||size<1||size>100){print "ERROR INVALID_PARAM";exit};if(!(user in known)){print "ERROR FORBIDDEN";exit} dir=direction=="DESC"?-1:1;if(sort=="AMOUNT")order_rows("4 1","1 0",dir " 1");else order_rows("1","0",dir "") total=0;for(j=1;j<=n;j++){row=base+idx[j]-1;if(pair(user,C[row,2]) in auth&&(status=="ALL"||eq(status,C[row,3])))selected[++total]=row};offset=(page-1)*size;ret=offset>=total?0:min(size,total-offset);print "OK",i(total),i(ret);for(j=1;j<=ret;j++){row=selected[offset+j];print C[row,1],i(num(C[row,4]))} } '测试与评分解释
公开样例用于理解与自测;隐藏测试分项计分,每题25分。现有4组主要覆盖空输入、单记录、重复及次序变化;它们不代表所有专业边界已充分覆盖。命题人还应独立设计能区分错误实现的测试,例如错误分母、错误等号、先过滤后去重、全部排序键反转及精度阈值。参考实现通过已有测试并不能证明题库已经达到正式比赛部署标准。
本题另提供独立计算期望的补充案例:参数错误优先级、已知用户无可读对象。输入输出见同目录
boundary_*.in/.out,已按本版所列环境核对;这些案例尚未加入原25分配置,部署时需统一调整测试点分值,不能直接叠加分数。
- 1
Information
- ID
- 863
- Time
- 2000ms
- Memory
- 256MiB
- Difficulty
- 6
- Tags
- # Submissions
- 0
- Accepted
- 0
- Uploaded By