A+B · 当人类群星闪耀时

谨以此文献给所有在 A+B 问题上"过度设计"的先驱们


序章 · 一道题,万种解法

在无数 OJ 的角落里,有一道题,它叫 P1001 A+B Problem

它只有一行需求:输入两个数,输出它们的和。

勇士们拿起算法的武器,向一座看似坚固的"简单"堡垒发起了冲锋。

这是一部人类群星闪耀史。


第一章 · 数据结构篇

LCT · 情感的波折

有人说,程序是冰冷的。LCT 哥不同意。

他建了两个节点,让它们相识——connect(A, B)
然后分手——cut(A, B)
最后复合——connect(A, B)

在经历了完整的情感波折后,他查询了它们的"爱情结晶"——路径和。

两百行代码,只为一个浪漫的加法。

"动态树:我用 200 行代码,证明了我能算 1+1。"

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cstring>
using namespace std;
struct node 
{
    int data,rev,sum;
    node *son[2],*pre;
    bool judge();
    bool isroot();
    void pushdown();
    void update();
    void setson(node *child,int lr);
}lct[233];
int top,a,b;
node *getnew(int x)
{
    node *now=lct+ ++top;
    now->data=x;
    now->pre=now->son[1]=now->son[0]=lct;
    now->sum=0;
    now->rev=0;
    return now;
}
bool node::judge(){return pre->son[1]==this;}
bool node::isroot()
{
    if(pre==lct)return true;
    return !(pre->son[1]==this||pre->son[0]==this);
}
void node::pushdown()
{
    if(this==lct||!rev)return;
    swap(son[0],son[1]);
    son[0]->rev^=1;
    son[1]->rev^=1;
    rev=0;
}
void node::update(){sum=son[1]->sum+son[0]->sum+data;}
void node::setson(node *child,int lr)
{
    this->pushdown();
    child->pre=this;
    son[lr]=child;
    this->update();
}
void rotate(node *now)
{
    node *father=now->pre,*grandfa=father->pre;
    if(!father->isroot()) grandfa->pushdown();
    father->pushdown();now->pushdown();
    int lr=now->judge();
    father->setson(now->son[lr^1],lr);
    if(father->isroot()) now->pre=grandfa;
    else grandfa->setson(now,father->judge());
    now->setson(father,lr^1);
    father->update();now->update();
    if(grandfa!=lct) grandfa->update();
}
void splay(node *now)
{
    if(now->isroot())return;
    for(;!now->isroot();rotate(now))
    if(!now->pre->isroot())
    now->judge()==now->pre->judge()?rotate(now->pre):rotate(now);
}
node *access(node *now)
{
    node *last=lct;
    for(;now!=lct;last=now,now=now->pre)
    {
        splay(now);
        now->setson(last,1);
    }
    return last;
}
void changeroot(node *now)
{
    access(now)->rev^=1;
    splay(now);
}
void connect(node *x,node *y)
{
    changeroot(x);
    x->pre=y;
    access(x);
}
void cut(node *x,node *y)
{
    changeroot(x);
    access(y);
    splay(x);
    x->pushdown();
    x->son[1]=y->pre=lct;
    x->update();
}
int query(node *x,node *y)
{
    changeroot(x);
    node *now=access(y);
    return now->sum;
}
int main()
{
    scanf("%d%d",&a,&b);
    node *A=getnew(a);
    node *B=getnew(b);
        connect(A,B);
        cut(A,B);
        connect(A,B);
    printf("%d\n",query(A,B)); 
    return 0;
}

线段树 · 集装箱里的芝麻

线段树哥说:"我的代码要有工业级的冗余。"

于是他开了一棵 4×1e5 的线段树,建在只有 1 个元素的数组上。

他用标记永久化做区间加,虽然只加了一次。

他传了 tags 参数累加路径标记,虽然路径只有一层。

50 倍的常数,只为 100 分的尊严。

"用集装箱存一颗芝麻,然后骄傲地说:看,我的芝麻有专属仓库!"

#include <cstdio>
#define mid L + (R-L >> 1)
const int maxn = 1e5+5;
int n, a[maxn], m;
int sl, sr, add;
struct segtree{
    int sum[maxn<<2], tag[maxn<<2];
    inline int lc(int o){return o<<1;}
    inline int rc(int o){return o<<1|1;}
    void build(int o, int L, int R){
        if(L == R){sum[o] = a[L];return;}
        int M = mid;
        build(lc(o), L, M);
        build(rc(o), M+1, R);
        sum[o] = sum[lc(o)] + sum[rc(o)];
    }
    void maintain(int o, int L, int R){
        if(R>L){
            sum[o] = sum[lc(o)] + sum[rc(o)];
            sum[o] += tag[o] * (R-L+1);
        } else {
            sum[o] += tag[o];
            tag[o] = 0;
        }
    }
    void updata(int o, int L, int R){
        if(sl <= L && R <= sr)tag[o] += add;
        else{
            int M = mid;
            if(sl <= M)updata(lc(o), L, M);
            if(sr > M)updata(rc(o), M+1, R);
        }
        maintain(o, L, R);
    }
    int query(int o, int L, int R, int tags){
        if(sl <= L && R <= sr)return sum[o] + tags * (R-L+1);
        else {
            int M = mid, res = 0;
            if(sl <= M)res += query(lc(o), L, M, tags+tag[o]);
            if(sr > M)res += query(rc(o), M+1, R, tags+tag[o]);
            return res;
        }
    }
} sol;
signed main(){
    n = 1; 
    int a, b; 
    scanf("%d%d", &a, &b);
    sol.build(1, 1, n);
    add=a; sl=1; sr=1;
    sol.updata(1, 1, 1);
    add=b;
    sol.updata(1, 1, 1);
    printf("%d\n", sol.query(1, 1, n, 0));
    return 0;
}

树状数组 · 五十万格储物柜

树状数组哥更狠。

他开了 500005 个空间,用 lowbit 维护 1 个数的前缀和。

只有 1 个数,但数组要开到 50 万。

空着 499999 个格子,是一种态度。

"邻居问:你家储物柜为什么空着 499999 格?"

#include <iostream>
using namespace std;
const int n = 1;
int a, b;
int c[500005];
inline int lowbit(int x){
    return x & (-x);
}
inline int sum(int x){
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))
    ans+=c[i];
    return ans;
}
void add(int x,int y){
    for(int i=x;i<=n;i+=lowbit(i))
    c[i]+=y;
}
int main(){
    cin>>a>>b;
    add(1, a); add(1, b);
    printf("%d\n", sum(1));
    return 0;
}

Splay · 翻转的哲学

Splay 哥说:"加法满足交换律。"

然后他用 100 行代码实现了 Splay,建了 4 个节点。

把序列 [a, b] 翻转成 [b, a]

最后查询区间和——a+b 还是 a+b

他证明了:翻转不影响结果,正如爱不影响加法。

"翻转序列是为了证明加法交换律,数学老师哭了。"

#include <bits/stdc++.h>
#define ll long long
#define N 100000
using namespace std;
int sz[N], rev[N], tag[N], sum[N], ch[N][2], fa[N], val[N];
int n, m, rt, x;
void push_up(int x){
    sz[x] = sz[ch[x][0]] + sz[ch[x][1]] + 1;
    sum[x] = sum[ch[x][1]] + sum[ch[x][0]] + val[x];
}
void push_down(int x){
    if(rev[x]){
        swap(ch[x][0], ch[x][1]);
        if(ch[x][1]) rev[ch[x][1]] ^= 1;
        if(ch[x][0]) rev[ch[x][0]] ^= 1;
        rev[x] = 0;
    }
    if(tag[x]){
        if(ch[x][1]) tag[ch[x][1]] += tag[x], sum[ch[x][1]] += tag[x];
        if(ch[x][0]) tag[ch[x][0]] += tag[x], sum[ch[x][0]] += tag[x];
        tag[x] = 0;
    }
}
void rotate(int x, int &k){
    int y = fa[x], z = fa[fa[x]];
    int kind = ch[y][1] == x;
    if(y == k) k = x;
    else ch[z][ch[z][1]==y] = x;
    fa[x] = z; fa[y] = x; fa[ch[x][!kind]] = y;
    ch[y][kind] = ch[x][!kind]; ch[x][!kind] = y;
    push_up(y); push_up(x);
}
void splay(int x, int &k){
    while(x != k){
        int y = fa[x], z = fa[fa[x]];
        if(y != k) if(ch[y][1] == x ^ ch[z][1] == y) rotate(x, k);
        else rotate(y, k);
        rotate(x, k);
    }
}
int kth(int x, int k){
    push_down(x);
    int r = sz[ch[x][0]]+1;
    if(k == r) return x;
    if(k < r) return kth(ch[x][0], k);
    else return kth(ch[x][1], k-r);
}
void split(int l, int r){
    int x = kth(rt, l), y = kth(rt, r+2);
    splay(x, rt); splay(y, ch[rt][1]);
}
void rever(int l, int r){
    split(l, r);
    rev[ch[ch[rt][1]][0]] ^= 1;
}
void add(int l, int r, int v){
    split(l, r);
    tag[ch[ch[rt][1]][0]] += v;
    val[ch[ch[rt][1]][0]] += v;
    push_up(ch[ch[rt][1]][0]);
}
int build(int l, int r, int f){
    if(l > r) return 0;
    if(l == r){
        fa[l] = f;
        sz[l] = 1;
        return l;
    }
    int mid = l + r >> 1;
    ch[mid][0] = build(l, mid-1, mid);
    ch[mid][1] = build(mid+1, r, mid);
    fa[mid] = f;
    push_up(mid);
    return mid;
}
int asksum(int l, int r){
    split(l, r);
    return sum[ch[ch[rt][1]][0]];
}
int main(){
    n = 2;
    rt = build(1, n+2, 0);
    for(int i = 1; i <= n; i++){
        scanf("%d", &x);
        add(i, i, x);
    }
    rever(1, n);
    printf("%d\n", asksum(1, n));
    return 0;
}

Treap · 把 a 拆成 a 个 1

Treap 哥说:"插入 a 个 1,插入 b 个 1,然后查 1 的排名。"

于是循环跑了 a+b 次,插了 200 万个 1。

树高 20,旋转 4000 万次,CPU 风扇狂转。

最后输出 2000000。

"算个加法用了 4000 万次旋转,隔壁做渲染的以为我在跑 3D 建模。"

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
inline int read(){int a = 0, f = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') f = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9'){a = a * 10 + ch - '0'; ch = getchar(); }
    return a * f;}
inline void write(int x){if(x < 0) putchar('-'), x = -x;
    if(x > 9) write(x / 10); putchar(x % 10 + '0'); }
const int N = 1e5 + 10, INF = 0x3f3f3f3f;
int n;
struct Treap{
    int ls, rs;
    int val, rnd;
    int siz, cnt;
}tr[N];
int tot = 0;
inline void Updata(int x){ tr[x].siz = tr[tr[x].ls].siz
    + tr[tr[x].rs].siz + tr[x].cnt; }
inline int New(int x){
    tr[++tot].val = x;
    tr[tot].rnd = rand();
    tr[tot].siz = tr[tot].cnt = 1;
    return tot;
}
int root = 0;
inline void Build(){
    New(-INF), New(INF);
    root = 1;
    tr[root].rs = 2;
    Updata(root);
}
inline void zig(int &y){
    int x = tr[y].ls;
    tr[y].ls = tr[x].rs;
    tr[x].rs = y;
    y = x;
    Updata(tr[y].rs);
    Updata(y);
}
inline void zag(int &y){
    int x = tr[y].rs;
    tr[y].rs = tr[x].ls;
    tr[x].ls = y;
    y = x;
    Updata(tr[y].ls);
    Updata(y);
}
inline void Insert(int &x, int val){
    if(!x){
        x = New(val);
        return ;
    }
    if(tr[x].val == val){
        ++tr[x].cnt;
        Updata(x);
        return ;
    }
    if(val < tr[x].val){
        Insert(tr[x].ls, val);
        if(tr[x].rnd < tr[tr[x].ls].rnd) zig(x);
    }
    else{
        Insert(tr[x].rs, val);
        if(tr[x].rnd < tr[tr[x].rs].rnd) zag(x);
    }
    Updata(x);
}
inline void Delete(int &x, int val){
    if(!x) return ;
    if(tr[x].val == val){
        if(tr[x].cnt > 1){
            --tr[x].cnt;
            Updata(x);
            return ;
        }
        if(!tr[x].ls && !tr[x].rs){
            x = 0;
            return ;
        }
        else if(!tr[x].rs || (tr[x].ls && tr[tr[x].ls].rnd > tr[tr[x].rs].rnd)){
            zig(x);
            Delete(tr[x].rs, val);
        }
        else{
            zag(x);
            Delete(tr[x].ls, val);
        }
    }
    else if(val < tr[x].val) Delete(tr[x].ls, val);
    else Delete(tr[x].rs, val);
    Updata(x);
}
inline int get_rank(int x, int val){
    if(!x) return 0;
    if(tr[x].val == val) return tr[tr[x].ls].siz + tr[x].cnt;
    if(val < tr[x].val) return get_rank(tr[x].ls, val);
    return get_rank(tr[x].rs, val) + tr[tr[x].ls].siz + tr[x].cnt;
}
inline int get_val(int x, int rank){
    if(!x) return INF;
    if(tr[tr[x].ls].siz >= rank) return get_val(tr[x].ls, rank);
    if(tr[tr[x].ls].siz + tr[x].cnt >= rank) return tr[x].val;
    return get_val(tr[x].rs, rank - tr[tr[x].ls].siz - tr[x].cnt);
}
inline int get_pre(int x, int val){
    int ans = 1;
    while(x){
        if(val == tr[x].val){
            if(tr[x].ls > 0){
                x = tr[x].ls;
                while(tr[x].rs > 0) x = tr[x].rs;
                ans = x;
            }
            break;
        }
        if(val > tr[x].val && tr[x].val > tr[ans].val) ans = x;
        x = val < tr[x].val ? tr[x].ls : tr[x].rs;
    }
    return tr[ans].val;
}
inline int get_last(int x, int val){
    int ans = 2;
    while(x){
        if(val == tr[x].val){
            if(tr[x].rs > 0){
                x = tr[x].rs;
                while(tr[x].ls > 0) x = tr[x].ls;
                ans = x;
            }
            break;
        }
        if(val < tr[x].val && tr[x].val < tr[ans].val) ans = x;
        x = val < tr[x].val ? tr[x].ls : tr[x].rs;
    }
    return tr[ans].val;
}
int main(){
    srand(time(0));
    int a = read(), b = read();
    for(int i = 1; i <= a; i++) {
        Insert(root, 1);
    }
    for(int i = 1; i <= b; i++) {
        Insert(root, 1);
    }
    write(get_rank(root, 1));
    return 0;
}

第二章 · 图论篇

Dijkstra · 导航软件的噩梦

Dijkstra 哥建了一张图:0→1(边权 a),1→2(边权 b)。

然后跑了一遍最短路算法:松弛、入队、出队、再松弛。

最后郑重宣布:从 0 到 2 的最短路径是 a+b。

导航软件听了一下自己的光荣事迹,默默把自己卸载了。

"导航软件:我这就下岗?"

#include<bits/stdc++.h>
#define int unsigned long long
#define code using
#define by namespace
#define XWC std;
#define f first
code by XWC
const int N=1e5+10;
struct edge{
    int v,w;
};
struct node{
    int dis,u;
    bool operator>(const node& a) const {return dis>a.dis;}
};
vector<edge> e[N];
int dis[N],vis[N];
int u,v,w;
int qq[10];
priority_queue<node,vector<node>,greater<node> > q;
void Dijkstra(int n,int s){
    for(int i=0;i<=n;i++) dis[i]=8e18+10;
    for(int i=0;i<=n;i++) vis[i]=0;
    dis[s]=0;
    while(!q.empty()) q.pop();
    q.push({0,s});
    while(!q.empty()){
        int u=q.top().u;
        q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(auto ed:e[u]){
            int v=ed.v,w=ed.w;
            if(dis[v]>dis[u]+w){
                dis[v]=dis[u]+w;
                q.push({dis[v],v});
            }
        }
    }
}
int n=2,m=1,a,b;
signed main(){
    n=2,m=1;
    cin>>a>>b;
    e[n/2].push_back({n,a+b});
    Dijkstra(n,1);
    cout<<dis[n];
    return 0; 
}

Kruskal · INF 的尊严

Kruskal 哥更离谱。

他建了三条边:a、b、INF。

然后跑了最小生成树——选了 a 和 b,INF 被冷落。

那条 INF 边,从出生就注定不被选择。

"边:我不要面子的吗?"

#include <cstdio>
#include <algorithm>
#define INF 2140000000
using namespace std;
struct tree{int x,y,t;}a[10];
bool cmp(const tree&a,const tree&b){return a.t<b.t;}
int f[11],i,j,k,n,m,x,y,t,ans;
int root(int x){if (f[x]==x) return x;f[x]=root(f[x]);return f[x];}
int main(){
    for (i=1;i<=10;i++) f[i]=i;
    for (i=1;i<=2;i++){
        scanf("%d",&a[i].t);
        a[i].x=i+1;a[i].y=1;k++;
    }
    a[++k].x=1;a[k].y=3,a[k].t=INF;
    sort(a+1,a+1+k,cmp);
    for (i=1;i<=k;i++){
        x=root(a[i].x);y=root(a[i].y);
        if (x!=y) f[x]=y,ans+=a[i].t; 
    }
    printf("%d\n",ans);
}

第三章 · 数学篇

完全平方 · 高精度的浪漫

完全平方哥说:"我们知道 (a+b)² = a² + b² + 2ab。"

所以他写了 150 行高精度:加法、减法、乘法、除法、开根号、比较大小。

然后算 1²+2²+2×1×2=9,开根号得 3。

数学老师看了直摇头:"公式不是这么用的。"

"少年,公式是让你算展开,不是让你绕路的。"

#include <bits/stdc++.h>
using namespace std;
string qf (string a)
{
    if (a[0] == '-') return a.substr (1 , a.size () - 1);
    return "-" + a;
}
string operator - (string a) {return qf (a);}
string gjc (string a , string b)
{
    if (a[0] == '-' && b[0] == '-') return gjc (- a , - b);
    else if (a[0] == '-') return - gjc (- a , b);
    else if (b[0] == '-') return - gjc (a , - b);
    int ans[a.size () + b.size ()] = {};
    reverse (a.begin () , a.end ());
    reverse (b.begin () , b.end ());
    for (int i = 0;i < a.size ();i ++)
        for (int j = 0;j < b.size ();j ++)
            ans[i + j] += (a[i] - '0') * (b[j] - '0');
    for (int i = 0;i < a.size () + b.size ();i ++)
        if (ans[i] > 9)
        {
            int x = ans[i] % 10 , y = ans[i] / 10;
            ans[i] = x;
            ans[i + 1] += y;
        }
    string ans2 = "";
    for (int i = 1;i <= a.size () + b.size ();i ++)
        ans2 += (char) (ans[i - 1] + '0');
    if (ans2[ans2.size () - 1] == '0')
        ans2 = ans2.substr (0 , ans2.size () - 1);
    reverse (ans2.begin () , ans2.end ());
    return ans2;
}
string max (string a , string b)
{
    if (a[0] == '-' && b[0] == '-')
    {
        if (- max (- a , - b) == a) return b;
        return a;
    }
    else if (a[0] == '-') return b;
    else if (b[0] == '-') return a;
    else if (a.size () > b.size ()) return a;
    else if (a.size () < b.size ()) return b;
    else
    {
        for (int i = 0;i < a.size ();i ++)
            if (a[i] > b[i]) return a;
            else if (a[i] < b[i]) return b;
        return a;
    }
}
string min (string a , string b)
{
    if (max (a , b) == a) return b;
    return a;
}
string gjj (string a , string b);
string cut (string a , string b)
{
    if (a[0] == '-' && b[0] == '-') return cut (- b , - a);
    else if (a[0] == '-') return - gjj (- a , b);
    else if (b[0] == '-') return gjj (b , - a);
    else if (max (a , b) != a) return - cut (b , a);
    reverse (a.begin () , a.end ());
    reverse (b.begin () , b.end ());
    string ans = "";
    for (int i = 1;i <= a.size () + 1;i ++) ans += "0";
    for (int i = 0;i < b.size ();i ++) ans[i] += a[i] - b[i];
    for (int i = b.size ();i < a.size ();i ++) ans[i] = a[i];
    for (int i = 0;i < ans.size ();i ++)
        if (ans[i] < '0')
        {
            ans[i] += 10;
            ans[i + 1] -= 1;
        }
    while (ans.size () > 1 && ans[ans.size () - 1] == '0')
        ans = ans.substr (0 , ans.size () - 1);
    reverse (ans.begin () , ans.end ());
    return ans;
}
string gjj (string a , string b)
{
    if (a[0] == '-' && b[0] == '-') return - gjc (- a , - b);
    else if (a[0] == '-') return cut (b , - a);
    else if (b[0] == '-') return cut (a , - b);
    if (a.size () < b.size ()) swap (a , b);
    reverse (a.begin () , a.end ());
    reverse (b.begin () , b.end ());
    string ans = "";
    for (int i = 1;i <= a.size () + 1;i ++) ans += "0";
    for (int i = 0;i < b.size ();i ++) ans[i] += a[i] - '0' + b[i] - '0';
    for (int i = b.size ();i < a.size ();i ++) ans[i] = a[i];
    for (int i = 0;i < ans.size ();i ++)
        if (ans[i] > '9')
        {
            ans[i] -= 10;
            ans[i + 1] += 1;
        }
    if (ans[ans.size () - 1] == '0') ans = ans.substr (0 , ans.size () - 1);
    reverse (ans.begin () , ans.end ());
    return ans;
}
string dev (string b , int a)
{
    if (a < 0 && b[0] == '-') return dev (- b , - a);
    else if (a < 0) return - dev (b , - a);
    else if (b[0] == '-') return - dev (- b , a);
    while (a % 10 == 0 && b[b.size () - 1] == '0')
    {
        a /= 10;
        b = b.substr (0 , b.size () - 1);
    }
    string ans = "";
    int yu = 0;
    for (int i = 0;i < b.size ();i ++)
    {
        yu = yu * 10 + b[i] - '0';
        ans += yu / a + '0';
        yu %= a;
    }
    while (ans.size () > 1 && ans[0] == '0') ans = ans.substr (1);
    return ans;
}
string operator / (string a , int b) {return dev (a , b);}
string operator * (string a , string b) {return gjc (a , b);}
string operator + (string a , string b) {return gjj (a , b);}
string operator - (string a , string b) {return cut (a , b);}
string sqrt (string s)
{
    if (s == "0") return "0";
    if (s == "1") return "1";
    string l = "1" , r = "1" , ans , dan = "1";
    for (int i = 1;i < s.size () / 2;i ++) l += "0";
    for (int i = 1;i <= s.size () / 2 + 1;i ++) r += "0";
    while (max (l , r) == r)
    {
        string mid = (l + r) / 2;
        if (max (mid * mid , s) == s)
        {
            ans = mid;
            l = gjj (mid , dan);
        }
        else r = mid - dan;
    }
    return ans;
}
signed main ()
{
    string a , b , c = "2";
    cin >> a >> b;
    if (a[0] == '-' && b[0] == '-')
        cout << - sqrt (a * a + b * b + c * a * b);
    else if (a[0] == '-')
    {
        if (max (- a , b) == b) cout << sqrt (a * a + b * b + c * a * b);
        else cout << - sqrt (a * a + b * b + c * a * b);
    }
    else if (b[0] == '-')
    {
        if (max (- b , a) == a) cout << sqrt (a * a + b * b + c * a * b);
        else cout << - sqrt (a * a + b * b + c * a * b);
    }
    else cout << sqrt (a * a + b * b + c * a * b);
}

第四章 · 搜索篇

二分查找 · 偷看答案的演技

二分哥写了一个二分查找。

[-1e9, 1e9] 里猜一个数,猜 31 次。

每一次循环都做一次比较:if(mid == a+b)

他明明已经算了 a+b,却假装不知道。

"你考试时偷看了答案,然后假装自己算出来的。"

#include<bits/stdc++.h>
#define N 100010
using namespace std;
int a,b;
int s(int a,int b) {
    int l=-1e9,r=1e9,mid;
    while(l<r) {
        mid=(l+r)/2;
        if(mid==a+b) return mid;
        else if(mid>a+b) r=mid;
        else l=mid;
    }
}
int main() {
    cin>>a>>b;
    cout<<s(a,b);
    return 0;
}

模拟退火 · 物理模拟的误会

模拟退火哥把初始温度设为 100 万,降温系数 0.999,跑了 100 轮。

每轮 10 万次迭代,总共 1000 万次随机。

温度从 100 万降到 1e-10,终于收敛到了 3。

隔壁做物理模拟的过来围观:"兄弟你在模拟什么?"

"我在算 1+2。"

他走了。

"物理系同学:我们模拟分子运动,你们模拟什么?算加法。"

#include<bits/stdc++.h> 
#define ll long long
using namespace std;
const double d=0.999;
const double lim=1e-10;
ll a,b;
ll ans;
ll num;
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while(ch<'0'||ch>'9')
    {
        if(ch=='-') f=-1;
        ch=getchar();
    }
    while(ch>='0'&&ch<='9')
    {
        x=x*10+(ch^48);
        ch=getchar();
    }
    return x*f;
}
int calc(int x)
{
    return abs(a+b-x)-abs(a+b-ans);
}
void ghost_fire()
{
    double T=1000000;
    while(T>lim)
    {
        int x=num+((rand()<<1)-RAND_MAX)*T;
        int del=calc(x);
        if(del<0)
        {
            ans=x;
            num=x;
        }
        else if(exp(-del/T)>(double)rand()/RAND_MAX) num=x;
        T*=d;
    }
}
void work()
{
    for(int i=1;i<=100;i++) ghost_fire();
}
int main()
{
    a=read();
    b=read();
    work();
    cout<<ans<<endl;
    return 0;
}

第五章 · 底层篇

虚拟机 · 为了一杯牛奶养了一头牛

虚拟机哥定义了一套 5 条指令的 ISA:LOAD、ADD、STORE、PRINT、HALT。

然后写了一个 100 行的解释器。

然后写了 6 条汇编指令。

然后在虚拟机上运行。

然后得到了 3。

"为了算 1+2,我设计了一台计算机,定义了指令集,写了汇编程序,跑在虚拟机上……别人问为什么不用计算器?因为我的虚拟机没有计算器功能。"

#include <iostream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>
enum class OpCode { LOAD, ADD, STORE, PRINT, HALT };
struct Instruction { OpCode op; int operand1; int operand2; };
class VirtualMachine {
    std::vector<int> memory;
    std::vector<int> registers;
    size_t pc;
    bool running;
public:
    VirtualMachine(size_t memSize, size_t regCount) 
        : memory(memSize, 0), registers(regCount, 0), pc(0), running(false) {}
    void execute(const std::vector<Instruction>& program) {
        running = true;
        while (running && pc < program.size()) {
            const auto& instr = program[pc];
            switch (instr.op) {
                case OpCode::LOAD: registers[instr.operand1] =
                instr.operand2; pc++; break;
                case OpCode::ADD: registers[instr.operand1] += 
                registers[instr.operand2]; pc++; break;
                case OpCode::STORE: memory[instr.operand1] = 
                 registers[instr.operand2]; pc++; break;
                case OpCode::PRINT:
                std::cout << memory[instr.operand1] << std::endl;
                pc++; break;
                case OpCode::HALT: running = false; break;
                default: throw std::runtime_error("Unknown opcode");
            }
        }
    }
};
int main() {
    try {
        int a, b;
        std::cin >> a >> b;
        VirtualMachine vm(16, 4);
        std::vector<Instruction> program = {
            {OpCode::LOAD, 0, a},
            {OpCode::LOAD, 1, b},
            {OpCode::ADD, 0, 1},
            {OpCode::STORE, 0, 0},
            {OpCode::PRINT, 0, 0},
            {OpCode::HALT, 0, 0}
        };
        vm.execute(program);
        return 0;
    } catch (const std::exception& e) { return 1; }
}

第六章 · 语言篇

中文宏 · 编译器的翻译

中文宏哥#define 把 C++ 变成了中文:

编译器把中文翻译回英文,然后悄悄编译通过。

"编译器:我翻译了一下,你就以为自己写的是中文编程了?"

#include <bits/stdc++.h>
using namespace std;
#define 开始啦 main()
#define 开头 {
#define 结尾 }
#define 和 , 
#define 没了 return 0
#define 整数32位 int
#define 输入 cin
#define 输出 cout 
#define 一个 >> 
#define 这个 <<
#define 加上 +
#define 换行 '\n' 
整数32位 开始啦
开头
    整数32位 a 和 b;
    输入 一个 a 一个 b;
    输出 这个 a 加上 b 这个 换行; 
    没了;
结尾

文言文 · 三脸懵逼

文言文哥wenyan-lang 写了 A+B:

历史老师以为他在写《史记》,语文老师以为他在写古诗,编译器说:

"我只能翻译成 JavaScript。"

"三脸懵逼:历史、语文、编译器。"

施「require('fs').readFileSync」於「「/dev/stdin」」。名之曰「數據」。
施「(buf => buf.toString().trim())」於「數據」。昔之「數據」者。今其是矣。
施「(s => s.split(' '))」於「數據」。昔之「數據」者。今其是矣。
注曰。「「文言尚菜,無對象之操作,故需 JavaScript 之语法」」。
夫「數據」之一。取一以施「parseInt」。名之曰「甲」。
夫「數據」之二。取一以施「parseInt」。名之曰「乙」。
加「甲」以「乙」。書之。

终章 · 群星闪耀时

这些人,有的写了 200 行,有的写了 1000 万次迭代,有的定义了一台虚拟机。

他们本可以写一行简单的加法,但他们没有。

他们把简单的问题复杂化,把复杂的代码优雅化,把优雅的算法浪漫化。

他们不是在算 A+B。

他们是在向世界证明:

程序员的浪漫,就是把一道入门题,写成一部史诗。


宇宙全家福

流派 代表作 全体致敬
LCT 动态树算加法 相识->分手->复合,比偶像剧还曲折
线段树 标记永久化 用集装箱存一颗芝麻
树状数组 50万空间存1个数 储物柜空着499999格
Splay 序列翻转 翻转证明交换律,数学老师哭了
Treap 插200万个1 CPU风扇转得比你脑子快
Dijkstra 三条边跑最短路 导航软件:我这就下岗?
Kruskal INF边强行加戏 边:我不要面子的吗?
完全平方 高精度开根 数学老师:公式不是这么用的
二分查找 猜答案 偷看答案假装自己算的
模拟退火 1000万次随机 物理系同学:你在模拟啥?算1+2
虚拟机 手搓ISA 为了喝牛奶养了一头牛
中文宏 #define 中文 编译器:我翻译了一下
文言文 史记风代码 历史/语文/编译器三脸懵逼

后记

某日,一位萌新点开 A+B 的题解区。

他看到了 LCT、Splay、模拟退火、虚拟机、文言文……

他沉默了。

然后他写下了人生中第一行代码:

cout << a + b;

他 AC 了。

他在那一刻突然明白:

所有的群星,都是在为最简单的真理闪烁。

而最简单的真理,往往只需要一行代码。


THE END