
[{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/ctf/problem/","section":"WP和杂文","summary":"","title":"题解","type":"ctf"},{"content":" 欢迎来到我的新博客。 # 时隔多年，我又重新开始经营博客了。\n上次写 post 已经是 22 年的事了，还记得当时高一打完 NOIP 半退役之后就没再更新博客了（不过本来就没写过几篇文章，主要是懒），NOIP2022 游记本来想写完，但是结果一直咕到现在。\n现在进了大学，有了更多的时间和有趣的经历，想着把这些写下来，就重建了 blog。\n这两天把老博客的文章搬了过来，重新看以前写的题解或者游记，感触颇深。我向来没有记日记的习惯，但是看着以前的文章，觉得有些东西还是记下来好。\n","date":"20 April 2026","externalUrl":null,"permalink":"/posts/hello/","section":"随笔日记","summary":"","title":"Hello","type":"posts"},{"content":"","date":"16 June 2026","externalUrl":null,"permalink":"/","section":"Rencj's Blog","summary":"","title":"Rencj's Blog","type":"page"},{"content":" “华为杯”杭州电子科技大学第二十六届大学生程序设计竞赛 # 个人赛，解题数*50+前两次 CSP 的总分来选拔暑假集训队。\n赛前 # 以为是一点开打，11 点 45 分下楼拿外卖的时候发现是 12 点比赛，来不及吃饭，直接去打了。（饿饿饿）\n赛中 # 准点赶到，登完号看题，想随榜做题，发现过了 5 min 才有人过题，意识到这次题不简单。\n\\(1007\\) 投票游戏\n应该是签到题，看完题，首先想到 如果一个数出现次数 \\(\u003en/2\\)，那么无论怎么排列，最终胜者肯定是这个数，其他数没法把这个数全消掉。然后猜一下结论，其余情况，答案应该是不同数的种数，因为想让一个数成为胜者，只需在前面把其他数消掉，最后放这个数就行了。\n\\(Code:\\)\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; const int N=1e5+5; int a[N]; signed main(){ cin.tie(0)-\u0026gt;sync_with_stdio(0); int T;cin\u0026gt;\u0026gt;T; while(T--){ int n;cin\u0026gt;\u0026gt;n; for(int i=1;i\u0026lt;=n;i++)cin\u0026gt;\u0026gt;a[i]; sort(a+1,a+n+1); int res=1,mx=0,len=1; for(int i=2;i\u0026lt;=n;i++){ if(a[i]==a[i-1])len++; else len=1,res++; mx=max(mx,len); } if(mx\u0026lt;=n/2)cout\u0026lt;\u0026lt;res\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; else cout\u0026lt;\u0026lt;1\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; } return 0; } \\(1005\\) 鸡煲是区\n过的人第二多的，确实不难，但是刚开始想错了，以为是贪心，想了一些错误做法。\n直接说做法吧，把整个题写成式子：\\(n\u003c=(x+y^{'})^{z^{'}},m\u003e=y^{'}+2\\times z^{'}\\)，发现只有两个变量，而且两个变量最大不超过 \\(m\\)，所以直接枚举其中一个即可。\n\\(Code:\\)\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; #define int long long int n,m,x,y,z; signed main(){ cin.tie(0)-\u0026gt;sync_with_stdio(0); int T;cin\u0026gt;\u0026gt;T; while(T--){ cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;m\u0026gt;\u0026gt;x\u0026gt;\u0026gt;y\u0026gt;\u0026gt;z; if(x==0\u0026amp;\u0026amp;y==0){ cout\u0026lt;\u0026lt;\u0026#34;ji bao shi qu\\n\u0026#34;; continue; } bool flag=0; for(int i=0;i\u0026lt;=min(m,y);i++){ int t=x+i,s=m-i; if(t==0)continue; int zz=z; while(zz\u0026amp;\u0026amp;s\u0026gt;=2\u0026amp;\u0026amp;t\u0026lt;n){ zz--,s-=2,t*=2; } if(t\u0026gt;=n){ flag=1;break; } } if(!flag)cout\u0026lt;\u0026lt;\u0026#34;ji bao shi qu\\n\u0026#34;; else cout\u0026lt;\u0026lt;\u0026#34;ji bao bu shi qu\\n\u0026#34;; } return 0; } \\(1001\\) 取石子游戏\n看完题，是博弈论~~（一眼不会）~~，猜了半天结论，感觉都不对，看了其他题感觉也没那么好做，开始坐牢。\n比赛时间过半，意识到如果博弈双方都取 \\(2\\) 的话，最终步数只有 \\(log(n)\\) 很小，如果必胜方要更快，会取 \\(2^x\\)，来加快步数，然后发现 时限给了 \\(6s\\)，想写个爆搜试一下，发现直接过了，我甚至没做任何剪枝。。。\n\\(Code:\\)\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; #define int long long const int N=1005; int n,m,ans; void dfs(int n,int m,int s){ if(n\u0026lt;m*2){ if(s\u0026amp;1)ans=min(ans,s); return ; } if(s\u0026amp;1){ for(int i=m;i\u0026gt;=2;i--){ dfs(n/i,m,s+1); } } else{ for(int i=2;i\u0026lt;=m;i++){ dfs(n/i,m,s+1); } } } signed main(){ cin.tie(0)-\u0026gt;sync_with_stdio(0); int T;cin\u0026gt;\u0026gt;T; while(T--){ cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;m; ans=1e5; dfs(n,m,1); if(ans==1e5)cout\u0026lt;\u0026lt;\u0026#34;-1\\n\u0026#34;; else cout\u0026lt;\u0026lt;ans\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; } return 0; } \\(1002\\) e\n此时，时间只剩一个半小时了，上了个厕所调整一下，开始看有点思路的 \\(1002\\)。\n看完题，一眼丁 \\(DP\\)，感觉处理起来很棘手，设状态函数：\\(f_{i,j,b,c}\\) 表示 \\(s_{1 \\thicksim i}\\) 组成的字符串，有 \\(j\\) 个好的 e，第 \\(i\\) 个字符是 \\(b\\)，前一个字符是 \\(c\\) 的最少修改次数。\n预处理把 \\(erd\\) 赋值为 \\(012\\)，方便写转移条件。\n初始化：\\(\\begin{cases} f[1][0][a[1]][0]=0 \\\\ f[1][0][i][0]=1 \u0026{{s2}_{1}=\\text{'1'},i \\neq a_1} \\end{cases}\\)\n转移：\\(\\begin{cases} f[i][j][b][c]=min~{f[i-1][j][c][d]+(b!=a[i])} \u0026 \\text{!(i\u003e2 \\\u0026 b \\\u0026 d \\\u0026 b!=d \\\u0026 !c)} \\\\ f[i][j+1][b][c]=min~f[i-1][j][c][d]+(b!=a[i]) \u0026\\text{i\u003e2 \\\u0026 b \\\u0026 d \\\u0026 b!=d \\\u0026 !c} \\end{cases}\\)\n其他细节就不放了，自己看码。\n\\(Code:\\)\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; const int N=1005,inf=0x3f3f3f3f; int n; string s1,s2; int f[N][N][3][3]; int a[N]; signed main(){ cin.tie(0)-\u0026gt;sync_with_stdio(0); int T;cin\u0026gt;\u0026gt;T; while(T--){ cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;s1\u0026gt;\u0026gt;s2;s1=\u0026#34;~\u0026#34;+s1,s2=\u0026#34;~\u0026#34;+s2; for(int i=1;i\u0026lt;=n;i++){ if(s1[i]==\u0026#39;r\u0026#39;)a[i]=1; else if(s1[i]==\u0026#39;d\u0026#39;)a[i]=2; else a[i]=0; } memset(f,0x3f,sizeof(f)); f[1][0][a[1]][0]=0; if(s2[1]==\u0026#39;1\u0026#39;){ for(int i=0;i\u0026lt;=2;i++){ if(a[1]==i)continue; else f[1][0][i][0]=1; } } for(int i=2;i\u0026lt;=n;i++){ for(int j=0;j\u0026lt;=i/2;j++){ for(int b=0;b\u0026lt;=2;b++){ if(s2[i]==\u0026#39;0\u0026#39;\u0026amp;\u0026amp;b!=a[i])continue; for(int c=0;c\u0026lt;=2;c++){ for(int d=0;d\u0026lt;=2;d++){ if(i==2\u0026amp;\u0026amp;d)continue; if(i\u0026gt;2\u0026amp;\u0026amp;b\u0026amp;\u0026amp;d\u0026amp;\u0026amp;b!=d\u0026amp;\u0026amp;!c){ f[i][j+1][b][c]=min(f[i][j+1][b][c],f[i-1][j][c][d]+(b!=a[i])); } else f[i][j][b][c]=min(f[i][j][b][c],f[i-1][j][c][d]+(b!=a[i])); } } } } } for(int i=0;i\u0026lt;=n;i++){ int res=inf; for(int b=0;b\u0026lt;=2;b++){ for(int c=0;c\u0026lt;=2;c++){ res=min(res,f[n][i][b][c]); } } if(res==inf)cout\u0026lt;\u0026lt;\u0026#34;-1 \u0026#34;; else cout\u0026lt;\u0026lt;res\u0026lt;\u0026lt;\u0026#39; \u0026#39;; }cout\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; } return 0; } /* 1 8 rederded 00000000 -1 -1 0 -1 -1 -1 -1 -1 -1 */ 预处理写挂调了半天，最后 5 min 调完过题，下班。\n赛后 # 终榜：\n大部分人都是 4~5 个题，只是罚时高了，写的快可以拿三等奖。\n自我评价是打的中规中矩，确实也符合我这学期基本没怎么刷题的水平。\n最终也是进队了。暑假要没的放松了。。。\n","date":"16 June 2026","externalUrl":null,"permalink":"/acm/hdu%E6%A0%A1%E8%B5%9B26/","section":"题解和游记","summary":"","title":"第二十六届杭电校赛","type":"acm"},{"content":"","date":"16 June 2026","externalUrl":null,"permalink":"/acm/","section":"题解和游记","summary":"","title":"题解和游记","type":"acm"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/ctf/problem/misc/","section":"WP和杂文","summary":"","title":"MISC","type":"ctf"},{"content":"","date":"27 April 2026","externalUrl":null,"permalink":"/ctf/","section":"WP和杂文","summary":"","title":"WP和杂文","type":"ctf"},{"content":" 有趣的加密\n题目描述：格式为QCTF{XXX} # WP # 附件提示了 keyword：lovekfc\n放到 stegsolve 里改颜色通道，发现顶上有像素点：\n结合给了密钥，应该是 LSB 隐写，找工具解密后得到隐写内容：\nPVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf} 尝试多种常见加密无果，最后搜索得到是 Nihilist cipher（虚无密码），一个矩阵式替换密码。\n这道题关键字为：LOVEKFC 原26个英文字母为 ABCDEFGHIJKLMNOPQRSTUVWXYZ 把关键字提前后为 LOVEKFCABDGHIJMNPQRSTUWXYZ Decode:\na1 = \u0026#34;ABCDEFGHIJKLMNOPQRSTUVWXYZ\u0026#34; b1 = \u0026#34;LOVEKFCABDGHIJMNPQRSTUWXYZ\u0026#34; a2 = a1.lower() b2 = b1.lower() str = \u0026#34;PVSF{vVckHejqBOVX9C1c13GFfkHJrjIQeMwf}\u0026#34; for i in str: if(i in b1): print(a1[b1.index(i)],end=\u0026#34;\u0026#34;) elif(i in b2): print(a2[b2.index(i)],end=\u0026#34;\u0026#34;) else: print(i,end=\u0026#34;\u0026#34;) Output:\nQCTF{cCgeLdnrIBCX9G1g13KFfeLNsnMRdOwf} ","date":"27 April 2026","externalUrl":null,"permalink":"/ctf/problem/misc/xctf-keyword/","section":"WP和杂文","summary":"","title":"XCTF Keyword","type":"ctf"},{"content":" Team：杭电5队 # 第一次参加天梯赛团队赛，感觉是个规则比较抽象的比赛（一队 10 人打个人赛，求和算团体总分，学校前三队伍算学校总分），今年题比较阳间，但是赛时不知道怎么看榜，错失个人国二。\n最终得分 223，差 3 分国二。最后两题没骗分，喜提团队垫底，最终团队国三。\nL1，L2 整体感觉比前几年阳间，基本直接模拟就过了，一个半小时做完。\nL3-1 中等模拟，赛时写了个 \\(n^2\\) 做法，时限 100ms 两个点 \\(T\\) 了，以为卡 \\( n^2\\)，思考了一下用 set 优化到 log 写起来很麻烦，当时还有 40min，所以直接去看后面两个题了。\nL3-3 看了眼只会 dfs 找全部可能取法，感觉拿不了分，先去看L3-2，感觉正解像 dp 或者高妙贪心，因为看不了榜，不知道能骗多少分，当时想了个比较难写的贪心做法，就是每次找两种颜色中相同类型个数最大的删，但是要动态维护，写起来也挺麻烦的，结果没调完。\n赛后和同学沟通后才知道，L3-1 \\(n^2\\) 能过，但是我用 vector 应该常数太大 \\(T\\) 了。L3-2 写个简单的贪心就能拿 12/16 分。L3-3 打个 dfs 爆搜就能拿 10分，全场暴力打满 256 就有国一，只能说没看榜确实可惜，至少拿国二。\n学校排名，我们第三，也是和往年差不多，主要前面题比较阳间，后面就是比难题得分了，这个确实和前两位有差距。\n","date":"21 April 2026","externalUrl":null,"permalink":"/acm/gplt2026/","section":"题解和游记","summary":"","title":"GPLT2026天梯赛","type":"acm"},{"content":"","date":"20 April 2026","externalUrl":null,"permalink":"/posts/","section":"随笔日记","summary":"","title":"随笔日记","type":"posts"},{"content":" 咕 # ","date":"11 March 2026","externalUrl":null,"permalink":"/about/","section":"Rencj's Blog","summary":"","title":"About me","type":"page"},{"content":" 队伍 ：Rencj #000a9a # Week1 # MISC # 打好基础 # txt是一堆表情包，应该是Base100加密，直接放进随波逐流一键解码\n发现乱码，继续放进去一键解码，发现是base混合多重解码\nshiori不想找女友 # zip解压得到一个加密zip和一张png，png中有规律黑白像素，010Editor中发现有像素提取提示\n根据提示写代码提取像素生成新图片,得到压缩包密码This_is_a_key_for_u\nfrom PIL import Image img = Image.open(\u0026#34;shiori.png\u0026#34;) width, height = img.size pixels = img.load() start_x, start_y = 10, 10 step_x, step_y = 7, 7 target_width = 450 # 题目给出的 column_num extracted_bits = [] # 3. 遍历原图提取数据 (流式提取) for y in range(start_y, height, step_y): for x in range(start_x, width, step_x): r, g, b = pixels[x, y] if r + g + b \u0026lt; 300: extracted_bits.append(1) else: extracted_bits.append(0) print(f\u0026#34;共提取到 {len(extracted_bits)} 个数据点\u0026#34;) # 5. 计算新图高度并重绘 target_height = len(extracted_bits) // target_width if len(extracted_bits) % target_width != 0: target_height += 1 print(f\u0026#34;正在生成新图: {target_width} x {target_height}\u0026#34;) new_img = Image.new(\u0026#39;RGB\u0026#39;, (target_width, target_height), (255, 255, 255)) new_pixels = new_img.load() idx = 0 for y in range(target_height): for x in range(target_width): if idx \u0026gt;= len(extracted_bits): break if extracted_bits[idx] == 1: new_pixels[x, y] = (0, 0, 0) idx += 1 new_img.show() new_img.save(\u0026#34;flag_result.png\u0026#34;) 解压加密zip得到一张jpg，放进010Editor中发现结构是png，后缀改成png发现图片是张空白，扔进Stegsolve中改通道，得到flag\n[REDACTED] # 兔兔说她对一份机密文件进行了“完美的”脱敏处理。还好你在发送之前检查了一下。\n提交说明： 附件中包含四段敏感字符串，格式为 [1-4]:.+。用下划线字符 _ 连接去掉序号的四段字符串，包裹 hgame{} 后提交。例如，若四段敏感字符串分别为 1:Example、2:Redacted、3:Secret、4:Strings_!，则提交的 flag 为 hgame{Example_Redacted_Secret_Strings_!}。\n解压得 manual.pdf，打开发现有两段黑色块覆盖文本，有一张图片中的一小段文本被遮。\n第一段文本直接复制出来得到 1:PAR4D0X。\n第二段黑色块中文字直接复制出来是乱码，应该是用了奇怪的字体，把 pdf 丢进轻闪PDF 中对第二段黑色块上遮盖的黑色块删除并把文本颜色改成黑色得到文本\nOCR 一下 base64 解密\n得到 2:AllCl3arToPr0ceed\n在轻闪 PDF 中把图片提取出来，放入 Stegsolve 中改通道，得到 3:Sh4m1R\n在010Editor中发现文件末尾有增量更新，删掉后打开 pdf 找到 4:D0cR3qu3st3r_Tutu\n组合得 flag：hgame{PAR4D0X_AllCl3arToPr0ceed_Sh4m1R_D0cR3qu3st3r_Tutu}\nCrypto # Classic # task.py是一个泄露 p 高位 leak 的 RSA，可用 Coppersmith 攻击可以还原完整的 p，然后分解 n 并解密得到明文 m；\ntxt里是一段密文VUHHX{Tti Julxmzooz sm zhq Rlc azh ane Apk}\n先解密明文：\nfrom Crypto.Util.number import long_to_bytes n = 103581608824736882681702548494306557458428217716535853516637603198588994047254920265300207713666564839896694140347335581147943392868972670366375164657970346843271269181099927135708348654216625303445930822821038674590817017773788412711991032701431127674068750986033616138121464799190131518444610260228947206957 leak = 6614588561261434084424582030267010885893931492438594708489233399180372535747474192128 c = 38164947954316044802514640871285562707869793354907165622336840432488893861610651450862702262363481097538127040490478908756416851240578677195459996252755566510786486707340107057971217557295217072867673485369358370289506549932119879791474279677563080377456592139035501163534305008864900509896586230830001710243 e = 65537 p_high = leak \u0026lt;\u0026lt; 230 PR.\u0026lt;x\u0026gt; = PolynomialRing(Zmod(n)) f = p_high + x res = f.small_roots(X=2^230, beta=0.5, epsilon=0.03) p = int(p_high + res[0]) q = n // p d = inverse_mod(e, (p-1)*(q-1)) m = pow(c, d, n) print(long_to_bytes(m)) 解得b'Vigenere,key=hgame'，得到密文是 Vigenere 加密，密钥是 hgame ，解密得 VIDAR{The Collision of the New and the Old}\nFlux # 首先要解出参数 (a, b, c)\n题目给出了连续的四个输出 x1, x2, x3, x4，它们满足 \\(x_{i+1} = (a x_i^2 + b x_i + c) \\pmod n\\)\n通过构建方程组：\n\\(\\begin{cases} x_2 - x_3 = a(x_1^2 - x_2^2) + b(x_1 - x_2) \\\\ x_3 - x_4 = a(x_2^2 - x_3^2) + b(x_2 - x_3) \\end{cases}\\)\n这是一个关于 a, b 的线性方程组，可以直接求解。解出 a, b 后带回原方程求 c。\n然后还原初始****哈希值 \\( h(x_0) \\):\n已知 \\(x_1 = (a x_0^2 + b x_0 + c) \\pmod n\\)，这是一个关于 \\(x_0 \\) 的一元二次方程。\n变形为 \\(a x_0^2 + b x_0 + (c - x_1) = 0 \\pmod n\\)。\n使用求根公式（要用 Tonelli-Shanks 算法求解二次剩余）即可得到 h 的两个可能值。\n最后逆推 Key :\nshash 函数的核心逻辑是：\\(x_{new} = (key \\cdot x_{old}) \\oplus c\\)（忽略 mask 和位移细节）。\n由于涉及乘法和异或，低位的运算结果只依赖于低位的输入。也就是说，结果的第 i 位只取决于 key 的第 \\(0 \\sim i\\) 位。\n利用这一特性，我们可以逐位爆破 key：\n先猜 key 的第 0 位（0 或 1），检查哈希结果的第 0 位是否匹配。 若匹配，再猜第 1 位，检查结果的第 1 位是否匹配（需累计计算）。 以此类推，直到恢复出 70 位的 key。 import libnum from Crypto.Util.number import * # 1. 数据准备 data = [ 259574080588277578527410299002867735023798216356763871244908783144610527451187, 954408432127642232121971189554605898975195279656270435479524132958262607464595, 902461413507524665418054778947872375987908929501605791883614896110219051835312, 92554599789649828855418140915311664257163346975111310560999959858873425332254 ] n = 1000081851369905197391900354119969103949357074708517572641608490670646955240669 x1, x2, x3, x4 = data print(\u0026#34;[+] Step 1: Recovering LCG parameters...\u0026#34;) A1 = (pow(x1, 2, n) - pow(x2, 2, n)) % n B1 = (x1 - x2) % n C1 = (x2 - x3) % n A2 = (pow(x2, 2, n) - pow(x3, 2, n)) % n B2 = (x2 - x3) % n C2 = (x3 - x4) % n det = (A1 * B2 - A2 * B1) % n det_inv = libnum.invmod(det, n) a = (C1 * B2 - C2 * B1) * det_inv % n b = (A1 * C2 - A2 * C1) * det_inv % n c = (x2 - a * pow(x1, 2, n) - b * x1) % n print(f\u0026#34; a = {a}\u0026#34;) print(f\u0026#34; b = {b}\u0026#34;) print(f\u0026#34; c = {c}\u0026#34;) print(\u0026#34;[+] Step 2: Recovering h (x0)...\u0026#34;) # 方程: a*h^2 + b*h + (c - x1) = 0 target_const = (c - x1) % n discriminant = (pow(b, 2, n) - 4 * a * target_const) % n # 修复点：libnum.sqrtmod 需要传入 factors 字典 {n: 1} factors = {n: 1} h_candidates = [] if libnum.has_sqrtmod(discriminant, factors): inv_2a = libnum.invmod(2 * a, n) roots = list(libnum.sqrtmod(discriminant, factors)) for r in roots: x0 = ((-b + r) % n) * inv_2a % n h_candidates.append(x0) print(f\u0026#34; Candidates: {h_candidates}\u0026#34;) else: print(\u0026#34;[-] No solution for h\u0026#34;) exit() print(\u0026#34;[+] Step 3: Recovering Key (Bit-wise DFS)...\u0026#34;) msg = \u0026#34;Welcome to HGAME 2026!\u0026#34; final_xor = len(msg) # 目标哈希函数逻辑def shash(value: str, key: int) -\u0026gt; int: length = len(value) mask = (1 \u0026lt;\u0026lt; 256) - 1 x = (ord(value[0]) \u0026lt;\u0026lt; 7) \u0026amp; mask for c in value: x = (key * x) \u0026amp; mask ^ ord(c) x ^= length \u0026amp; mask return x # 逐位爆破求解器def solve_recursive(k_curr, bit_to_find, target_val):if bit_to_find == 70: # 题目限制 key \u0026lt; 70 bitsreturn k_curr # 当前检查的掩码（只看低 bit_to_find+1 位） mask_check = (1 \u0026lt;\u0026lt; (bit_to_find + 1)) - 1# 尝试当前位是 0 还是 1for bit in [0, 1]: k_next = k_curr | (bit \u0026lt;\u0026lt; bit_to_find) # 模拟 shash 过程，但只关注低位# 注意：中间变量 x 在迭代中会积累高位信息，必须在最后比较前 mask# 且在每一步迭代中，(k*x) 的低位只受 x 低位和 k 低位影响 x = (ord(msg[0]) \u0026lt;\u0026lt; 7) \u0026amp; mask_check for char in msg: x = (k_next * x) \u0026amp; mask_check ^ ord(char) x ^= final_xor \u0026amp; mask_check # 检查是否与目标的低位一致if (x \u0026amp; mask_check) == (target_val \u0026amp; mask_check): res = solve_recursive(k_next, bit_to_find + 1, target_val) if res is not None: return res return None real_key = Nonefor h in h_candidates: # 尝试解出 key res = solve_recursive(0, 0, h) if res is not None: # 二次验证全量哈希if shash(msg, res) == h: real_key = res print(f\u0026#34; Found Key: {real_key}\u0026#34;) breakif real_key: magic_word = \u0026#34;I get the key now!\u0026#34; flag_hash = shash(magic_word, real_key) flag = \u0026#34;VIDAR{\u0026#34; + hex(flag_hash)[2:] + \u0026#34;}\u0026#34; print(f\u0026#34;[+] FLAG: {flag}\u0026#34;) else: print(\u0026#34;[-] Failed to find key.\u0026#34;) 解得flag：VIDAR{1069466028b4c4a9694a3175f2f9410ab398b939bdb52afb39534b6f8cc59abc}\nReverse # PVZ # 解压打开exe发现提示需要 java 环境，丢到 DIE 中发现 exe 是 java 套壳\n把 exe 后缀改 zip 尝试解压，解压成功，在 ...\\com\\pvz\\vidar\\game\\wsdx233\\top\\screen里找到 FlagScreen.class，这是 flag 的加密代码，flag 是根据变量 zombieKillCount 的值生成，所以只要根据加密函数写出对应解密代码，然后暴力尝试 zombieKillCount 匹配找到 flag 即可，以下是解密代码：\nfrom typing import List kill_encrypted = [0, -8, -6, 6, 31, -39, -104, 114, 86, -23, -35, 28, -122, 56, 29, -126, -29, 94, 23, -29, 46, -126, -4, 45, 20, -57] # 将有符号字节转换为 0-255 的无符号表示 kill_encrypted = [b \u0026amp; 0xFF for b in kill_encrypted] hello = 4359010814435432432 xor_key1 = 102 xor_key2 = 119 aes_encrypted_key = [74, -111, -61, 127, 46, -75, 104, -44, 28, -119, 58, -14, 93, -90, 113, -66] aes_encrypted_key = [b \u0026amp; 0xFF for b in aes_encrypted_key] # 源代码中给出的替换对（substitution 表），下方构造反向映射 sub_pairs = [(\u0026#39;A\u0026#39;,\u0026#39;Q\u0026#39;),(\u0026#39;B\u0026#39;,\u0026#39;W\u0026#39;),(\u0026#39;C\u0026#39;,\u0026#39;E\u0026#39;),(\u0026#39;D\u0026#39;,\u0026#39;R\u0026#39;),(\u0026#39;E\u0026#39;,\u0026#39;T\u0026#39;),(\u0026#39;F\u0026#39;,\u0026#39;Y\u0026#39;),(\u0026#39;G\u0026#39;,\u0026#39;U\u0026#39;),(\u0026#39;H\u0026#39;,\u0026#39;I\u0026#39;),(\u0026#39;I\u0026#39;,\u0026#39;O\u0026#39;),(\u0026#39;J\u0026#39;,\u0026#39;P\u0026#39;),(\u0026#39;K\u0026#39;,\u0026#39;A\u0026#39;),(\u0026#39;L\u0026#39;,\u0026#39;S\u0026#39;),(\u0026#39;M\u0026#39;,\u0026#39;D\u0026#39;),(\u0026#39;N\u0026#39;,\u0026#39;F\u0026#39;),(\u0026#39;O\u0026#39;,\u0026#39;G\u0026#39;),(\u0026#39;P\u0026#39;,\u0026#39;H\u0026#39;),(\u0026#39;Q\u0026#39;,\u0026#39;J\u0026#39;),(\u0026#39;R\u0026#39;,\u0026#39;K\u0026#39;),(\u0026#39;S\u0026#39;,\u0026#39;L\u0026#39;),(\u0026#39;T\u0026#39;,\u0026#39;Z\u0026#39;),(\u0026#39;U\u0026#39;,\u0026#39;X\u0026#39;),(\u0026#39;V\u0026#39;,\u0026#39;C\u0026#39;),(\u0026#39;W\u0026#39;,\u0026#39;V\u0026#39;),(\u0026#39;X\u0026#39;,\u0026#39;B\u0026#39;),(\u0026#39;Y\u0026#39;,\u0026#39;N\u0026#39;),(\u0026#39;Z\u0026#39;,\u0026#39;M\u0026#39;),(\u0026#39;_\u0026#39;,\u0026#39;!\u0026#39;),(\u0026#39;{\u0026#39;,\u0026#39;[\u0026#39;),(\u0026#39;}\u0026#39;,\u0026#39;]\u0026#39;)] # 反向映射：从替换后的字符映回原字符（value -\u0026gt; key） reverse_sub = {v:k for (k,v) in sub_pairs} def derive_key_from_killcount(var1: int) -\u0026gt; bytes: # 模拟 Java 的 int 行为：var1 应为 32 位整数，但 Python 使用任意精度整型 var3 = (var1 * 31 + 17) % 997 var4 = (var1 * 37 + 23) % 991 var5 = (var1 * 41 + 29) % 983 var6 = (var3 ^ (var4 \u0026lt;\u0026lt; 3) ^ (var5 \u0026gt;\u0026gt; 2)) % 256 var7 = (var1 * 7 + var3 + var4 + var5) % 65536 out = bytearray(16) var9 = var7 for i in range(16): var9 = (var9 * 1103515245 + 12345) \u0026amp; 0x7FFFFFFF out[i] = ((var9 \u0026gt;\u0026gt; 16) % 256) return bytes(out) def decrypt_with_killcount(data: List[int], key_seed: int) -\u0026gt; bytes: key = derive_key_from_killcount(key_seed) out = bytearray(len(data)) for i in range(len(data)): v = data[i] var7 = key[i % len(key)] var8 = (i * 13 + 7) % 256 out[i] = v ^ var7 ^ var8 return bytes(out) def xor_decrypt(data: bytes, k: int) -\u0026gt; bytes: return bytes([(b ^ k) \u0026amp; 0xFF for b in data]) def simple_aes_decrypt(data: bytes, key: List[int]) -\u0026gt; bytes: out = bytearray(len(data)) for i in range(len(data)): out[i] = data[i] ^ key[i % len(key)] return bytes(out) def rotate_decrypt(s: str, rot: int) -\u0026gt; str: res = [] for ch in s: if \u0026#39;A\u0026#39; \u0026lt;= ch \u0026lt;= \u0026#39;Z\u0026#39;: idx = (ord(ch) - 65 - rot) % 26 res.append(chr(65 + idx)) elif \u0026#39;a\u0026#39; \u0026lt;= ch \u0026lt;= \u0026#39;z\u0026#39;: idx = (ord(ch) - 97 - rot) % 26 res.append(chr(97 + idx)) else: res.append(ch) return \u0026#39;\u0026#39;.join(res) def substitution_decrypt(s: str) -\u0026gt; str: out = [] for ch in s: out.append(reverse_sub.get(ch, ch)) return \u0026#39;\u0026#39;.join(out) def get_rotation_offset() -\u0026gt; int: s = \u0026#34;PLANTS_VS_ZOMBIES_2025\u0026#34; tot = sum(ord(c) for c in s) return tot % 26 ROT = get_rotation_offset() def try_decrypt_for(zcount: int) -\u0026gt; str: # 模拟 Java 中 (hello + zcount) 转为 int 的行为（取低 32 位） val = (hello + zcount) \u0026amp; 0xFFFFFFFF # Java 的 int 是有符号的；但 deriveKeyFromKillCount 内部使用模运算，取低 32 位也能复现行为 dec = decrypt_with_killcount(kill_encrypted, val) half = len(dec) // 2 a = dec[:half] b = dec[half:] part1 = xor_decrypt(a, xor_key1) part2 = xor_decrypt(b, xor_key2) merged = part1 + part2 aes = simple_aes_decrypt(merged, aes_encrypted_key) try: s = aes.decode(\u0026#39;utf-8\u0026#39;) except Exception: return None s = rotate_decrypt(s, ROT) s = substitution_decrypt(s) return s if __name__ == \u0026#39;__main__\u0026#39;: # 暴力搜索合理范围，这里尝试 0..200000 for z in range(0, 200001): res = try_decrypt_for(z) if res is None: continue if len(res) == 26 and res.startswith(\u0026#39;flag{\u0026#39;) and res.endswith(\u0026#39;}\u0026#39;): print(\u0026#39;FOUND\u0026#39;, z, res.replace(\u0026#39;flag\u0026#39;,\u0026#39;hgame\u0026#39;)) break else: print(\u0026#39;NOT FOUND\u0026#39;) FOUND 36278 hgame{BECAUSE_I_AM_CRAAAZY} Signal Storm # 用 ida 打开，先找字符串，找到密钥'C0lm_be4ore_7he_st0rm'\n找到主函数，发现函数开头有一段反调试语句，会读取 /proc/self/status 检查 TracerPid 字段，如果 TracerPid 不为 0（即程序正在被调试器跟踪），程序会进入一个错误解密分支，对密钥字符串进行异或操作，所以直接静态分析伪代码，找到加密函数分析发现是 RC4 算法。\n加密过程：\n先用标准 KSA 算法用密钥打乱 S 盒， 然后用 PRGA 生成 KeyStream，不过这个 PRGA 与标准相比有改动，标准 RC4 仅用 S[i] 更新 j，但这道题还加了当前密钥字符：\\(j = (j + S[i] + \\text{Key}[i \\pmod{21}]) \\pmod{256}\\) 最后还做了移位操作，把密钥字符串向左移动 1 位。 写出解密代码：\nimport struct def ksa(key, S): j = 0 key_len = len(key) for i in range(256): j = (j + S[i] + key[i % key_len]) % 256 S[i], S[j] = S[j], S[i] return S def solve(): # 1. 提取密文 targets = [ 0x8260C1C9C8D936E3, 0x1C4BB2D52511D975, 0xF11CAF1C716DE64D, 0x1A5AF67F261CA506 ] ciphertext = b\u0026#39;\u0026#39; for t in targets: ciphertext += struct.pack(\u0026#39;\u0026lt;Q\u0026#39;, t) # 2. 准备密钥 key_str = \u0026#34;C0lm_be4ore_7he_st0rm\u0026#34; key = [ord(c) for c in key_str] # 3. 初始化 S 盒 (仅一次) S = list(range(256)) S = ksa(key, S) # 4. 解密循环 i = 0 j = 0 flag = [] for k in range(32): # --- 变种 PRGA --- i = (i + 1) % 256 # 混入 Key (注意 Key 是动态变化的) current_k = key[i % 21] j = (j + S[i] + current_k) % 256 S[i], S[j] = S[j], S[i] # 生成密钥流并解密 t = (S[i] + S[j]) % 256 k_byte = S[t] flag.append(ciphertext[k] ^ k_byte) # --- 密钥移位 (Rotate Left 1) --- first = key.pop(0) key.append(first) print(\u0026#34;Flag:\u0026#34;, bytes(flag).decode(\u0026#39;utf-8\u0026#39;)) solve() 得到 flag：hgame{Null_c0lm_wi7hout_0_storm}\nWeb # 魔理沙的魔法目录 # F12 发现有个 record 得 POST 请求和 check 得 GET 请求，根据提示应该是要时长到达一定值时 check 会返回 flag，直接在 HackBar 中修改 POST 请求中的数据 {\u0026quot;time\u0026quot;:10}，把 10 改成100000，发送即可\nPOST /record HTTP/1.1 Authorization: eedef51e-792d-4900-8e36-42c7da1adf2f Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,ja;q=0.8,en;q=0.7 Cookie: session=eyJfcGVybWFuZW50Ijp0cnVlLCJ1c2VyIjoiYWRtaW4ifQ.aYLUSQ.6T7bdG_K_aZV2w6SFzBmAMDnzvo Content-Type: application/json {\u0026#34;time\u0026#34;:100000} 回到主页得到flag：hgame{Y0U-aRe_@I50-a_M4Hou_tSuKAI_N0w!12c3c8}\nVidarshop # 根据题目可知需要用 admin 账号改 balance ，所以先想办法搞到 admin 账号权限。\n注册账号会分配 UID，多次尝试发现用数字做用户名，UID 就是用户名本身，而且用户名 a 的 UID 是 1，b 是 2，aa 是 11，ab 是 12，可知 UID 生成规则会重复，且生成是按字典序的，并非随机，所以可以尝试碰撞出 admin 的 UID。\n尝试 admim 的 UID 是 1413913，admio 的 UID 是1413915，所以 admin 的 UID 是 1413914，注册用户名为 1413913 的账号登陆，显示是 admin。\n接下来尝试修改余额，发现前端代码 mineCoin() 函数是直接把变化的 balance POST 到后端且后端会有响应，\n{\u0026quot;is_admin\u0026quot;: true, \u0026quot;msg\u0026quot;: \u0026quot;System Access Granted\u0026quot;,\u0026quot;user_info\u0026quot;: {\u0026quot;balance\u0026quot;: 10,\u0026quot;role\u0026quot;: \u0026quot;user\u0026quot;,\u0026quot;username\u0026quot;: \u0026quot;1413914\u0026quot;}}，但是无论怎么修改 balance 的值发送，都不会在后端修改。\n根据 POST 的响应标头看出服务器是 Python 的 Flask 写的，根据 hint：update接口直接改的好像是User类的balance属性欸，但是User属性中balance似乎并非。。。该怎么修改balance呢，猜测 balance 可能是只读属性 (@property)，而并非直接属性。\n尝试用 Python 类污染，最后发现用 init 链污染全局变量中的 balance 成功造成修改，Payload 如下：\nawait apiRequest(\u0026#39;/api/update\u0026#39;, \u0026#39;POST\u0026#39;, { \u0026#34;__init__\u0026#34;: { \u0026#34;__globals__\u0026#34;: { \u0026#34;balance\u0026#34;:10000000 } } }); 购买得到 flag： hgame{re@14Dm1n_MU5tB3r1cH56cd8b8390}\nWeek2 # MISC # Invest on Matrix # 先花 425pts 解开所有 hint，然后把矩阵还原，发现是 01 矩阵，而且是个二维码，写代码生成二维码：\nfrom PIL import Image data = \u0026#34;\u0026#34;\u0026#34; 1 1 1 1 1 1 1 0 1 1 0 0 1 0 0 0 0 0 1 1 1 1 1 1 1 1 0 0 0 0 0 1 0 1 1 1 1 1 0 1 0 0 0 1 0 0 0 0 0 1 1 0 1 1 1 0 1 0 1 0 0 0 0 1 1 1 1 0 1 0 1 1 1 0 1 1 0 1 1 1 0 1 0 0 0 0 1 1 0 1 1 1 0 1 0 1 1 1 0 1 1 0 1 1 1 0 1 0 0 1 0 1 1 0 0 0 0 0 1 0 1 1 1 0 1 1 0 0 0 0 0 1 0 1 0 0 0 1 1 0 1 0 0 1 0 0 0 0 0 1 1 1 1 1 1 1 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1 0 1 0 0 0 1 0 0 1 1 1 1 1 0 0 1 1 1 1 0 1 0 0 1 0 1 0 1 0 1 0 0 1 1 1 0 1 0 0 1 0 1 1 1 1 0 1 1 1 1 0 1 1 1 1 0 1 0 1 1 1 0 1 1 1 1 1 1 1 1 1 0 1 0 0 1 1 0 0 0 1 0 1 0 0 0 0 1 1 0 0 0 1 0 1 1 0 0 1 1 0 1 0 1 1 1 1 0 1 0 1 0 1 1 0 0 0 1 1 0 0 0 1 0 0 0 0 1 1 1 0 1 1 1 1 0 1 0 0 0 0 1 0 1 0 0 1 1 1 1 1 1 0 1 0 0 1 0 0 0 0 1 0 0 1 0 0 1 1 0 1 0 1 0 0 0 1 0 1 1 1 0 1 0 1 1 1 1 0 0 0 1 0 1 0 1 0 1 0 1 0 1 1 0 0 1 1 1 0 1 1 1 1 1 1 0 1 1 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 1 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 1 1 0 0 1 1 1 0 1 0 1 1 0 1 1 1 0 0 0 0 0 1 0 0 0 0 1 1 1 1 1 1 0 0 0 1 0 0 1 1 1 0 1 1 1 0 1 0 1 1 1 1 0 1 0 1 1 1 1 1 1 0 0 0 0 1 0 1 1 1 0 1 0 1 1 1 0 0 1 1 0 0 0 1 1 0 0 1 0 1 1 0 1 1 1 0 1 0 1 0 1 1 0 0 1 1 0 1 1 1 0 0 1 0 1 1 0 0 0 0 0 1 0 0 1 1 0 1 1 0 1 0 1 0 1 0 1 0 0 1 1 1 1 1 1 1 1 0 0 1 1 0 0 0 1 0 1 0 0 0 0 0 1 1 1 \u0026#34;\u0026#34;\u0026#34; matrix = [line.split() for line in data.strip().split(\u0026#39;\\n\u0026#39;)] size = len(matrix) scale = 10 # 放大10倍 img = Image.new(\u0026#39;L\u0026#39;, (size, size)) for y in range(size): for x in range(size): color = 0 if matrix[y][x] == \u0026#39;1\u0026#39; else 255 img.putpixel((x, y), color) img = img.resize((size * scale, size * scale), resample=Image.NEAREST) img.save(\u0026#34;qrcode.png\u0026#34;) print(\u0026#34;二维码已生成并保存为 qrcode.png\u0026#34;) 扫码得到 W0RTH_1T?，用 hgame{} 包裹得到 flag ：hgame{W0RTH_1T?}\nVidar Token # 先看到网页代码 app.js 末尾禁用了 Run check 按钮：\n接着再看到 checkEligibility 函数实现，发现代码会发 GET 请求获取 /wasm/k.wasm 里的元数据，调用 get_entrance，然后读取一个字符串 ENTRANCE=0x... 来获取合约地址，在函数最后调用了智能合约的 tokenURI(0)，但是没有使用其返回值，猜测信息藏在这里。\n根据 html 里的提示使用 eth toolkit，这里我用 Foundry 的 cast。\n首先先要拿到 Vault : 入口的合约地址，写 js 代码解析 k.wasm 拿到地址，\nfetch(\u0026#34;/wasm/k.wasm\u0026#34;) .then(res =\u0026gt; res.arrayBuffer()) .then(wasm =\u0026gt; WebAssembly.instantiate(wasm, {})) .then(({ instance }) =\u0026gt; { // 获取 WASM 导出的内存对象和入口指针函数 const memory = instance.exports.memory; const ptr = instance.exports.get_entrance(); // 从内存缓冲区 (Buffer) 的该指针位置开始，读取字节数据 const bytes = new Uint8Array(memory.buffer, ptr, 100); let text = \u0026#34;\u0026#34;; for (let i = 0; i \u0026lt; bytes.length; i++) { if (bytes[i] === 0) break; text += String.fromCharCode(bytes[i]); } console.log(`Text: \u0026#34;${text}\u0026#34;`); }) 拿到地址 0x39529fdA4CbB4f8Bfca2858f9BfAeb28B904Adc0，接着使用 cast 工具直接调用合约的 tokenURI(0)：\ncast call 0x39529fdA4CbB4f8Bfca2858f9BfAeb28B904Adc0 \u0026#34;tokenURI(uint256)(string)\u0026#34; 0 --rpc-url ``http://.../rpc 得到一段 text 数据：\ndata:application/json;base64,eyJuYW1lIjoiVmlkYXJQdW5rcyAjMCIsImRlc2NyaXB0aW9uIjoiVmlkYXJQdW5rcyBWYXVsdCBORlQuIFNlZWsgeW91ciBmb3J0dW5lIHdpdGggVmlkYXJDb2luLiIsImF0dHJpYnV0ZXMiOlt7InRyYWl0X3R5cGUiOiJMaW5rZWQgQ29pbiBBZGRyZXNzIiwidmFsdWUiOiIweGM1MjczYWJmYjM2NTUwMDkwMDk1YjFlZGVjMDE5MjE2YWQyMWJlNmMifV0sInZpZGFyX2NvaW4iOiIweGM1MjczYWJmYjM2NTUwMDkwMDk1YjFlZGVjMDE5MjE2YWQyMWJlNmMifQ== Base64 解码得到元数据：\n{ \u0026#34;name\u0026#34;: \u0026#34;VidarPunks #0\u0026#34;, \u0026#34;description\u0026#34;: \u0026#34;VidarPunks Vault NFT. Seek your fortune with VidarCoin.\u0026#34;, \u0026#34;attributes\u0026#34;: [ { \u0026#34;trait_type\u0026#34;: \u0026#34;Linked Coin Address\u0026#34;, \u0026#34;value\u0026#34;: \u0026#34;0xc5273abfb36550090095b1edec019216ad21be6c\u0026#34; } ], \u0026#34;vidar_coin\u0026#34;: \u0026#34;0xc5273abfb36550090095b1edec019216ad21be6c\u0026#34; } 拿到 Coin 的合约地址：0xc5273abfb36550090095b1edec019216ad21be6c\n尝试去读 symbol()：\ncast call 0xc5273abfb36550090095b1edec019216ad21be6c \u0026#34;symbol()(string)\u0026#34; --rpc-url ``http://.../rpc 得到 hex 数据：\n\u0026#34;0x6960606a647c742a404552684d5275346d5e5e6c6f37762a44756258564652702c5d5b5d3333363e64357c\u0026#34; 在刚刚的 GET k.wasm 响应里发现有 baseA 和 baseB\n再用刚刚 js 代码小改提取得到：\nBASEA=0x5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d5b5d BASEB=0x5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a5a 猜测是异或加密，用每位异或 (hex^BASEA^BASEB) 得到一段疑似 flag：hgame{u-ABSoLUt3lY_kn0w-Erc_WASm-ZZZ2479e2}，但是提交是错的。\n根据题面提示 ：On-chain, the truth has nowhere to hide.，尝试提取合约创世区块中的合约创建代码： cast block 1 --rpc-url ``http://.../rpc\n分析 input 里的 hex，这是一段以太坊智能合约创建字节码 (Creation Bytecode / Calldata)，代码末尾有附加在编译器元数据 (CBOR Metadata) 后的 合约部署时传入的初始化参数，提取最后 256 字节 ABI 编码的数据：\n拆解得： 偏移量：0000000000000000000000000000000000000000000000000000000000000020 长度 ：000000000000000000000000000000000000000000000000000000000000002b Data: 6960606a647c742a404552684d5275346d5e5e6c6f37762a447562585646526a2c5d5b5d3333363e64357c000000000000000000000000000000000000000000 Data 再按异或解密得到最终 flag：hgame{u-ABSoLUt3lY_kn0w-Erc_Wasm-ZZZ2479e2}。\nCrypto # ezRSA # 首先要得到模数 N，由于初始状态下 safe = True，服务器的第二项操作（Decrypt message）会返回解密结果。 我们可以利用 RSA 的性质来求 N。 根据 RSA 解密公式 \\(M \\equiv C^d \\pmod N\\)，如果我们输入密文 \\(C = -1\\)， 可以得到：\\((-1)^d \\pmod N \\equiv N -1\\)， 因此，我们将服务器返回的明文转为整数后加 1，即可得到完整的模数 N。 然后要求 e，在已知代码中看到 e 是个 50 bits 的随机数，看第一项操作，输入 M，x，给出:\\(C \\equiv M^{e \\oplus 2^x} \\pmod N\\)，可用其求 e。 先询问 \\(M = 2,x = 50\\) 因为 e 是 50 位，所以异或相当于做加法，移项得到 \\(C \\cdot inv(2^ {2^{50}} )\\equiv 2^e\\pmod N\\)，设 \\(T \\equiv 2^e \\pmod N\\)，接着我们就可以取 \\(x = 0\\sim 49\\)，逐位异或得到的密文与 \\(T\\) 做比对确定 e 的每个 bit 取值，具体地，若 e 第 k 位是 0：用 1 异或该位后，指数增加 \\(2^k \\) ，即得到的密文 C 应该是：\\(T \\cdot 2^{2^k} \\pmod N\\)，否则，第 k 位是 1。 看第三个选项，调用后获取 Flag 的密文，且服务器会将 safe 设为 False，之后所有的解密结果都会调用 disguise 函数，分析发现这段异或加密中明文 msg 的最低位和 mask 异或两次没变，所以密文最低位就是明文最低位。 所以目前已知 N，e，密文 C，明文最低位，这是道 RSA LSB Oracle 攻击题，用二分法还原 flag。 from pwn import * from Crypto.Util.number import long_to_bytes, bytes_to_long import base64 HOST = \u0026#39;1.116.118.188\u0026#39; PORT = 31174 def solve(): r = remote(HOST, PORT) def get_menu(): r.recvuntil(b\u0026#39;Your choice \u0026gt; \u0026#39;) def encrypt(plain, x): r.sendline(b\u0026#39;1\u0026#39;) r.recvuntil(b\u0026#39;plaintext:\\n\u0026#39;) r.sendline(str(plain).encode()) r.recvuntil(b\u0026#39;flip:\\n\u0026#39;) r.sendline(str(x).encode()) line = r.recvline().strip() return bytes_to_long(base64.b64decode(line)) def decrypt(cipher_int): r.sendline(b\u0026#39;2\u0026#39;) r.recvuntil(b\u0026#39;ciphertext:\\n\u0026#39;) r.sendline(str(cipher_int).encode()) line = r.recvline().strip() return base64.b64decode(line) def get_flag_cipher(): r.sendline(b\u0026#39;3\u0026#39;) line = r.recvline().strip() return bytes_to_long(base64.b64decode(line)) # 求 N get_menu() N = bytes_to_long(decrypt(-1)) + 1 print(f\u0026#34;N: {N}\u0026#34;) # 求 e get_menu() # 先求 T = 2^e mod N C = encrypt(2, 50) get_menu() offset = pow(2, pow(2, 50), N) inv = pow(offset, -1, N) T = (C * inv) % N e = 0 for k in range(50): c = encrypt(2, k) get_menu() t = pow(2, pow(2, k), N) if_0 = (T * t) % N if c != if_0: e |= (1 \u0026lt;\u0026lt; k) # 若不相等，说明第 k 位原本是 1 print(f\u0026#34;e: {e}\u0026#34;) # 求密文 C enc = get_flag_cipher() print(f\u0026#34;C: {enc}\u0026#34;) # LSB Oracle 攻击 R = N L = 0 multiplier = pow(2, e, N) now = enc bit_length = N.bit_length() for i in range(bit_length): now = (now * multiplier) % N dec_bytes = decrypt(now) get_menu() # 提取最后一个字节的奇偶性 lsb = dec_bytes[-1] % 2 if lsb == 0: R = (L + R) // 2 else: L = (L + R) // 2 if i % 50 == 0: print(f\u0026#34;[{i}/{bit_length}] Decrypting...\u0026#34;, end=\u0026#39;\\r\u0026#39;) #进度 print(\u0026#34;\u0026#34;) flag = long_to_bytes(R) print(f\u0026#34;Flag: {flag.decode(errors=\u0026#39;ignore\u0026#39;)}\u0026#34;) r.close() if __name__ == \u0026#34;__main__\u0026#34;: solve() N: 68490548161036552898516001601476855132645134861293885612551847785058377647690773859428599213691349521208537285930854708379958533636438593111144603815849409092520667755741396618143247066534385236458855915136333018686031630115422435269006084490728384530902352597646673695839518925684978865412851767713231412347 e: 90640555210077 C: 48824099081227318157276361642617379663372857428440029766276244257492718404301325617665192973210054169328309750044619180181341492161210293994082524653911924571653203901152193855327361075557899124525285665901471407426388007837028689994332967396572567290399612605204666246320229425349986297682616839975030013744 Flag: hgame{EZRS4_is-St1lL-pRETtY-EZ,rlGHT?e37163}SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSR ezDLP # 这是一道矩阵离散对数题。\n已知模数 n，矩阵 A，B，其中 n 是是一个合数，A，B 满足 \\(B \\equiv A^k \\pmod n\\)，flag 用 AES 加密，密钥是 k 的哈希值，所以目标是求 指数 k (1000 bits)。\n首先因为 n 是合数，若要求使用离散对数的各个算法要知道群阶，合数群阶难算，而且合数取模在处理如逆元等情况时会有很多问题，所以需要先分解再做，先放到 factordb 中分解，得到它唯二的两个大素数因子 p，q：\np = 282964522500710252996522860321128988886949295243765606602614844463493284542147924563568163094392590450939540920228998768405900675902689378522299357223754617695943 q = 511405127645157121220046316928395473344738559750412727565053675377154964183416414295066240070803421575018695355362581643466329860038567115911393279779768674224503 那么，原式可分解成两个素数域下的子问题，\n将矩阵 A，B 里的所有元素对 p 取模，等式变为：\\(A_p^k \\equiv B_p \\pmod p\\) 同理，对 q 取模，得到\\(A_q^k \\equiv B_q \\pmod q\\) 这样，我们就可以在素数域求出 k，但是求得的 \\(k_1,k_2\\) 并非是要求的 k ，而是 \\(k_1,k_2\\) 对 \\(p-1,q-1\\)（素数域的群阶）取模的余数，于是我们得到同余方程组： \\(k \\equiv k_1 \\pmod{p-1}\\)，\\(k \\equiv k_2 \\pmod{q-1}\\)，且 \\((p-1) \\times (q-1) \\) 大小（约1074 bits）大于 k 的大小，由此，我们可以用 CRT（中国剩余定理）求解出 k。\n整体思路有了，现在要求解 \\(k_1,k_2\\)，先把矩阵对角化，把矩阵运算变成特征值的标量运算，\n对于 p，先尝试在基域 p 上找 A，B 的特征值，找到如下，然后把阶数 p-1 放进 factordb 里，发现它是光滑数，没有大质因子，直接用 Pohlig-Hellman 算法解出 \\(k_1\\)。\n对于 q，发现在基域上不能对特征方程式做因式分解，找不到特征根，所以特征值在扩域 \\(\\mathbb{F}_{q^2}\\) 上，其群阶为 \\(q^2 -1\\)，对 q-1 和 q+1 分解，发现 q-1 是个光滑数，而 q+1 有一个 162 位数的大质因子，所以直接跑 Pohlig-Hellman 算法会爆内存，因为它会在大质因子的子群里跑 BSGS；但是因为 q-1 是光滑数，若只对 q-1 这部分的小因子求解，也能求出约 538 bits 的信息，然后根据前面 p 求出的约 537 bits 的信息，足够求出1000bits 的 k。 根据 Pohlig-Hellman 的原理，如果你想把一个元素投影到阶数为 q-1 的子群中，你需要把这个元素取指数，指数的值为：\\(\\text{投影指数} = \\frac{\\text{群的总阶数}}{\\text{目标子群的阶数}} =q+1\\)，于是就能求出 q-1 部分的信息。\n解题代码如下：\nfrom sage.all import * from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes from Crypto.Util.Padding import unpad import hashlib from base64 import b64decode p = Integer(282964522500710252996522860321128988886949295243765606602614844463493284542147924563568163094392590450939540920228998768405900675902689378522299357223754617695943) q = Integer(511405127645157121220046316928395473344738559750412727565053675377154964183416414295066240070803421575018695355362581643466329860038567115911393279779768674224503) # q-1 中难以直接 factor() 的巨大合数余数，以及我们提前算好的素因子表 rem_q_minus_1 = Integer(255702563822578560610023158464197736672369279875206363782526837688577482091708207147533120035401710787509347677681290821733164930019283557955696639889884337112251) factors_rem = [ 2870841851, 3474870851, 4291204249, 2579730631, 3729159451, 3963309473, 3847848661, 2711989219, 3438707611, 4153951723, 2708075807, 2909724073, 2244072599, 2232963553, 2294802047, 2711612389, 4277578859 ] def solve_dlp_smart(A, B, prime_mod): F = GF(prime_mod) Ma = A.change_ring(F) Mb = B.change_ring(F) # 计算特征多项式，判断是否需要扩域 char_poly = Ma.characteristic_polynomial() # 构建分裂域。如果是基域，K 就是 F；如果是扩域，K 比如是 GF(q^2) K = char_poly.splitting_field(\u0026#39;g\u0026#39;) deg = K.degree() print(f\u0026#34;在 GF(p^{deg}) 上处理\u0026#34;) # 提升到域 K 计算特征值 lambda_a = Ma.change_ring(K).eigenvalues()[0] candidates_b = Mb.change_ring(K).eigenvalues() # 投影指数 exponent = (q^k - 1) / (q - 1)。 exponent = (prime_mod**deg - 1) // (prime_mod - 1) alpha = F(lambda_a ** exponent) # 准备群阶 (必定是 prime_mod - 1) dlp_order = prime_mod - 1 factors_dict = {} temp_val = dlp_order # 混合使用硬编码的因子和 Sage 自动分解，防止大数分解卡死 if temp_val % rem_q_minus_1 == 0: for f in factors_rem: factors_dict[f] = 1 temp_val //= rem_q_minus_1 for f, e in factor(temp_val): factors_dict[f] = factors_dict.get(f, 0) + e # 执行 Pohlig-Hellman 求解离散对数 for lambda_b in candidates_b: beta = F(lambda_b ** exponent) try: # 显式传入 order 避免它内部再算一遍 k = discrete_log(beta, alpha, ord=dlp_order) return k, dlp_order except ValueError: # 说明当前的 lambda_b 不是 lambda_a 对应的那个特征值 continue return None, None def main(): data = load(\u0026#39;data.sobj\u0026#39;) _, A, B = data # 分解计算 P 部分 kp, mp = solve_dlp_smart(A, B, p) if kp is not None: print(f\u0026#34;k_p = {kp} (mod {mp})\u0026#34;) # 分解计算 Q 部分 kq, mq = solve_dlp_smart(A, B, q) if kq is not None: print(f\u0026#34;k_q = {kq} (mod {mq})\u0026#34;) # 使用中国剩余定理 (CRT) 合并最终的 k real_k = crt([kp, kq], [mp, mq]) print(f\u0026#34;k = {real_k}\u0026#34;) print(f\u0026#34;密钥长度: {real_k.nbits()} bits\u0026#34;) # AES 解密获取 Flag ct = b64decode(\u0026#34;ieJNk5335o9lCy6Ar2XymrDy+HVHcQhikluNSra0kBafw1WDCyyuNPkLACeBsavy\u0026#34;) key = hashlib.md5(long_to_bytes(int(real_k))).digest() pt = AES.new(key, AES.MODE_ECB).decrypt(ct) # 去除 PKCS7 填充 pad_len = pt[-1] flag = pt[:-pad_len].decode() print(f\u0026#34;FLAG: {flag}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() 在 GF(p^1) 上处理 k_p = 194150014357322102280977981338911107437254410679354395257750085987337403209324692720314282643889898235086070845264350104700871344665853762747251645817295210793819 (mod 282964522500710252996522860321128988886949295243765606602614844463493284542147924563568163094392590450939540920228998768405900675902689378522299357223754617695942) 在 GF(p^2) 上处理 k_q = 97793690266943406431658643072078654035411321038962116230462569216011685651545408039832085776884476346748323733709768043043618444157526539743259646621167459948583 (mod 511405127645157121220046316928395473344738559750412727565053675377154964183416414295066240070803421575018695355362581643466329860038567115911393279779768674224502) k = 5463754687272471737506776408037533940627804591798278445130135065605330977833426586716416744739219464309901200430253822046020844041200415664946278359861866133489677434120672308142791902796785290614151341853251007655379400487129815328838115937720032928180197970866510291616307119349784305483808117548561 密钥长度: 1000 bits FLAG: hgame{1s_m@trix_d1p_rEal1y_sImpLe??} Decision # 这是一个 Decision-LWE 问题。\nLWE 问题基本方程为 \\(b = A \\cdot s + e \\pmod q\\)。题目给了参数：维度 \\(n=25\\)，单块样本数 \\(m=15\\)，模数 \\(q \\approx 2^{128}\\)，误差分布 \\(D \\approx 2^{16}\\)。\n加密方式：\n如果比特是 1：输出 LWE 样本 \\((A, b)\\)。 如果比特是 0：输出完全随机的 \\((A, b)\\)。 看代码可以发现：\n题目中 lwe 变量是在循环外初始化的。这说明所有比特为 1 的块，其背后的私钥向量 \\(s\\) 是完全相同的。 单个块提供 m=15 个方程，而未知数有 n=25 个，方程数少于未知数，所以对于单个块，无论它是 LWE 样本还是随机样本，都存在解，因此很难直接区分。 但是如果我们找到两个或三个均为 1 的块，将它们拼在一起，总样本数就大于 25 了，就能确定样本了 。\n我们通过构造一个区分器进行处理。\n假设我们将 \\(k\\) 个块组合，得到矩阵 \\(A_{comb}\\) (\\(km \\times n\\)) 和向量 \\(b_{comb}\\) (\\(km \\times 1\\))。\n寻找正交向量：我们希望找到一个短向量 \\(u \\in \\mathbb{Z}^{km}\\)，使得：\\(u \\cdot A_{comb} \\equiv 0 \\pmod q\\)，这表示 u 在模 q 下与矩阵 A 的列空间正交。 检验：计算标量 \\(test = u \\cdot b_{comb} \\pmod q\\)。 若是 LWE 样本：代入 \\(b = As + e\\)：\\(test = u \\cdot (As + e) = (uA)s + ue \\equiv 0 \\cdot s + ue \\equiv ue \\pmod q\\) 由于 u 是短向量（通过 LLL 找到），e 是小误差（题目设定 \\(2^{16}\\)），所以 \\(u\\cdot e\\) 的值相对于巨大的 q (\\(2^{128}\\)) 来说非常小。 若含有随机样本：b 是均匀随机的，与 A 无关。因此 \\(u \\cdot b\\) 的结果会在 \\([0, q)\\) 之间均匀分布，大概率是一个很大的数。 为了找到这个短向量 u，我们需要构造一个格并运行 LLL 算法。我们构造矩阵 \\(L\\) 使得其生成的格包含满足 \\(uA \\equiv 0 \\pmod q\\) 的向量。\n构造如下的矩阵（以行向量为基）：\\(B = \\begin{pmatrix} I_M \u0026 K \\cdot A_{comb} \\\\ 0 \u0026 K \\cdot q \\cdot I_n \\end{pmatrix}\\)\n左边是单位矩阵 \\(I_M\\)，用于记录向量 u。 右边是约束条件。乘以大常数 K 是为了强迫 LLL 算法优先优化右边的部分变为 0。 如果在格中找到短向量 \\((u, \\epsilon)\\)，当 \\(\\epsilon = 0\\) 时，意味着 \\(u \\cdot (K \\cdot A_{comb}) + \\text{integer} \\cdot (K \\cdot q) = 0\\)，即 \\(u \\cdot A_{comb} \\equiv 0 \\pmod q\\)。 总结解题过程：\n先寻找基准块，因为我们不知道哪些是 1，我们随机选取 3 个块。假设它们都是 1，运行上述的区分器\n如果计算出的 \\(u \\cdot b\\) 很小，说明假设成立，我们找到了 3 个确定的 1 块（基准块） 如果很大，说明选取的 3 个块中混入了 0，重新随机选取。 拿到基准块（设 block A，B）后，对于剩余的每一个未知 Block X：将 (Block A, Block B, Block X) 组合调用区分器：\n如果结果小说明 \\(X=1\\)。 如果结果大说明 \\(X=0\\)。 最后把得到的 01 串转 ASCLL 得到 flag\nfrom sage.all import * import ast import random def solve(): with open(\u0026#34;output.txt\u0026#34;, \u0026#34;r\u0026#34;) as f: data = ast.literal_eval(f.read()) n = 25 m = 15 q = 256708627612544299823733222331047933697 # 预处理：将数据转换为 Matrix 对象，加速后续计算 # blocks[i] = (Matrix A, Vector b) blocks = [] for blk in data: A_rows = [] b_rows = [] for sample in blk: A_rows.append(sample[:-1]) b_rows.append(sample[-1]) blocks.append((Matrix(GF(q), A_rows), vector(GF(q), b_rows))) num_blocks = len(blocks) print(f\u0026#34;总块数: {num_blocks}\u0026#34;) def is_lwe_group(indices): # 组合矩阵 A 和向量 b sub_As = [blocks[i][0] for i in indices] sub_bs = [blocks[i][1] for i in indices] A_comb = matrix(GF(q), sum([list(m) for m in sub_As], [])) b_comb = vector(GF(q), sum([list(v) for v in sub_bs], [])) M_total = A_comb.nrows() # 总样本数，例如 3*15 = 45 # 构造格基寻找对偶向量 u # [ I_M K * A ] # [ 0 K * q * I_n ] K = 2**100 # 权重因子，强迫右侧为 0 # 转为整数环进行格基构造 A_int = A_comb.change_ring(ZZ) # 手动构造大矩阵 # 维度: (M + n) x (M + n) # 构造行向量基 lattice_rows = [] # 上半部分 [ I_M | K*A ] for r in range(M_total): row = [0] * (M_total + n) row[r] = 1 for c in range(n): row[M_total + c] = K * int(A_int[r][c]) lattice_rows.append(row) # 下半部分 [ 0 | K*q*I_n ] for r in range(n): row = [0] * (M_total + n) row[M_total + r] = K * q lattice_rows.append(row) L = Matrix(ZZ, lattice_rows) # LLL 算法 L_reduced = L.LLL() # 取最短向量（通常是第一行） shortest_v = L_reduced[0] u = shortest_v[:M_total] # 验证区分条件 # 计算 inner_product = u * b u_vec = vector(GF(q), u) val = u_vec.dot_product(b_comb) # 将结果中心化到 [-q/2, q/2] 并取绝对值 val_int = int(val) if val_int \u0026gt; q // 2: val_int = q - val_int # 判定 if val_int \u0026lt; (q \u0026gt;\u0026gt; 10): return True return False # 寻找基准块 (全是 1 的三个块) reference_group = None # 随机尝试，概率很高 (1/8) for _ in range(100): idxs = random.sample(range(num_blocks), 3) if is_lwe_group(idxs): reference_group = idxs print(f\u0026#34;找到基准块: {reference_group}\u0026#34;) break # 使用基准块中的前两个作为“锚点” anchors = reference_group[:2] # 恢复所有比特 bits = \u0026#34;\u0026#34; for i in range(num_blocks): # 如果是已知的基准块，直接填 1 if i in reference_group: bits += \u0026#34;1\u0026#34; continue # 测试当前块 i # 将锚点和 i 组合，构成 3 个块的组 test_group = anchors + [i] if is_lwe_group(test_group): bits += \u0026#34;1\u0026#34; else: bits += \u0026#34;0\u0026#34; if i % 20 == 0: sys.stdout.write(f\u0026#34;\\r进度: {i}/{num_blocks}\u0026#34;) sys.stdout.flush() print(f\u0026#34;\\n恢复完成: {bits}\u0026#34;) # 解码 flag_int = int(bits, 2) flag_bytes = flag_int.to_bytes((len(bits) + 7) // 8, \u0026#39;little\u0026#39;) # 题目代码中是 little endian # 题目中 flagbin 是 split 出来的部分，所以我们要还原格式 content = flag_bytes.decode(errors=\u0026#39;ignore\u0026#39;) print(f\u0026#34;Flag: hgame{{{content}}}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: solve() Flag: hgame{w1sh_you_4_h@ppy_new_y3ar} eezzDLP # 看代码得到已知模数 n 是由一个素数 p 生成的 \\(n = p^2\\)，然后在模 n 的环上生成了一个随机矩阵 A，并计算了 \\(B \\equiv A^k \\pmod n\\)。\n目标是求一个 660 位的素数 k，从而得到 AES 的密钥来解密密文。\n因为 n 约 1224 位，可知 p 约 612 位。而 k 是 660 位，这意味着 k 大于 p。\n我们可以将未知数 k 表示为：\\(k = k_0 + m \\cdot p\\)，其中 \\(k_0 = k \\pmod p\\)，而 m 是商（大约 \\(660 - 612 = 48\\) 位）。求解 k 就变成了先求 \\(k_0\\)，再求 m 。\n先利用 Paillier 算法恢复低位 \\(k_0\\)。 在矩阵群 \\(GL_2(\\mathbb{Z}_{p^2})\\) 中，有一个到 \\(GL_2(\\mathbb{Z}_p)\\) 的自然同态映射。\n那些在模 \\(p\\) 意义下等于单位阵 \\(I\\) 的矩阵，构成了形如 \\(I + p \\cdot M\\) 的核子群。\n投影到核子群： 我们需要找到一个指数 L，使得矩阵 \\(A^L \\equiv I \\pmod p\\)。在 \\(GL_2(\\mathbb{Z}_p)\\) 中，大部分随机矩阵的阶是整除 \\(p^2-1\\) 的。因此我们取 \\(L = p^2 - 1\\)。 令 \\(A' \\equiv A^L \\pmod{p^2}\\)，由于 \\(A'\\) 在模 p 下是单位阵，它可以写成：\\(A' = I + p \\cdot \\Delta_A\\) 同理，令 \\(B' \\equiv B^L \\pmod{p^2}\\)，它也可以写成：\\(B' = I + p \\cdot \\Delta_B\\) 利用二项式定理化简为线性方程： 根据题目 \\(B = A^k\\)，必然有 \\(B' = (A')^k \\pmod{p^2}\\)。 我们将 \\(A'\\) 代入并展开：\\((I + p \\cdot \\Delta_A)^k = I + k \\cdot p \\cdot \\Delta_A + \\binom{k}{2}p^2 \\cdot \\Delta_A^2 + \\dots\\) 因为我们是在模 \\(p^2\\) 下运算，所有包含 \\(p^2\\) 及更高次幂的项全部变为 0！所以式子极度简化为：\\(B' \\equiv I + k \\cdot p \\cdot \\Delta_A \\pmod{p^2}\\) 将 \\(B' = I + p \\cdot \\Delta_B\\) 代入，两边减去 I 并除以 p，我们得到模 p 意义下的线性方程：\\(\\Delta_B \\equiv k \\cdot \\Delta_A \\pmod p\\) 因为这里是在模 p 下运算，所以求出来的也就是 \\(k \\pmod p\\)，即我们定义的 \\(k_0\\)。只需要做一个简单的矩阵元素除法即可得到 \\(k_0\\)。 利用 BSGS 恢复高位 m 现在我们已知 \\(k = k_0 + m \\cdot p\\)，且 \\(m\\) 大约只有 48 位。 回到原始的 DLP 方程，我们将其直接放入 模 p (\\(\\mathbb{F}_p\\)) 的域中运算（既然模 \\(p^2\\) 成立，模 p 必定成立，且模 p 计算会很快）：\\(B \\equiv A^{k_0 + m \\cdot p} \\pmod p\\)， 两边同乘 \\(A^{-k_0}\\) 得到：\\(B \\cdot A^{-k0} \\equiv (A^p)^m \\pmod p\\) 我们设目标值: \\(T = B \\cdot A^{-k_0} \\pmod p\\)，底数: \\(G = A^p \\pmod p\\)，把问题转化为了标准的离散对数问题：求解 \\(G^m \\equiv T \\pmod p\\)。 因为 \\(m \u003c 2^{50}\\)，我们可以使用 BSGS 算法求解： 令步长 \\(step = 2^{24}\\)。 Baby Steps：计算并存储 \\(G^i\\) 及其对应的索引 i。 注意：这里如果直接把 1600 万个矩阵存入字典，会爆内存。所以我们只提取矩阵左上角元素 \\(M[0,0]\\) 的低 64 位作为特征 Hash，用int 存储，将内存控制在几百 MB 以内。 Giant Steps：令 \\(Stride = G^{-step}\\)，我们依次计算 \\(T \\cdot Stride^j\\)，如果其特征 Hash 出现在 Baby Steps 表中，说明找到了 \\(m = j \\times step + i\\)，验证通过即可。 最后拿到完整的 \\(k\\) 后，按照题目逻辑生成 MD5 密钥，进行 AES 解密即可。\nfrom sage.all import * from Crypto.Cipher import AES from Crypto.Util.number import long_to_bytes import hashlib from base64 import b64decode import time def solve(): data = load(\u0026#34;data.sobj\u0026#34;) n, A, B = data # 恢复素数 p p = Integer(n).isqrt() # 利用 Paillier 同构求解 k mod p (k0) L = p*p - 1 # 使得 A^L 映射到 I mod p 的指数 # 在模 n(即 p^2) 下进行矩阵快速幂 A_lift = A ** L B_lift = B ** L # 验证 A^L mod p 是否为单位阵 Fp = GF(p) if not A_lift.change_ring(Fp).is_one(): print(\u0026#34;[-] Standard lift L=p^2-1 failed. Trying L=p-1...\u0026#34;) L = p - 1 A_lift = A ** L B_lift = B ** L # 计算 Delta = (Lift - I) / p Ident = matrix.identity(ZZ, 2) A_lift_Z = A_lift.change_ring(ZZ) B_lift_Z = B_lift.change_ring(ZZ) Delta_A = (A_lift_Z - Ident).apply_map(lambda x: Integer(int(x) // int(p))).change_ring(Fp) Delta_B = (B_lift_Z - Ident).apply_map(lambda x: Integer(int(x) // int(p))).change_ring(Fp) # 求解线性方程 Delta_B = k0 * Delta_A (mod p) k0 = None for r in range(2): for c in range(2): val_a = Delta_A[r, c] val_b = Delta_B[r, c] if val_a != 0: k0 = Integer(val_b * val_a.inverse()) break if k0 is not None: break print(f\u0026#34;Recovered k0: {k0}\u0026#34;) # 内存优化的 BSGS 求解高位 m # 全部转入 GF(p) 运算 A_Fp = A.change_ring(Fp) B_Fp = B.change_ring(Fp) # 目标 T = B * A^(-k0) (mod p) A_inv_k0 = A_Fp ** (-int(k0)) Target = B_Fp * A_inv_k0 # 底数 G = A^p (mod p) Base = A_Fp ** int(p) # BSGS 参数设定 (m 约 48 位) m_bits = 660 - p.nbits() + 2 m_step = 1 \u0026lt;\u0026lt; 24 # 1677万步长 print(f\u0026#34;Search Range: 2^{m_bits}\u0026#34;) print(f\u0026#34;Baby Step Size: {m_step:,}\u0026#34;) lookup = {} curr = matrix.identity(Fp, 2) t0 = time.time() print(\u0026#34;Generating BS...\u0026#34;) for i in range(m_step): if i % 4000000 == 0 and i \u0026gt; 0: print(f\u0026#34;{i}/{m_step}\u0026#34;) # 内存优化：只提取左上角元素并截断作为哈希存入原生字典 key = int(curr[0,0]) \u0026amp; 0xFFFFFFFFFFFFFFFF lookup[key] = i curr = curr * Base print(\u0026#34;Generating GS...\u0026#34;) Giant_Stride = Base ** (-m_step) curr_giant = Target limit = (1 \u0026lt;\u0026lt; m_bits) // m_step + 500 m_found = None t0 = time.time() for j in range(limit): if j % 5000000 == 0 and j \u0026gt; 0: print(f\u0026#34;Step {j}/{limit}\u0026#34;) # 提取当前 Giant Step 的哈希 key = int(curr_giant[0,0]) \u0026amp; 0xFFFFFFFFFFFFFFFF if key in lookup: i = lookup[key] candidate_m = j * m_step + i # 找到可能的碰撞，进行完整矩阵验证防假阳性 if (Base ** candidate_m) == Target: m_found = candidate_m print(f\u0026#34;MATCH! m = {m_found}\u0026#34;) break curr_giant = curr_giant * Giant_Stride # 拼合 k 并解密 k = k0 + m_found * p print(f\u0026#34;Recovered full k: {k}\u0026#34;) print(\u0026#34;Decrypting Flag...\u0026#34;) key_md5 = hashlib.md5(long_to_bytes(int(k))).digest() ct_b64 = \u0026#34;Q3UBa1pz1fi35L94peaFbPvpQe4UyXOUif3CKS/CmZdXOiV7bA5NNNjJ1KeUiAFE\u0026#34; ciphertext = b64decode(ct_b64) cipher = AES.new(key_md5, AES.MODE_ECB) pt = cipher.decrypt(ciphertext) pad = pt[-1] if pad \u0026lt; 16: print(f\u0026#34;\\n[+] FLAG: {pt[:-pad].decode()}\u0026#34;) else: print(f\u0026#34;\\n[+] FLAG (raw): {pt}\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: solve() Recovered full k: 4238873411283850941524834332937913444291533048380278889933287099990199178752115950062698973120574658223722822108986551677048478954034338616186015239894923832089467914215948935216404122157104593061117 FLAG: hgame{M@trix-d1p_iz_rea1ly_1z!1!111!} ","date":"16 February 2026","externalUrl":null,"permalink":"/ctf/hgame2026/","section":"WP和杂文","summary":"","title":"Hgame2026 WP","type":"ctf"},{"content":" （\\( \\color{green}{\\sqrt{}} \\)）已完成。（\\( 73 \\) 个）\n（\\( \\color{red}{\\times{}} \\)）未完成。（\\( 24 \\) 个）\n（\\( \\color{grey} {?} \\)） 不确定。（\\( 3 \\) 个）\n1.给自己起个ID（\\( \\color{green}{\\sqrt{}} \\)）\nRencj 或者 RC·阿柒 （快进到被开盒子） 2.爆零一场模拟赛（\\( \\color{green}{\\sqrt{}} \\)）\n不止一场了。。。 3.AK一场模拟赛（\\( \\color{red}{\\times{}} \\)）\n太菜了，没机会 4.记下第一次提交 hello world 的日期（\\( \\color{green}{\\sqrt{}} \\)）\n2020-05-20 5.向大佬请教问题（\\( \\color{green}{\\sqrt{}} \\)）\n太多了，但是本人太菜，经常听不懂。 6.对自己的板子越看越满意（\\( \\color{green}{\\sqrt{}} \\)）\n虽然写的不多，但是简洁、美观为主，很满意 7.给学弟学妹讲个课（\\( \\color{grey} {?} \\)）\n不确定，可能退役后会给下一届讲。 8.在学习上照顾自己的学弟/学妹（\\( \\color{grey} {?} \\)）\n不确定，但是我被他们吊打。 9.给学弟学妹传承机房精神（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 10.参与出一场自己的模拟赛（\\( \\color{green}{\\sqrt{}} \\)）\n出过一场，不过还没打，估计留给下一届了。 11.体验比赛结束前一分钟极限过题（\\( \\color{red}{\\times{}} \\)）\n没有，唯一一场 T1 最后一分钟调出正解，但是数组没滚，MLE 了。 12.和同学组队打一场 ACM（\\( \\color{green}{\\sqrt{}} \\)）\nTHUPC2022，和 Awlgot、蒟酱，还结识了单开同学 Kobe303，现在我们是好朋友。 13.在 OierDB 上留下名字（\\( \\color{green}{\\sqrt{}} \\)）\n在第一条里应该能开出来。 14.跟回来讲课的学长谈笑风生（\\( \\color{red}{\\times{}} \\)）\n因为不太认识，而且我比较社恐。 15.在考前祝福大家 RP++（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 16.出去集训认识外校同学（\\( \\color{red}{\\times{}} \\)）\n没出去过。 17.为了忘了是什么的执念去卷排行榜/过题数（\\( \\color{green}{\\sqrt{}} \\)）\n有吧，但是主要是卡最优解。 18.学某个算法的时候发现好像自己在考场上也发明出来过（\\( \\color{red}{\\times{}} \\)）\n我思维能力很差，没有什么想法。 19.说出最喜欢的 OJ 三大优点（\\( \\color{green}{\\sqrt{}} \\)）\n界面优美柔和，功能全面，有翻译。 20.在考挂之后跟学长或队友谈心（\\( \\color{green}{\\sqrt{}} \\)）\n肯定的。 21.调出来调一天的代码（\\( \\color{green}{\\sqrt{}} \\)）\n太多了，一些 DS、大模拟，比如猪国杀调了我3天。 22.hack 别人或被 hack 或求被 hack（\\( \\color{green}{\\sqrt{}} \\)）\n有过，打 CF 的很常见吧。 23.暴踩标程（\\( \\color{red}{\\times{}} \\)）\n没有过，顶多卡常抢最优。 24.很稳的不挂分（\\( \\color{green}{\\sqrt{}} \\)）\nCSP2022 暴力分没挂，但是没想出一道正解。。。 25.被机惨（\\( \\color{green}{\\sqrt{}} \\)）\n偶尔，毕竟锁机的习惯还是有的。 26.学习一些基本的机惨方法（\\( \\color{green}{\\sqrt{}} \\)）\n有，比如再开始界面把别人密码扬了。 27.机惨别人（\\( \\color{green}{\\sqrt{}} \\)）\n有，毕竟机房里总有不锁机的。 28.打一场阴间时间的 CF（\\( \\color{green}{\\sqrt{}} \\)）\nCF 不是场场阴间场。 29.精疲力竭/轻轻松松阅读一些英语文献和日语题解（\\( \\color{green}{\\sqrt{}} \\)）\n英文短的话，还是会看看的，但是有 deepl 为什么不用呢。 30.了解自己的竞赛教练最喜欢颓什么（\\( \\color{grey} {?} \\)）\n野花 / sigma 不知道，土豆成分复杂。 31.记住一些永生难忘的数字（\\( \\color{green}{\\sqrt{}} \\)）\n114514、998244353、2147483647、19260817 32.流畅的敲一个自己喜欢的板子（\\( \\color{green}{\\sqrt{}} \\)）\n我板子挺短的，简简单单。 33.自己测评哪种写法最优秀（\\( \\color{green}{\\sqrt{}} \\)）\n正常，毕竟为了简洁。 34.遇到一个让你卡常卡到升华的“绝世好题”（\\( \\color{green}{\\sqrt{}} \\)）\n挺多的吧，但主要为了最优解。 35.挑选一个自己喜欢的模数（\\( \\color{green}{\\sqrt{}} \\)）\n998244353 或 1e9+7 36.拥有一套满意的变量名分配方案（\\( \\color{green}{\\sqrt{}} \\)）\n想到啥用啥。 37.在代码里夹带私货（\\( \\color{green}{\\sqrt{}} \\)）\n偶尔写写。 38.想出比题解优秀的做法并过题后大骂—（\\( \\color{red}{\\times{}} \\)）\n大骂倒是不会，而且也没想出过。 39.暴搜的奇迹总会降临一次是不是（\\( \\color{green}{\\sqrt{}} \\)）\n有的，比如之前一场模拟赛，T1 想了半天，正解是暴搜。 40.开一个自己的博客（\\( \\color{green}{\\sqrt{}} \\)）\nMy blog 41.在经常压码长的内卷中逐渐开始压行（\\( \\color{green}{\\sqrt{}} \\)）\n为了美观，压行在所难免。 42.打一场愚人节比赛（\\( \\color{red}{\\times{}} \\)）\n可惜，没报名。 43.写退役记是好文明（\\( \\color{green}{\\sqrt{}} \\)）\n雀食。 44.写博客题解是好文明（\\( \\color{green}{\\sqrt{}} \\)）\n雀食 45.记得一些神奇的机房语录（\\( \\color{green}{\\sqrt{}} \\)）\ndd_d：我鼠呢？ Mine_King：没有人能锕掉我！ 46.停课搞 OI（\\( \\color{green}{\\sqrt{}} \\)）\n正常 47.交到一些 Oier 朋友（\\( \\color{green}{\\sqrt{}} \\)）\n正常 48.听过一些知名 OI 歌曲如膜你抄（\\( \\color{green}{\\sqrt{}} \\)）\n偶尔听听 49.拿到一道题最优解（\\( \\color{green}{\\sqrt{}} \\)）\n太常见了 50.因为想停课跟老师长谈（\\( \\color{red}{\\times{}} \\)）\n没有。 51.曾经以 Oier 的身份介绍自己（\\( \\color{red}{\\times{}} \\)）\n没有 52.吃柿子补式子就能推式子（\\( \\color{red}{\\times{}} \\)）\n我就没推出过什么柿子。 53.在课件/题目里面夹带私货（\\( \\color{green}{\\sqrt{}} \\)）\n模拟赛里放过一些 game。 54.参与过大括号换不换行的经典讨论（\\( \\color{green}{\\sqrt{}} \\)）\n但是我选择换行。 55.向别人称赞自己最喜欢的数据结构（\\( \\color{green}{\\sqrt{}} \\)）\n分块 56.给同学调代码（\\( \\color{green}{\\sqrt{}} \\)）\n同学互帮互助，正常。 57.和队友在考前打奇怪的赌（\\( \\color{green}{\\sqrt{}} \\)）\n立 失败 flag。 58.考前给大家发放好吃的（\\( \\color{green}{\\sqrt{}} \\)）\n分点零食一起吃。 59.强（qi）迫（qiu）同学给自己调代码（\\( \\color{green}{\\sqrt{}} \\)）\n正常 60.意外的学到一些英语比如说我们来拼一下算法这个单词（\\( \\color{green}{\\sqrt{}} \\)）\nalgorithm，多打打头文件就好了。 61.沾染上一些对英语字母的奇怪读音（\\( \\color{green}{\\sqrt{}} \\)）\nx 读成 叉，比如 xor。 62.现在电脑的某些角落还残存一些数据样例对拍（\\( \\color{green}{\\sqrt{}} \\)）\n正常。 63.与Linux小企鹅对视超过三分钟（\\( \\color{red}{\\times{}} \\)）\n只用过 Ubuntu。 64.点进去一个洛谷帖子并发现新世界（\\( \\color{green}{\\sqrt{}} \\)）\n有过。 65.学会使用一些新的聊天语法如奇怪的括号或者表情（\\( \\color{green}{\\sqrt{}} \\)）\n雀食。 66.在接触OI后认识了一些二次元人物（\\( \\color{green}{\\sqrt{}} \\)）\n太多了。 67.玩过博主用来放算法标签的球球（\\( \\color{red}{\\times{}} \\)）\n？？？ 68.疯狂点击屏幕投喂一些别人博客的仓鼠（\\( \\color{red}{\\times{}} \\)）\n没见过 69.进行一些膜与被膜（\\( \\color{green}{\\sqrt{}} \\)）\n常见 70.你应该在一秒内认出txdy（\\( \\color{red}{\\times{}} \\)）\n完了，不知道 71.加入一个 OI QQ群（\\( \\color{green}{\\sqrt{}} \\)）\n太多了 72.给队友起一些接下来会陪伴你们直到退役的可爱昵称（\\( \\color{green}{\\sqrt{}} \\)）\n有 73.透过 OI 群妹子 90% 的表象看到本质（\\( \\color{red}{\\times{}} \\)）\n？？？ 74.听说一些江湖上的传说比如 tourist（\\( \\color{green}{\\sqrt{}} \\)）\n都知道了。 75.听说每个人都会认识一个OJ密码是复制登录页面的一些字符的小伙伴（\\( \\color{red}{\\times{}} \\)）\n？？？ 76.阅读整型溢出（\\( \\color{green}{\\sqrt{}} \\)）\n有的。 77.在没有人的校园慢慢的走（\\( \\color{red}{\\times{}} \\)）\n还没有过。 78.接触一些学校的保安叔叔（\\( \\color{green}{\\sqrt{}} \\)）\n有的。 79.和队友在机房订外卖rand一个人去取（\\( \\color{red}{\\times{}} \\)）\n没点过。 80.拥有一本从来没看完过的竞赛书（\\( \\color{green}{\\sqrt{}} \\)）\n蓝书。 81.掌握一些基本的办公室电脑技术（如切屏）（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 82.掌握一些基本的心理知识并实践（如面不改色的切屏）（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 83.立一个绝对不颓的 flag（\\( \\color{green}{\\sqrt{}} \\)）\n有过，但是第二天就打破了。。。 84.与某个学长道别后突然开始想象自己要退役了（\\( \\color{red}{\\times{}} \\)）\n没道别过，因为没有认识的。 85.或许在完全退役之前经历了一些以为自己差点退役的时刻（\\( \\color{red}{\\times{}} \\)）\n心态还没那么坏。 86.在去外地集训或比赛时晚上去这个城市走走（\\( \\color{red}{\\times{}} \\)）\n没有过，毕竟疫情。 87.每一次出去集训多照一些照片（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 88.和一些网友 Oier 面基（\\( \\color{red}{\\times{}} \\)）\n但是疫情，而且没什么网友。 89.拥有和队友的合影（\\( \\color{green}{\\sqrt{}} \\)）\n有的。 90.出去集训记得带插排，有头发的妹子记得带吹风机（\\( \\color{red}{\\times{}} \\)）\n没出去过。 91.尝试记住退役前教练说的最后一句话（\\( \\color{green}{\\sqrt{}} \\)）\n太多了，比如 目标又不明确了？ 92.从容的面对退役前最后一场正式赛（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 93.退役前记得回望自己的 OI 之旅（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 94.退役前记得和想道别的人认真道别（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 95.退役前记得祝福大家前程似锦江湖再见（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 96.退役前记得在最后一份代码上写一点退役感言（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 97.退役前记得整理好在这个 cruel world 留下的一切（\\( \\color{green}{\\sqrt{}} \\)）\n必须的。 98.你要记住这一切的一切啊（\\( \\color{green}{\\sqrt{}} \\)）\n知道了。 99.好啦最后一个，退役了不要哭啦（\\( \\color{green}{\\sqrt{}} \\)）\n不会哭的，后面的路还长着呢。 写下属于自己的 AFO 吧.意外的（\\( \\color{green}{\\sqrt{}} \\)） $$ \\large{\\mathcal{AFO.}} $$","date":"24 November 2022","externalUrl":null,"permalink":"/acm/afo100/","section":"题解和游记","summary":"","title":"退役前要做的100件事","type":"acm"},{"content":" 动态规划 # 背包DP： # \\(0-1\\) 背包\n完全背包\n多重背包\n​\t二进制分组\n​\t单调队列优化\n混合背包\n分组背包\n区间DP\n树形DP： # 树上背包\n换根\n状压DP\n数位DP\n动态DP\nDP优化： # 单调队列/单调栈优化\n斜率优化\n四边形不等式\nDS优化\n数学 # 斐波那契数列\n杨辉三角\n数论： # 欧拉筛 \u0026amp; 埃氏筛\nMiller-Rabin \u0026amp; Pollard Rho\n扩展欧几里得法（Ex gcd）\n数论分块\n裴蜀定理\n欧拉定理\n费马小定理\n中国剩余定理\nLucas 定理\n莫比乌斯反演\n线性规划： # 高斯消元\n线性基\n矩阵乘法\n组合数学： # 排列组合\n第二类斯特林数\n抽屉原理\n康托展开\n容斥原理\n博弈论： # Nim 博弈\nSG 函数\n牛顿迭代法\n分段打表\n图论 # 拓扑排序\n最短路 \u0026amp; 次短路 \u0026amp; k短路\n最小生成树\n差分约束\nTarjan：\n强连通分量\n缩点\n点双 \u0026amp;边双\n割点 \u0026amp; 割边\n2-SAT\n网络流： # 最大流 \u0026amp; 最小割 \u0026amp;Dinic\n最小费用最大流\n黑白染色\n图的匹配： # 二分图最大匹配 \u0026amp; 二分图最大权匹配\n一般图最大匹配 \u0026amp; 一般图最大权匹配\n树： # 树的直径\n树的重心\nLCA\n树链剖分\nDsu on Tree（树上启发式合并）\n树哈希\n树分治\n数据结构（基础的不计） # 并查集\n堆\n单调栈 \u0026amp; 单调队列\n分块 \u0026amp; 块状链表\nST表\n树状数组\n线段树： # 主席树\n线段树分裂\n线段树合并\n线段树分治\n线段树上二分\n李超线段树\n吉司机线段树\n平衡树\n珂朵莉树\n笛卡尔树\n字符串 # 最小表示法\n哈希\nKMP\nTrie\nZ函数\nAC自动机\n失配树\nManacher\n基础 # 搜索： # 双向搜索 \u0026amp; Meet in the middle\nA*\n迭代加深\nIDA*\nMinimax算法 \u0026amp; $ \\alpha - \\beta $ 剪枝\n贪心\n排序（sort）\n二分\n倍增\n分治\n离散化\n杂 # 摩尔投票\n双指针\n","date":"22 November 2022","externalUrl":null,"permalink":"/acm/learn_algo/","section":"题解和游记","summary":"","title":"关于部分学过的算法统计","type":"acm"},{"content":" CF1009E 题解 # 数学题，可见答案求的是所有代价的和。\n考虑每个 \\(a_{i}\\) 会被计算几次，发现对于一个 \\(a_{i}\\)，它的贡献是 \\(\\sum\\limits_{i=1}^{n-i+1}i \\times C_{n-i+1}^{n-i}$。例如，样例 \\(2\\) 中 \\(a_{1}\\) 的贡献是 ((4\\times 1 +3\\times 3+2\\times 3+1\\times 1 )\\times a_{1}=20$。\n但是直接做是 \\(O(n^2)\\) 的，考虑化简，把式子 \\(\\times 2\\)，变成（暂时把上式中 \\(n-i+1\\) 看成 \\(N\\) ）：\n原式 (=\\large{\\frac{N\\times C_{N}^{0}+ 1\\times C_{N}^{N-1} +(N-1)\\times C_{N}^{1}+2\\times C_{N}^{N-2}+\u0026hellip; }{2}}$\n​\t$=\\large{\\frac{(N+1)\\times \\sum\\limits_{i=0}^{N-1}C_{N}^{i}}{2}})\n​\t$=\\large{\\frac{(N+1)\\times 2^{N-1}}{2}})\n​\t$=(N+1)\\times 2^{N-2}$\n把 \\(N=n-i+1\\) 代入，得 原式 \\(=(n-i+2)\\times 2^{n-i-1}\\)。\n最后答案便是 \\(\\sum\\limits_{i=1}^{n} a_{i}\\times (n-i+2)\\times 2^{n-i-1}\\)，时间复杂度 \\(O(n)\\)。\n注意指数可能为负，稍微改改快速幂就行。\nTalk is cheap, show me the code.\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; #define int long long typedef pair\u0026lt;int,int\u0026gt; Pii; const int N=1e6+5,Mod=998244353; int n; int a[N]; int ans=0; inline int qp(int a,int b,int res=1) { bool f=b\u0026lt;0;b=abs(b); while(b) { if(b\u0026amp;1)res=res*a%Mod; a=a*a%Mod;b\u0026gt;\u0026gt;=1; }return f?qp(res,Mod-2):res; } signed main() { cin.tie(0)-\u0026gt;sync_with_stdio(0); cin\u0026gt;\u0026gt;n; for(int i=1;i\u0026lt;=n;i++) { cin\u0026gt;\u0026gt;a[i]; (ans+=(n-i+2)*qp(2,n-i-1)%Mod*a[i]%Mod)%=Mod; } cout\u0026lt;\u0026lt;ans\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; return 0; } ","date":"13 November 2022","externalUrl":null,"permalink":"/acm/cf1009e/","section":"题解和游记","summary":"","title":"CF1009E题解","type":"acm"},{"content":" 初赛篇： # Day -？~ -1： # 模拟赛 + 初赛模拟。。。\nDay 0： # 上午摆烂，下午最后一场初赛模拟，晚上 \\(\\text{Left 4 Dead 2}\\) （拔刀剑真的爽），早早的睡了。\nDay 1： # 上午睡到自然醒，吃完饭来到机房。考前很紧张，但是干等。\n\\(2：15\\) 进场，但是干等。\n开考了，前面选择题还好，但是宇宙射线和期末作业。后面大题感觉还算看得懂，急急忙忙做完了，但是也快考完了，不是很想检查，就在算期末作业那题，结果到最后才发现自己是小丑，语文理解能力真是差透了。。。\n考完听同学说挺简单的，但是民间自测 \\(76\\) \\(Boom!!!\\) ，突然害怕分数线会高的离谱，不会连初赛都寄吧。\nDay ？： # 出分日，\\(76\\)，但是分数线 \\(74.5\\)，总算松了一口气，但是还是被同机房的人吊打，简直菜透了。。。\n复赛篇： # Day -？~ -1： # 疯狂模拟赛。。。（场场垫底，题出的都好难）\nDay 0： # 突然来了一场远古模拟赛，但是 \\(\\text{T1 dp}\\) 调出来，忘记滚了，喜提保单。\n下午打板子，看了点数学思维题。\n晚上还是摆。\nDay 1： # 早上被叫醒，以为要按时到校，但是来到机房发现人都没有。。。\n于是接着睡，看了看板子，研究了一下快速配 \\(Vscode\\)。\n吃完饭，上了大巴。车上后排在打委托（op），但是奈何我之前把原删了，只好听歌。\n到了杭师大（世界最高校），但是第一次看到完美的 锕人 现场（锕大树）。\n\\(2：15\\) 进场，但是不让动键盘，干等。\n开考了，\\(1\\) 分钟配完 \\(Vscode\\)，开始看题。\n\\(T1\\) 看了半天题目，刚开始把最多 \\(k\\) 次看成了恰好，差点以为不可做。但是 \\(O(nm)\\) 预处理之后，发现答案要 \\(n^4\\) 求，而且不知道为什么，完全想不出怎么优化（赛后觉得当时一定是脑抽了）。结果打了 \\(70pts\\) 暴力。\n\\(T2\\) 一眼分讨，但是手玩了一会感觉情况好多（但是其实暴力枚举就行，我评价是赛时脑子跟 \\(shit\\) 一样），打算先写个 \\(75pts\\)，待会再来看。\n\\(T3\\) 题面真的长，看了 \\(114514\\) 毫秒才看懂，想了想不会正解，就开始打暴力，暴力还调了好久，估计 \\(40pts\\)。\n发现之后 \\(35min\\) 了，感觉很危，\\(T4\\) 看了 \\(15min\\) 看懂，但是感觉暴力 \\(20min\\) 不一定调的出来，只好弃了，开始想 \\(T1\\) 优化，结果脑抽的厉害，没想出来，还错过了写 \\(T2\\) 的时间。。。直接 \\(Boom!!!\\)\n考完听其他同学都切了 \\(T1T2\\) ，感觉心态很炸，估计 1= 没了。\n民间数据瞎测测，发现暴力没 f，\\([70,80]+75+40+0=[185,195] pts\\)，心情稍微好了点，但是班里垫底。。。听说 1= 可能要 \\(200\\) 左右，快进到差 \\(10pts\\) 1=（去年 \\(J\\) 组的经历）。\nDay ？： # 出分日（CCF数据怎么这么水），\\(75+75+60+0=210\\)，这样的数据，感觉分数线会变高，害怕。rnm，退钱！\nDay ？？： # 出线日！\\(\\text{ZJ1=}\\) 线 \\(210~!~!~!\\)，惊险卡线 \\(\\text{1=}\\)（快说感谢CCF）。但是 7 级勾 \\(240\\)，所以 \\(\\large{NOIP~~rp++}\\) ！\n","date":"1 November 2022","externalUrl":null,"permalink":"/acm/csp2022/","section":"题解和游记","summary":"","title":"CSP-S2022游记","type":"acm"},{"content":" （树上问题+组合数学） \\(O(D)\\)\n设 \\(X=\\) 左边深度为 \\(k\\) 的顶点数 \\(\\times\\) 右边深度为 \\(D−k\\) 的顶点数， \\(M=max(k,D−k)\\)\n而高度大于等于 M 的子树有 \\(2^{N−M}-1\\) 个。\n对于每个 k 的贡献是 \\((2^{N−M}-1)\\times X\\)\n我们枚举 \\(k=0∼D\\) 即可。\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; #define int long long typedef pair\u0026lt;int,int\u0026gt; Pii; const int N=1e6+5,Mod=998244353; int n,d; int a[N]; signed main() { cin.tie(0)-\u0026gt;sync_with_stdio(0); a[0]=1;cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;d; for(int i=1;i\u0026lt;=n;i++) a[i]=a[i-1]*2%Mod; int ans=0; for(int i=0;i\u0026lt;=d;i++) { int j=d-i; if(i\u0026gt;=n||j\u0026gt;=n)continue; int res=(a[n-max(i,j)]-1+Mod)%Mod; res=res*a[max(i-1,0ll)]%Mod*a[max(j-1,0ll)]%Mod; (ans+=res)%=Mod; } cout\u0026lt;\u0026lt;(ans\u0026lt;\u0026lt;1)%Mod\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; return 0; } ","date":"11 October 2022","externalUrl":null,"permalink":"/acm/abc220e/","section":"题解和游记","summary":"","title":"Atcoder ABC220E","type":"acm"},{"content":" 对于每次删点求最短路，范围小可以考虑倒着做 Floyd，因为 Floyd 对松弛操作的顺序没有要求（CF295B）。 求序列本质不同子序列数量。 令 \\(f_{i}\\) 表示 \\(S_{1∼i}\\) 的本质不同子序列数量。\n方法一：是通过子序列自动机得出转移：\\(f_{i} = 1 + \\sum_{x} f_{lst_{x}}\\)。\n方法二：是通过容斥得出转移：\\(f_{i} = 2f_{i−1} − f_{lst_{a_{i}}−1}\\)。\n给简单无向联通图定向，要求每个点出度为偶数，求构造方案。 发现边数为奇数显然无解，对于边数为偶数，考虑先跑出生成树，生成树外的边随便连，内的边从下到上连边，若当前点出度为奇数连自己向父亲的边。\n给一堆二元组，要求分成两组，使 \\(\\sum x_i+\\sum y_i\\) 最大的问题。 考虑一个经典贪心，考虑两个二元组 \\((x_i,y_i)\\) 和 \\((x_j, y_j)\\)，若 \\(x_i+y_j\u003ex_j+y_i\\)，则有 \\(x_i-y_i\u003ex_j-y_j\\)，即 \\(x-y\\) 更大的二元组选 \\(y\\) 一定更优。所以把这些二元组按 \\(x-y\\) 从小到大排序，最大的 \\(X\\) 个选成 \\(x\\)，剩下的选成 \\(y\\) 即可。\nEx: 若是三元组，分成三组。AGC018C 。只要先全选第一个。然后，就转化成两个的问题了。\n求 \\(\\large{\\lfloor \\frac{x}{M}\\rfloor\\mod p}\\) 。 经典做法，对于 \\(x\\) 这部分计算时可以直接对 \\(M*p\\) 取模（注意开 \\( \\text{int128} \\)），然后正常除下取整，外面 \\(\\mod p\\) 。\n\\(\\large{\\sum\\limits_{i=1}^k \\sum\\limits_{d|\\gcd(i,n)}{\\mu(d)}}=\\large{\\sum\\limits_{d|n}\\mu(d)\\lfloor \\frac{k}{d} \\rfloor}\\)，简单的转化，但是怕忘。 莫比乌斯函数： 定义：\\(\\mu(n)=\\left\\{ \u003e \\begin{array}{lc} \u003e 1 \u0026 n=1 \\\\ \u003e 0 \u0026 n含有平方因子\\\\(-1)^k \u0026 k为 n 的本质不同质因子个数\\end{array} \u003e \\right.\\)\n性质：\\(\\sum\\limits_{d|n}\\mu(d)=\\left\\{ \u003e \\begin{array}{lc} \u003e 1 \u0026 n = 1 \\\\ \u003e 0 \u0026 n \\ne 1 \\\\ \u003e \\end{array} \u003e \\right.\\)\n结论：\\(\\large{[\\gcd(i,j)=1]\\Rightarrow \\sum\\limits_{d|\\gcd(i,j)}\\mu(d)}\\)\n","date":"30 September 2022","externalUrl":null,"permalink":"/acm/trick/","section":"题解和游记","summary":"","title":"Trick","type":"acm"},{"content":" CF1000C # 难度 1700 \\(代码\\)\n开始以为是线段树，但是发现好像直接离线做就好了。。。 # CF1176D # 难度 1800 \\(代码\\)\n思维题，考虑从大到小贪心。\n若当前数为质数，那么它一定不在原数组中，那么找到 \\(prime_{x}==b_{i}\\) 的 \\(x\\) 并放入原数组，删除两数。\n若为合数，那么它一定在原数组中，找到它因子中最大的那个，删除两数。 # CF295B # 难度 1700 \\(代码\\)\n发现正着做，无论如何都是 \\(\\geq O(n^4)\\)，于是考虑倒着做，转化为每次加一个点，此时可以 \\(Floyd\\) 直接来，因为 \\(Floyd\\) 对松弛操作的顺序没有要求，所以复杂度 \\(O(n^3)\\)。 # CF1388C # 难度 1800 \\(代码\\)\n细节题，考虑 \\(YES\\) 需要满足的条件：\n1.经过当前点的人数 \\(\\ge |h_{u}|\\)，显然。\n2.经过当前点的人数 \\(-h_{u}\\) 能被 2 整除，考虑设 经过当前点的人数为 x，可得当前点心情好的有 \\(\\frac{1}{2} (x+h_{i})\\) 个，不好的有 \\(\\frac{1}{2} (x-h_{i})\\) 个。\n3.当前点心情不好的人数 \\(\\leq\\) 其子树中所有点心情不好的人数之和，因为心情不好的人不会变好，所以心情不好的人数只增不减。 # CF1354D # 难度 1900 \\(代码\\)\n本来想拿 \\(pbds\\) 的 \\(tree\\) 写，发现卡空间。于是想到值域分块做，但是看数据范围 \\(1e6\\) ，感觉很卡，应该过不了（后来看题解区好像有人写了，能过），只能改用二分+树状数组，时间复杂度 \\(O(nlog^2n)\\)。 # CF1400E # 难度 2200 \\(代码\\)\n本来想写 \\(dp\\)，但是奈何太菜了，写萎了。于是考虑分治。\n\\(f(l,r)\\) 表示把 \\(l~r\\) 推平的最少次数，若全用操作 2，则需 \\(r-l+1\\) 次，若用操作 1，则考虑一直用操作 1，直至有一位变成 0，然后贡献是 \\(f(l,p-1)+f(p+1,r)+a_{p}\\)。 时间复杂度 \\(O(n^2)\\)。 # CF743D # 难度 1800 \\(代码\\)\n考虑树形 dp，设 \\(f_{u}\\) 表示以 u 为根的子树的最大子树和，转移显然为 \\(\\large{f_{u}=\\max_{\\max_{f_{v}},sum_{u}}}\\)，而答案则是取每个 u 的儿子 v 中，最大的和次大的 \\(f_{v}\\) 之和的最大值。若只有一个儿子，则不更新答案。 # CF626D # 难度 1800 \\(代码\\)\n概率题，考虑 dp，假设第一个人取的数是 \\(a_{1},a_{2},a_{3}\\)，第二个人是 \\(b_{1},b_{2},b_{3}\\)，求 \\( (a_{1}-b_{1})+(a_{2}-b_{2}) \u003c a_{3}-b_{3} [a_{1} \u003e b_{1},a{2} \u003e b_{2}] \\) 的概率。设 \\(f_{0,i}\\) 表示 \\(a_{x}-a_{y}==i\\) 的概率， \\(f_{1,i}\\) 表示 \\((a_{x1}-a_{y1})+(a_{x2}-a_{y2})==i\\) 的概率，直接做就行了。 # CF1690G # 难度 2000 \\(代码\\)\n萌萌 ds 题，相当于是单点减 + 区间推平，考虑直接珂朵莉做。推平就暴力往后枚举，能并就并，经过我们的若干次操作，最多会有 \\(n+m\\) 个区间，所以复杂度 \\(O((n+m)\\log{n})\\)。 # CF1702G2 # 难度 2000 \\(代码\\)\nLCA 好题，要求判断是否存在一条链包含树上给定点集。考虑这样一个性质：若存在满足条件的最短链，则点集中深度最深的点 u 是该链的一个端点，点集中距离 u 最远的点 v 是该链的另一端点。\n所以，直接求出头尾两点的 LCA，对于其他点，若其 \\(dep_{u} \u003c dep_{LCA}\\) 则不满足，否则若其不在 \\(1-st || 1-ed\\) 的路径上，则也不满足。每个点可以用 LCA 判。 # CF730B # 难度 1800 \\(代码\\)\n如图先每相邻两个一组，询问 \\(\\lfloor \\frac n 2 \\rfloor\\) 次，再组与组之间比较，每两组比较两次，找最大最小预计 \\(n-1\\) 次，刚好满足题目要求次数。 # CF1012C # 难度 1900 \\(代码\\)\ndp，考虑设:\n\\(f_{i,j,0}\\) 表示 前 i 个数中，有 j 个峰，第 i,i-1 都不为峰的方案数。\n\\(f_{i,j,1}\\) 表示 前 i 个数中，有 j 个峰，第 i 为峰的方案数。\n\\(f_{i,j,2}\\) 表示 前 i 个数中，有 j 个峰，第 i-1 为峰的方案数。\n考虑转移：\n\\(f_{i,j,0}=min(f_{i-1,j,0},f_{i-1,j,2});\\)\n\\(f_{i,j,1}=min(f_{i-1,j-1,0}+max(0,a_{i-1}-a_{i}+1),f_{i-1,j-1,2}+max(0,min(a_{i-1},a_{i-2}-1)-a_{i}+1));\\)\n\\(f_{i,j,2}=f_{i-1,j,1}+max(0,a_{i}-a_{i-1}+1);\\)\n第一维可以直接滚掉，同时注意要倒序枚举 j，从转移可见，先更新顺序为 \\(f_{i,j,0},f_{i,j,2},f_{i,j,1}\\)。时间复杂度 (O(n^2)$ # CF1174D # 难度 1900 \\(代码\\)\n先考虑构造，序列中相邻两个元素异或和不为 0 或 x，这个可以记 vis 直接做，然后考虑怎么把相邻扩展到任意，直接异或前缀和即可。 # CF1537D # 难度 1700 \\(代码\\)\n找规律题，可以手玩或者打暴力，发现奇数都是 \\(Bob\\) 赢，偶数则是当其为 \\(2^{2k+1}\\) 时是 \\(Bob\\) 赢，否则是 \\(Alice\\) 赢。 # CF161D # 难度 1800 \\(代码\\)\n范围比较小，可以直接 \\(O(nk)\\) 树形 dp，设 \\(f_{u,d}\\) 表示与 u 距离为 d 的点数，直接暴力转移就行。 # CF1144F # 难度 1700 \\(代码\\)\n你谷恶评，这题居然有紫。。。，要使图中没有距离 \\(\\geq 2\\) 的路径，那么就是相邻两点不能同时为入或者同时为出，那么发现直接黑白染色即可。 # CF1144G # 难度 2400 \\(代码\\)\n设 \\(f_{i,0}\\) 表示把序列的前 i 个数拆成一个递增序列和一个递减序列（可以为空），并且 \\(A_i\\) 属于递增序列时，递减序列结尾可能的最大值。$f_{i,1})，表示 \\(A_i\\) 属于递减序列时，递增序列结尾可能的最小值。 # 转移有四种： # \\(A_{i-1},A_i\\) 都属于递增序列，条件是 \\(A_{i-1} \u003c A_i\\)，转移为 \\(f_{i-1,0} \\rightarrow f_{i,0}\\)。 # \\(A_{i-1},A_i\\) 都属于递减序列，情况类似。 # \\(A_{i-1}\\) 属于递减序列，\\(A_i\\) 属于递增序列，条件是 \\(f_{i-1,1} \u003c A_i\\)，转移为 \\(A_{i-1} \\rightarrow f_{i,0}\\)。 # \\(A_{i-1}\\) 属于递增序列，\\(A_i\\) 属于递减序列，情况类似。 # 为了输出方案，记 \\(lst_{i,0}\\) 表示在最优方案中 \\(A_{i-1}\\) 属于哪个序列，\\(lst_{i,1}\\) 同理。 # CF1335E2 # 难度 1800 \\(代码\\)\n模拟题。首先我们记录某一个数 \\(a_i\\) 到 i 出现的次数以及出现到这个次数的位置。前缀后缀个各做一遍。然后我们枚举第一个颜色 x 以及它第一段的长度l。然后得出 \\(p_1,p_2\\) 分别表示前缀 x 颜色到达 l 个的位置，以及后缀到达 l 个的位置。 # 然后在枚举第二种颜色 y，那么 \\(ans=\\max(ans,l\\times 2+\\max(p_2-p_1))\\) 为到 \\([p1,p2]\\) 这段区间里面出现次数最多的颜色数量。 # CF1477B # 难度 1900 \\(代码\\)\n我们模拟一下样例的过程，发现正着不好做，考虑反过来，即将 T 改为 F。 # 对于 一段区间 \\([l_i,r_i]\\) # 如果 01 数量相同，不能改变字符串。 # 如果 1 的数量超过一半，就只能把所有 0 改为 1。 # 如果 1 的数量没超过一半，就只把所有 1 改为 0。 # 只需一个数据结构维护区间覆盖 + 区间求和，直接上线段树或分块即可。 # CF527D # 难度 1800 \\(代码\\)\n设 \\(x_i \\geq x_j\\)，原式可以写为 \\(x_i-w_i \\geq x_j+w_j\\)，对于一个点 i，我们再给它加上两个属性：l 和 r。其中 \\(l_i = x_i-w_i\\)，\\(r_i=x_i+w_i\\)，然后再看看刚才推出来的式子，就变成了 \\(l_i \\geq r_j\\)，接着就是贪心线段覆盖。 # ","date":"30 September 2022","externalUrl":null,"permalink":"/acm/cf%E9%9A%8F%E6%9C%BA%E5%81%9A%E9%A2%98/","section":"题解和游记","summary":"","title":"CF随机做题","type":"acm"},{"content":" 题目翻译 # 注：翻译中样例仍存在一些换行符的问题，具体下文注意点会有。\n解： # 模拟题没什么好说的，就纯模拟呗。\n写大模拟的建议： # 动手前先理清思路，细节想清楚。 代码写干净一些，便于调试。 有好用的视网膜和强大的耐心。 注意点： # 个人认为此题的模拟部分不难，但 WA 的原因都在很多细小的注意点上。（关键有些注意点题目里也没给出。。。）\n建议大家过了样例后，去和 uDebug 上的一组 大数据 对一下，\n正常情况下，这组数据过了，交上去基本上就过了。（但也有特例，比如我。。。）\n错误大全： # 首先这两篇讨论涵盖了几乎大部分注意点：\n讨论一\n讨论二\n然后是我的补充：\n\\(-nan\\) 的情况 在讨论二中楼主说到 \\(-nan\\) 的情况，最好判一下，因为我发现我的代码如果不判的话会 WA。\n判断方法： 首先要知道 \\(-nan\\) 的情况是在操作 \\(5\\) 统计人数，当人数为 \\(0\\) 时，计算平均数 (double)(num)/(double)(0) 时的结果，所以只要特判一下就好了。\n记得赋初值 还有如果有人在写操作 \\(5\\) 时，像我一样开一个结构体存统计结果，记得一定要给里面的变量都赋初值！（这就是为什么我 uDebug 上的数据过了，却 WA 的原因，我就漏赋了一个值。。。）\nCode： # #include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; #define Debug(i) cout\u0026lt;\u0026lt;i\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;//找 Bug用的 struct Student{//学生信息 string SID,CID,Name; int Chinese,Math,English,Program; int Tot,Rank,ID; bool operator\u0026lt;(const Student \u0026amp;n)const{ return Tot\u0026gt;n.Tot; } }Message[201]; struct Statistics{//统计信息，注意赋初值 int Student_Tot=0;//我最后就是这个变量没赋初值，就 WA了 double Chinese_Ave=0.0,Math_Ave=0.0,English_Ave=0.0,Program_Ave=0.0; int Chinese_Tot=0,Math_Tot=0,English_Tot=0,Program_Tot=0; int Chinese_Passed_Student=0,Chinese_Failed_Student=0; int Math_Passed_Student=0,Math_Failed_Student=0; int English_Passed_Student=0,English_Failed_Student=0; int Program_Passed_Student=0,Program_Failed_Student=0; int Passed_1_more_Subjects=0; int Passed_2_more_Subjects=0; int Passed_3_more_Subjects=0; int Passed_All_Subjects=0; int Failed_All_Subjects=0; }; int opt,top,Top; //top记录当前系统中学生人数 //Top记录按输入顺序第几个加入的学生，为了操作 3输出多个人信息时，按输入顺序排序 double eps=1e-5;//避免精度误差 //Output每个操作的输出 void Welcome(){printf(\u0026#34;Welcome to Student Performance Management System (SPMS).\\n\\n1 - Add\\n2 - Remove\\n3 - Query\\n4 - Show ranking\\n5 - Show Statistics\\n0 - Exit\\n\\n\u0026#34;);} //Add void AddQ(){printf(\u0026#34;Please enter the SID, CID, name and four scores. Enter 0 to finish.\\n\u0026#34;);} void Duplicated(){printf(\u0026#34;Duplicated SID.\\n\u0026#34;);} //Remove void RemoveQ(){printf(\u0026#34;Please enter SID or name. Enter 0 to finish.\\n\u0026#34;);} void Remove_Student(int x){printf(\u0026#34;%d student(s) removed.\\n\u0026#34;,x);} //Query void QueryQ(){printf(\u0026#34;Please enter SID or name. Enter 0 to finish.\\n\u0026#34;);} void Query_Student(int i){printf(\u0026#34;%d %s %s %s %d %d %d %d %d %.2lf\\n\u0026#34;,Message[i].Rank,Message[i].SID.data(),Message[i].CID.data(),Message[i].Name.data(),Message[i].Chinese,Message[i].Math,Message[i].English,Message[i].Program,Message[i].Tot,double(Message[i].Tot/4.0+eps));} //Show_Ranking void Show_RankingQ(){printf(\u0026#34;Showing the ranklist hurts students\u0026#39; self-esteem. Don\u0026#39;t do that.\\n\u0026#34;);} //Show_Statistics void Show_StatisticsQ(){printf(\u0026#34;Please enter class ID, 0 for the whole statistics.\\n\u0026#34;);} void Show_Statistics_Class(Statistics Sum,bool flag) { if(flag) { printf(\u0026#34;Chinese\\nAverage Score: %.2lf\\nNumber of passed students: %d\\nNumber of failed students: %d\\n\\n\u0026#34;,Sum.Chinese_Ave,Sum.Chinese_Passed_Student,Sum.Chinese_Failed_Student); printf(\u0026#34;Mathematics\\nAverage Score: %.2lf\\nNumber of passed students: %d\\nNumber of failed students: %d\\n\\n\u0026#34;,Sum.Math_Ave,Sum.Math_Passed_Student,Sum.Math_Failed_Student); printf(\u0026#34;English\\nAverage Score: %.2lf\\nNumber of passed students: %d\\nNumber of failed students: %d\\n\\n\u0026#34;,Sum.English_Ave,Sum.English_Passed_Student,Sum.English_Failed_Student); printf(\u0026#34;Programming\\nAverage Score: %.2lf\\nNumber of passed students: %d\\nNumber of failed students: %d\\n\\n\u0026#34;,Sum.Program_Ave,Sum.Program_Passed_Student,Sum.Program_Failed_Student); printf(\u0026#34;Overall:\\nNumber of students who passed all subjects: %d\\nNumber of students who passed 3 or more subjects: %d\\nNumber of students who passed 2 or more subjects: %d\\nNumber of students who passed 1 or more subjects: %d\\nNumber of students who failed all subjects: %d\\n\\n\u0026#34;,Sum.Passed_All_Subjects,Sum.Passed_3_more_Subjects,Sum.Passed_2_more_Subjects,Sum.Passed_1_more_Subjects,Sum.Failed_All_Subjects); } else//特判 -nan { printf(\u0026#34;Chinese\\nAverage Score: -nan\\nNumber of passed students: %d\\nNumber of failed students: %d\\n\\n\u0026#34;,Sum.Chinese_Passed_Student,Sum.Chinese_Failed_Student); printf(\u0026#34;Mathematics\\nAverage Score: -nan\\nNumber of passed students: %d\\nNumber of failed students: %d\\n\\n\u0026#34;,Sum.Math_Passed_Student,Sum.Math_Failed_Student); printf(\u0026#34;English\\nAverage Score: -nan\\nNumber of passed students: %d\\nNumber of failed students: %d\\n\\n\u0026#34;,Sum.English_Passed_Student,Sum.English_Failed_Student); printf(\u0026#34;Programming\\nAverage Score: -nan\\nNumber of passed students: %d\\nNumber of failed students: %d\\n\\n\u0026#34;,Sum.Program_Passed_Student,Sum.Program_Failed_Student); printf(\u0026#34;Overall:\\nNumber of students who passed all subjects: %d\\nNumber of students who passed 3 or more subjects: %d\\nNumber of students who passed 2 or more subjects: %d\\nNumber of students who passed 1 or more subjects: %d\\nNumber of students who failed all subjects: %d\\n\\n\u0026#34;,Sum.Passed_All_Subjects,Sum.Passed_3_more_Subjects,Sum.Passed_2_more_Subjects,Sum.Passed_1_more_Subjects,Sum.Failed_All_Subjects); } } void Add() { string SID,CID,Name; int Chinese,Math,English,Program; int Tot; while(true) { AddQ(); cin\u0026gt;\u0026gt;SID; if(SID==\u0026#34;0\u0026#34;)break; cin\u0026gt;\u0026gt;CID\u0026gt;\u0026gt;Name; scanf(\u0026#34;%d %d %d %d\u0026#34;,\u0026amp;Chinese,\u0026amp;Math,\u0026amp;English,\u0026amp;Program); for(int i=1;i\u0026lt;=top;i++) if(Message[i].SID==SID){Duplicated();goto Nxt;}//学生SID重复 Tot=Chinese+Math+English+Program; Top++; Message[++top]=(Student){SID,CID,Name,Chinese,Math,English,Program,Tot,0,Top};//加入 Nxt:; } } void Remove() { string str; while(true) { RemoveQ(); int sum=0; cin\u0026gt;\u0026gt;str; if(str==\u0026#34;0\u0026#34;)break; for(int i=1;i\u0026lt;=top;i++) if(Message[i].SID==str||Message[i].Name==str) { top--; for(int j=i;j\u0026lt;=top;j++) Message[j]=Message[j+1]; i--,sum++; } Remove_Student(sum); } } void Rank()//更改排名 { for(int i=1,Rank=1;i\u0026lt;=top;i++) { Message[i].Rank=Rank; if(Message[i].Tot!=Message[i+1].Tot)Rank=i+1; } } bool cmp(const int \u0026amp;x,const int \u0026amp;y)//按输入顺序排序 { return Message[x].ID\u0026lt;Message[y].ID; } void Query() { sort(Message+1,Message+top+1);//先按成绩排序 Rank(); string str; int a[101],sum; while(true) { memset(a,0,sizeof(a)); sum=0; QueryQ(); cin\u0026gt;\u0026gt;str; if(str==\u0026#34;0\u0026#34;)break; for(int i=1;i\u0026lt;=top;i++) if(Message[i].SID==str||Message[i].Name==str) a[++sum]=i; sort(a+1,a+sum+1,cmp);//对所有符合的学生按输入顺序排序 for(int i=1;i\u0026lt;=sum;i++) Query_Student(a[i]); } } void Show_Ranking() { Show_RankingQ(); } void Show_Statistics() { Show_StatisticsQ(); string str; cin\u0026gt;\u0026gt;str; bool isAll=false; if(str==\u0026#34;0\u0026#34;) isAll=true; Statistics Sum; for(int i=1;i\u0026lt;=top;i++)//统计 { if(isAll||Message[i].CID==str) { int Pass_Sum=0; Sum.Student_Tot++; //统计四科分数和，用于求平均数 Sum.Chinese_Tot+=Message[i].Chinese; Sum.Math_Tot+=Message[i].Math; Sum.English_Tot+=Message[i].English; Sum.Program_Tot+=Message[i].Program; //四科及格人数统计 if(Message[i].Chinese\u0026gt;=60) Sum.Chinese_Passed_Student++,Pass_Sum++; else Sum.Chinese_Failed_Student++; if(Message[i].Math\u0026gt;=60) Sum.Math_Passed_Student++,Pass_Sum++; else Sum.Math_Failed_Student++; if(Message[i].English\u0026gt;=60) Sum.English_Passed_Student++,Pass_Sum++; else Sum.English_Failed_Student++; if(Message[i].Program\u0026gt;=60) Sum.Program_Passed_Student++,Pass_Sum++; else Sum.Program_Failed_Student++; switch(Pass_Sum)//统计总体情况 { case 1: Sum.Passed_1_more_Subjects++; break; case 2: Sum.Passed_1_more_Subjects++,Sum.Passed_2_more_Subjects++; break; case 3: Sum.Passed_1_more_Subjects++,Sum.Passed_2_more_Subjects++,Sum.Passed_3_more_Subjects++; break; case 4: Sum.Passed_1_more_Subjects++,Sum.Passed_2_more_Subjects++,Sum.Passed_3_more_Subjects++,Sum.Passed_All_Subjects++; break; default: Sum.Failed_All_Subjects++; } } } if(Sum.Student_Tot\u0026gt;0)//特判学生人数是否为 0 { Sum.Chinese_Ave=double(1.0*Sum.Chinese_Tot/double(1.0*Sum.Student_Tot)+eps); Sum.Math_Ave=double(1.0*Sum.Math_Tot/double(1.0*Sum.Student_Tot)+eps); Sum.English_Ave=double(1.0*Sum.English_Tot/double(1.0*Sum.Student_Tot)+eps); Sum.Program_Ave=double(1.0*Sum.Program_Tot/double(1.0*Sum.Student_Tot)+eps); Show_Statistics_Class(Sum,1); } else Show_Statistics_Class(Sum,0); } signed main()//主函数 { while(true) { Welcome(); scanf(\u0026#34;%d\u0026#34;,\u0026amp;opt); switch(opt) { case 1:Add();break; case 2:Remove();break; case 3:Query();break; case 4:Show_Ranking();break; case 5:Show_Statistics();break; default:exit(0);//opt是 0就结束 } } return 0; } 注： # ","date":"11 March 2022","externalUrl":null,"permalink":"/acm/uva12412/","section":"题解和游记","summary":"","title":"UVA12412 A Typical Homework (a.k.a 师兄帮帮忙)题解","type":"acm"},{"content":" 题目传送门 # 题意分析： # 这题最重要的是要看懂题目意思：\n给出 n 个城市和 m 条边，其中 z 表示此边是否能通行。 求一条从城市 \\(1\\) 到 n 尽可能短且对道路的影响（即炸毁或修复）尽可能小的路。 那么如何使选出的路对道路的影响小呢？\n多读几遍题可知：\n对于在所选路径上的路，所要修复（即不可通行）的路要尽可能少。 对于非所选路径上的路，所要炸毁（即可通行）的路要尽可能少。 解： # 这题用最短路做（关于 SPFA ，它死了。），于是我用 Dijkstra 做。\n根据刚才的分析，\n我们用 \\( dis_{ i } \\) 表示从城市 \\( 1 \\) 到 i 的最短路长度，\n\\( path_{ i } \\) 表示所选最短路中从城市 1 到 i 的不可通行路的数量，\n更新时考虑找路径最短，且路径中不可通行路数量少：\n//it.nxt表示下一个城市，now表示现在的城市， //it.w表示这条路能否通行（因为 0 表示不可通行，所以加 !it.w，即加 1）。 if((dis[it.nxt]==dis[now]+1\u0026amp;\u0026amp;path[it.nxt]\u0026gt;path[now]+!it.w)||dis[it.nxt]\u0026gt;dis[now]+1) { 更新... } 输出： # 这题的输出也是有点麻烦。\n我们先用 \\( pre_{ v } = u \\) 表示前驱，即路径中城市 v 的上一个是城市 u，\n跑完 Dijkstra 后从 n 点递归标记选出的最短路：\nvoid Print(int x) { vis[x]=1; if(pre[x]) Print(pre[x]); } int main() { ... memset(vis,0,sizeof(vis)); Print(n); ... } 然后对于所有边按题意模拟一遍输出即可。（具体见代码）\nCode： # 注：\n本代码中有关 auto 的用法最好用 C++17 提交，\n不过其实这个在 C++14 就能用（只是会警告，我平时都用 C++14 交的），\n但这题用 C++14 在 CF 上交会 CE。\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; struct Node{ int nxt;bool w; }; int n,m; vector\u0026lt;Node\u0026gt;a[100001];//本人较懒，就用vector存边了 int dis[100001],vis[100001],path[100001],pre[100001]; typedef pair\u0026lt;int,int\u0026gt; Pii; priority_queue\u0026lt;Pii,vector\u0026lt;Pii\u0026gt;,greater\u0026lt;Pii\u0026gt; \u0026gt;q;//小根堆 void Dijkstra(int s)//堆优化 Dijkstra { memset(dis,0x3f,sizeof(dis));//初始化 memset(path,0x3f,sizeof(path));//别忘记初始化 path dis[s]=0;path[s]=0;q.emplace(0,s); while(!q.empty()) { auto [w,now]=q.top();q.pop(); if(vis[now])continue; vis[now]=1; for(auto it:a[now])//遍历与城市 now 相连的城市，用 auto 可以省很多码。 { if((dis[it.nxt]==dis[now]+1\u0026amp;\u0026amp;path[it.nxt]\u0026gt;path[now]+!it.w)||dis[it.nxt]\u0026gt;dis[now]+1) { //更新 dis[it.nxt]=dis[now]+1; path[it.nxt]=path[now]+!it.w; pre[it.nxt]=now; q.emplace(dis[it.nxt],it.nxt); } } } } void Print(int x)//递归标记选出的路径 { vis[x]=1; if(pre[x]) Print(pre[x]); } int ans[100001][3],sum;//用来存答案 signed main() { ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); cin\u0026gt;\u0026gt;n\u0026gt;\u0026gt;m; for(int i=1,u,v,w;i\u0026lt;=m;i++)//无向图存边 cin\u0026gt;\u0026gt;u\u0026gt;\u0026gt;v\u0026gt;\u0026gt;w,a[u].emplace_back((Node){v,w}),a[v].emplace_back((Node){u,w}); Dijkstra(1); memset(vis,0,sizeof(vis)); Print(n); for(int i=1;i\u0026lt;=n;i++) { for(auto it:a[i]) { if((vis[i]\u0026amp;\u0026amp;vis[it.nxt]\u0026amp;\u0026amp;it.w==1)||(!(vis[i]\u0026amp;\u0026amp;vis[it.nxt])\u0026amp;\u0026amp;it.w==0))continue;//按题意模拟 else if(i\u0026lt;it.nxt)//无向图防止重复输出 { sum++; ans[++ans[0][0]][0]=i,ans[ans[0][0]][1]=it.nxt,ans[ans[0][0]][2]=!it.w;//记录答案，修复和炸毁和原先反一下，所以存 !it.w } } } cout\u0026lt;\u0026lt;sum\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;;//输出 for(int i=1;i\u0026lt;=ans[0][0];i++) cout\u0026lt;\u0026lt;ans[i][0]\u0026lt;\u0026lt;\u0026#39; \u0026#39;\u0026lt;\u0026lt;ans[i][1]\u0026lt;\u0026lt;\u0026#39; \u0026#39;\u0026lt;\u0026lt;ans[i][2]\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; return 0; } ","date":"19 February 2022","externalUrl":null,"permalink":"/acm/cf507e/","section":"题解和游记","summary":"","title":"CF507E Breaking Good题解","type":"acm"},{"content":" Updata: # 2022 . 2 . 16： SPJ 已加，本题解中 80 分的写法也能 AC。\n题目传送门\n思路： # 这题明显是区间 dp（ 做法和石子合并很像 ）。\n首先我们设 f[i][j] 表示从 i 到 j 区间合并后所能得到的中间和之和的最小代价，\n于是就是区间 dp 的模板：\n状态转移方程： f[i][j] = min(f[i][j],f[i][k] + f[k+1][j] + sum[j] - sum[i-1] ) # 其中 sum[i] 表示前缀和，也就是从 a[1] 到 a[i] 的和，\n所以 sum[j] - sum[i-1] 表示从 a[i] 到 a[j] 的和。\n初始状态： 自己和自己合并代价是 0，所以 f[i][j] = 0\n由于是求最小代价，所以\nmemset(f,0x3f,sizeof(f))//给 f 赋一个较大值\n输出部分： # 这题要注意的是它的输出 （输出调了我一晚上）\n我最开始是想在每个不同区间中找到最小时用 sl[l]++ 和 sr[r]++ 来存左右括号数，最后输出。 写法：\nint l,r; for(int len=2;len\u0026lt;=n;len++) { int Min=1e9; for(int i=1;i\u0026lt;=n-len+1;i++) { int j=len+i-1; if(f[i][j]\u0026lt;Min) l=i,r=j; } sl[l]++,sr[r]++; } for(int i=1;i\u0026lt;=n;i++) { for(int j=1;j\u0026lt;=sl[i];j++) cout\u0026lt;\u0026lt;\u0026#39;(\u0026#39;; cout\u0026lt;\u0026lt;a[i]; for(int j=1;j\u0026lt;=sr[i];j++) cout\u0026lt;\u0026lt;\u0026#39;)\u0026#39;; if(i!=n) cout\u0026lt;\u0026lt;\u0026#39;+\u0026#39;; }cout\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; 虽过了样例，然而 30pts 记录。\n于是百思不得其解的我拿了一位 dalao 的码对拍，立即就出错。。。\n输入：\n7 10 2 9 9 9 6 2 输出：\n//std.out: ((10+(2+9))+(9+(9+(6+2)))) 130 11 21 8 17 26 47 //bf.out: (10+(2+(9+(9+(9+(6+2)))))) 130 8 17 26 27 15 37 观察后发现 std 是递归输出（括号层数少），于是我改了一下： void print(int l,int r) { if(l==r) cout\u0026lt;\u0026lt;a[l]; else { cout\u0026lt;\u0026lt;\u0026#39;(\u0026#39;; print(l,num[l][r]);//num[i][j]是 i ，j 区间最小值的断点 cout\u0026lt;\u0026lt;\u0026#39;+\u0026#39;; print(num[l][r]+1,r); cout\u0026lt;\u0026lt;\u0026#39;)\u0026#39;; ans[++ans[0]]=sum[r]-sum[l-1];//这是存每个中间和 } } //求f[i][j]（写在主函数里） for(int len=2;len\u0026lt;=n;len++) for(int i=1;i\u0026lt;=n-len+1;i++) { int j=len+i-1; for(int k=i;k\u0026lt;=j-1;k++) if(f[i][k]+f[k+1][j]+sum[j]-sum[i-1]\u0026lt;f[i][j]) f[i][j]=f[i][k]+f[k+1][j]+sum[j]-sum[i-1],num[i][j]=k;//做标记num[i][j] } 于是 80pts 记录。（SPJ已加，现在已能过）\n最后在不断对拍后，发现 k 枚举的顺序也会影响 num[i][j] 的值，不过题目说从左到右也许是要标记靠右的 k， 所以终于 AC 了。\nCode：\n#include\u0026lt;bits/stdc++.h\u0026gt; using namespace std; int n; int num[21][21]; int ans[21]; int a[21],sum[21]; int f[21][21]; void print(int l,int r)//递归输出 { if(l==r) cout\u0026lt;\u0026lt;a[l]; else { cout\u0026lt;\u0026lt;\u0026#39;(\u0026#39;; print(l,num[l][r]); cout\u0026lt;\u0026lt;\u0026#39;+\u0026#39;; print(num[l][r]+1,r); cout\u0026lt;\u0026lt;\u0026#39;)\u0026#39;; ans[++ans[0]]=sum[r]-sum[l-1];//存中间和 } } int main() { ios::sync_with_stdio(0); memset(f,0x3f,sizeof(f));//初始化 cin\u0026gt;\u0026gt;n; for(int i=1;i\u0026lt;=n;i++) { cin\u0026gt;\u0026gt;a[i]; sum[i]=sum[i-1]+a[i];//前缀和 f[i][i]=0;//初始化 } for(int len=2;len\u0026lt;=n;len++)//区间dp写法 { for(int i=1;i\u0026lt;=n-len+1;i++) { int j=len+i-1; for(int k=j-1;k\u0026gt;=i;k--)//这里顺序改了改就过了。。。 { if(f[i][k]+f[k+1][j]+sum[j]-sum[i-1]\u0026lt;f[i][j]) f[i][j]=f[i][k]+f[k+1][j]+sum[j]-sum[i-1],num[i][j]=k; } } } print(1,n);cout\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; cout\u0026lt;\u0026lt;f[1][n]\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; for(int i=1;i\u0026lt;=ans[0];i++) cout\u0026lt;\u0026lt;ans[i]\u0026lt;\u0026lt;\u0026#34; \u0026#34;; cout\u0026lt;\u0026lt;\u0026#39;\\n\u0026#39;; return 0; } 谢谢各位 dalao 观赏。\nEnd\n","date":"19 February 2022","externalUrl":null,"permalink":"/acm/p2308/","section":"题解和游记","summary":"","title":"洛谷P2308 添加括号","type":"acm"}]