题解、Writeup、游记和碎碎念
本次第五空间有一个“人工智能”分类,但是里面却是一道 Crypto 题目。队友搜索到了 https://www.cryptool.org/assets/posts/2019-11-05-20-years-cryptool-looking-back-and-forward/CT20years_DeepLearningSpeck.pdf 这个用人工智能分析块密码的 slide,但是我看到本题觉得用普通的差分攻击就能做,于是没考虑人工智能相关的东西。
本题下发文件可以在 这里 下载。
task.py 中,实现了一个块密码,其中有一个 sbox,4 个 pbox,而明密文长度固定为 8 字节。main 函数每局会随机生成 sbox 和 pbox,给出这两个 box,在 1~4 中随机选择一个轮数,然后我们需要对他进行选择明文攻击,如果每局都解出某个随机密文对应的明文就赢了。
首先查看 encrypt 函数,主要部分如下:
for i in range(self.r):
    L, R = R, L ^ BlockCipher.F(self.sbox, self.pbox[i], R ^ self.subkeys[i])
这里面调用到的三个主要函数如下:
@staticmethod
def F(sbox,pbox,x):
    x = BlockCipher.S(sbox,x)
    x = BlockCipher.P(pbox,x)
    return x
@staticmethod
def S(sbox,x):
    B = [(x >> 24) & 0xff,(x >> 16) & 0xff,(x >> 8) & 0xff,x & 0xff]
    B = [sbox[i] for i in B]
    return (B[0] << 24) | (B[1] << 16) | (B[2] << 8) | B[3]
@staticmethod
def P(pbox,x):
    x = [int(i) for i in bin(x)[2:].rjust(32,"0")]
    result = 0
    for i in range(len(x)):
        if x[i] == 1:
            result |= 1 << pbox[i]
            return result
可以发现,S 函数是把每个字节过一遍 sbox,P 函数是把 32 bit 按 pbox[i] shuffle。
为方便起见,定义 为代码中对应的函数( 为使用的 pbox 编号,也即轮数),同时定义 , 同理。为了简化表述,把 记作 。
设初始时加密状态为 ,则一轮之后为 ,两轮之后为 。
可以解得 。( 也可直接解出)
令 。
假设我们用两组 得到了 ,那么 。
令 ,则 。
的每个字节均满足 ,可以枚举 的该字节,得到可能的 选择。
取三组 ,两两如上操作,即可将 求出,之后不难求出 和 。
令 ,则
假设我们取两组 得到 ,令
。
枚举 的某个字节 ,我们控制 和 仅在这个字节不同,则可以求出 ,令这个值为 ,则 。
现在问题是,如何知道 的的正确性。
我们可以再枚举 的某个字节 ,那么可以得到 。注意到这个表达式即为 ,于是 正确仅当 。
由于这不是充要条件,我们需要固定 ,多取几组 ,让解唯一。
由此可以得到 。我们可以用相同的办法处理 对 的约束,从而得到 。
最后
则直接反推即可。
这个方法共需要 次选择明文, 是前面取的 数量。之后的枚举次数是 ,非常快,不需要担心时限问题。
题目中每次的轮数是随机的,但是并没有告诉我们,于是我们需要保证,对于 轮,选择的明文包含了 轮的。
1~2 轮的攻击均只需要一条明密文对就能解出 ,但是 2 轮的攻击还需要一条来验证他确实是两轮。
3 轮的攻击共需要 3 条明密文对,可以再加一条用来验证。让 1~2 轮的攻击使用其中前两条即可。
4 轮的攻击完全不缺次数,于是就无所谓了。
Python 交互代码:
import string
from pwn import *
from ast import literal_eval
from hashlib import sha256
context.log_level = 'debug'
#r = process(['python', 't.py'])
r = remote('114.115.154.39', 9998)
r.recvuntil('XXXX+')
suffix = r.recv(16)
r.recvuntil('== ')
hs = bytes.fromhex(r.recv(64).decode())
chars = string.ascii_letters + string.digits
ans = None
for a in chars:
    if ans is not None:
        continue
    for b in chars:
        for c in chars:
            for d in chars:
                t = a + b + c + d
                if sha256(t.encode() + suffix).digest() == hs:
                    ans = t
r.sendline(ans)
for _ in range(4):
    r.recvuntil('[*] Challenge')
    r.recvline()
    r.recvuntil('[*] The sbox is : ')
    sbox = r.recvline()
    r.recvuntil('[*] The pbox is : ')
    pbox = r.recvline()
    open('box.txt', 'wb').write(sbox + pbox)
    sbox = literal_eval(sbox.decode())
    pbox = literal_eval(pbox.decode())
    r.recvuntil('[*] The randomCipher is : ')
    cipher = bytes.fromhex(r.recvline().decode())
    solver = process('./a')
    while True:
        a = solver.recvline().decode().split()
        if a[0] == 'keys':
            break
        a, b = int(a[0]), int(a[1])
        r.sendline((a.to_bytes(4, 'big') + b.to_bytes(4, 'big')).hex())
        r.recvuntil('[*] The cipher is : ')
        tc = bytes.fromhex(r.recvline().decode())
        solver.sendline('%d %d' % (int.from_bytes(tc[:4], 'big'), int.from_bytes(tc[4:], 'big')))
    solver.sendline('%d %d' % (int.from_bytes(cipher[:4], 'big'), int.from_bytes(cipher[4:], 'big')))
    a, b = map(int, solver.recvline().decode().split())
    r.sendline((a.to_bytes(4, 'big') + b.to_bytes(4, 'big')).hex())
r.interactive()
C++ 攻击代码:(用了 OI 板子,没有注释,建议对照前面观看(虽然变量名和前面讲解部分不同))
#include<bits/stdc++.h>
#ifdef __SIZEOF_INT128__
typedef __uint128_t ulll;typedef __int128_t lll;
#define Fr128 I Fr&OP,(lll&x){RX;if(f)x=-x;RT}I OP lll(){lll x;TR}I Fr&OP,(ulll&x){RU;RT}I OP ulll(){ulll x;TR}
#define Fw128 I Fw&OP,(lll x){WI(39,ulll);RT}I Fw&OP,(ulll x){WU(39);RT}
#else
#define Fr128
#define Fw128
#endif
#define xx first
#define yy second
#define mp(a,b)std::make_pair(a,b)
#define pb push_back
#define I __attribute__((always_inline))inline
#define mset(a,b)memset(a,b,sizeof(a))
#define mcpy(a,b)memcpy(a,b,sizeof(a))
#define fo0(i,n)for(int i=0,i##end=n;i<i##end;i++)
#define fo1(i,n)for(int i=1,i##end=n;i<=i##end;i++)
#define fo(i,a,b)for(int i=a,i##end=b;i<=i##end;i++)
#define fd0(i,n)for(int i=(n)-1;~i;i--)
#define fd1(i,n)for(int i=n;i;i--)
#define fd(i,a,b)for(int i=a,i##end=b;i>=i##end;i--)
#define foe(i,x)for(__typeof((x).end())i=(x).begin();i!=(x).end();++i)
#define fre(i,x)for(__typeof((x).rend())i=(x).rbegin();i!=(x).rend();++i)
#define OP operator
#define RT return*this;
#define RX x=0;char t=P();while((t<48||t>57)&&t!='-')t=P();bool f=0;if(t=='-')t=P(),f=1;x=t-48;for(t=P();t>=48&&t<=57;t\
=P())x=x*10+t-48
#define RL if(t=='.'){lf u=.1;for(t=P();t>=48&&t<=57;t=P(),u*=0.1)x+=u*(t-48);}if(f)x=-x
#define RU x=0;char t=P();while(t<48||t>57)t=P();x=t-48;for(t=P();t>=48&&t<=57;t=P())x=x*10+t-48
#define TR *this,x;return x;
#define WI(S,T)if(x){if(x<0){P('-'),x=-x;if(x<0){*this,(T)x;RT}}unsigned char s[S],c=0;while(x)s[c++]=x%10+48,x/=10;\
while(c--)P(s[c]);}else P(48)
#define WL if(y){lf t=0.5;for(int i=y;i--;)t*=0.1;if(x>=0)x+=t;else x-=t,P('-');*this,(ll)(abs(x));P('.');if(x<0)x=-x;\
while(y--){x*=10;x-=floor(x*0.1)*10;P(((int)x)%10+48);}}else if(x>=0)*this,(ll)(x+0.5);else*this,(ll)(x-0.5);
#define WU(S)if(x){char s[S],c=0;while(x)s[c++]=x%10+48,x/=10;while(c--)P(s[c]);}else P(48)
typedef unsigned int uint;typedef long long ll;typedef unsigned long long ull;typedef double lf;typedef long double llf;
typedef std::pair<int,int>pii;template<typename T>T max(T a,T b){return a>b?a:b;}template<typename T>T min(T a,T b){
return a<b?a:b;}template<typename T>T abs(T a){return a>0?a:-a;}template<typename T>T sqr(T x){return x*x;}template<
typename T>bool repr(T&a,T b){return a<b?a=b,1:0;}template<typename T>bool repl(T&a,T b){return a>b?a=b,1:0;}template<
typename T>T gcd(T a,T b){T t;if(a<b){while(a){t=a;a=b%a;b=t;}return b;}else{while(b){t=b;b=a%b;a=t;}return a;}}I bool
IS(char x){return x==10||x==13||x==' ';}template<typename T>struct Fr{T P;I Fr&OP,(int&x){RX;if(f)x=-x;RT}I OP int(){int
x;TR}I Fr&OP,(ll&x){RX;if(f)x=-x;RT}I OP ll(){ll x;TR}I Fr&OP,(char&x){for(x=P();IS(x);x=P());RT}I OP char(){char x;TR}I
Fr&OP,(char*x){char t=P();for(;IS(t)&&~t;t=P());if(~t){for(;!IS(t);t=P())*x++=t;}*x++=0;RT}I Fr&OP,(lf&x){RX;RL;RT}I OP
lf(){lf x;TR}I Fr&OP,(llf&x){RX;RL;RT}I OP llf(){llf x;TR}I Fr&OP,(uint&x){RU;RT}I OP uint(){uint x;TR}I Fr&OP,(ull&x){
RU;RT}I OP ull(){ull x;TR}void file(const char*x){P.file(x);}Fr128};struct Fwp{int p;};Fwp prec(int x){return(Fwp){x};}
template<typename T>struct Fw{T P;int p;I Fw&OP,(int x){WI(10,uint);RT}I Fw&OP,(uint x){WU(10);RT}I Fw&OP,(ll x){WI(19,
ull);RT}I Fw&OP,(ull x){WU(20);RT}I Fw&OP,(char x){P(x);RT}I Fw&OP,(const char*x){while(*x)P(*x++);RT}I Fw&OP,(const Fwp
&x){p=x.p;RT}I Fw&OP,(lf x){int y=p;WL;RT}I Fw&OP()(lf x,int y){WL;RT}I Fw&OP,(llf x){int y=p;WL;RT}I Fw&OP()(llf x,int
y){WL;RT}void file(const char*x){P.file(x);}void flush(){P.flush();}Fw128};
#ifdef LOCAL
struct Cg{I char operator()(){return getchar();}void file(const char*f){freopen(f,"r",stdin);}};struct Cp{I void
operator()(char x){putchar(x);}void file(const char*f){freopen(f,"w",stdout);}void flush(){fflush(stdout);}};struct Cpr{
I void operator()(char x){fputc(x,stderr);}void file(const char*f){freopen(f,"w",stderr);}void flush(){fflush(stderr);}}
;template<typename T>struct Fd{Fw<T>*o;template<typename P>I Fd&OP,(P x){(*o),x,' ';RT;}~Fd(){(*o),'\n';}};template<
typename T>struct Fds{Fw<T>*o;template<typename P>I Fd<T>OP,(P x){(*o),x,' ';return(Fd<T>){o};}};Fw<Cpr>err;Fds<Cpr>dbg{
&err};
#else
#define BSZ 131072
struct Cg{char t[BSZ+1],*o,*e;Cg(){e=o=t+BSZ;}I char operator()(){if(o==e)t[fread(o=t,1,BSZ,stdin)]=0;return*o++;}void
file(const char*f){freopen(f,"r",stdin);}};struct Cp{char t[BSZ+1],*o,*e;Cp(){e=(o=t)+BSZ;}I void operator()(char p){if(
o==e)fwrite(o=t,1,BSZ,stdout);*o++=p;}void file(const char*f){freopen(f,"w",stdout);}void flush(){fwrite(t,1,o-t,stdout)
,o=t,fflush(stdout);}~Cp(){fwrite(t,1,o-t,stdout);}};
#endif
Fr<Cg>in;Fw<Cp>out;
template<const char*fn>struct Cgf{
    FILE*f;
    Cgf(){f=fopen(fn,"r");}
    ~Cgf(){fclose(f);}
    char operator()(){return fgetc(f);}
};
typedef uint8_t u8;
struct cipher
{
    u8 sbox[256],rsbox[256],pbox[4][32];
    uint pbu[4][32],pbr[4][32];
    void set(u8 sb[256],u8 pb[4][32])
    {
        mcpy(sbox,sb),mcpy(pbox,pb);
        fo0(i,4)fo0(j,32)
        {
            pbu[i][31-j]=1u<<pbox[i][j];
            pbr[i][pbox[i][j]]=1u<<31-j;
        }
        fo0(i,256)rsbox[sbox[i]]=i;
    }
    template<const char*fn>void open()
    {
        Fr<Cgf<fn>>f;
        u8 sb[256],pb[4][32];
        fo0(i,256)sb[i]=(int)f;
        fo0(i,4)fo0(j,32)pb[i][j]=(int)f;
        set(sb,pb);
    }
    void ran(int seed)
    {
        std::mt19937 ran(seed);
        u8 sb[256],pb[4][32];
        fo0(i,256)sb[i]=i;
        std::shuffle(sb,sb+256,ran);
        fo0(i,4)
        {
            fo0(j,32)pb[i][j]=j;
            std::shuffle(pb[i],pb[i]+32,ran);
        }
        set(sb,pb);
    }
    uint S(uint x)const
    {
        return sbox[x>>24]<<24|sbox[x>>16&0xff]<<16|sbox[x>>8&0xff]<<8|sbox[x&0xff];
    }
    uint P(int rd,uint x)const
    {
        uint r=0;
        fo0(i,32)if(x>>i&1)r|=pbu[rd][i];
        return r;
    }
    uint F(uint rd,uint x)const
    {
        return P(rd,S(x));
    }
    uint RS(uint x)const
    {
        return rsbox[x>>24]<<24|rsbox[x>>16&0xff]<<16|rsbox[x>>8&0xff]<<8|rsbox[x&0xff];
    }
    uint RP(int rd,uint x)const
    {
        uint r=0;
        fo0(i,32)if(x>>i&1)r|=pbr[rd][i];
        return r;
    }
    uint RF(uint rd,uint x)const
    {
        return RS(RP(rd,x));
    }
    std::pair<uint,uint> encrypt(uint L,uint R,const uint*keys,int rounds)const
    {
        fo0(i,rounds)
        {
            uint t=L^F(i,R^keys[i]);
            L=R,R=t;
        }
        std::swap(L,R);
        return mp(L,R);
    }
    std::pair<uint,uint> decrypt(uint L,uint R,const uint*keys,int rounds)const
    {
        fd0(i,rounds)
        {
            uint t=L^F(i,R^keys[i]);
            L=R,R=t;
        }
        std::swap(L,R);
        return mp(L,R);
    }
};
typedef std::vector<std::pair<uint,uint>>ciphers;
typedef std::function<ciphers(ciphers)>encrypt_func;
//#define ERR(s) (printf("Error at %s: %s\n", __func__, s),0)
//#define OK (printf("Solved keys %s: %u %u %u %u\n", __func__, keys[0], keys[1], keys[2], keys[3]),1)
#define ERR(s) 0
#define OK 1
bool solve_1round(const cipher&c,encrypt_func f,uint*keys)
{
    ciphers a;
    a.pb(mp(0,0));
    ciphers b=f(a);
    for(auto&o:b)std::swap(o.xx,o.yy);
    if(b[0].xx)return ERR("not 1 round");
    keys[0]=c.RF(0,b[0].yy);
    fo1(i,3)keys[i]=0;
    return OK;
}
bool solve_2round(const cipher&c,encrypt_func f,uint*keys)
{
    ciphers a;
    a.pb(mp(0,0));
    a.pb(mp(0,0x1010101));
    ciphers b=f(a);
    auto u=b.back();
    for(auto&o:b)std::swap(o.xx,o.yy);
    keys[0]=c.RF(0,b[0].xx);
    keys[1]=c.RF(1,b[0].yy)^b[0].xx;
    fo(i,2,3)keys[i]=0;
    if(c.encrypt(a[1].xx,a[1].yy,keys,2)!=u)return ERR("not 2 round");
    return OK;
}
bool solve_3round(const cipher&c,encrypt_func f,uint*keys)
{
    ciphers a;
    a.pb(mp(0,0));
    a.pb(mp(0,0x1010101));
    a.pb(mp(1,0x2020202));
    ciphers b=f(a);
    for(auto&o:b)std::swap(o.xx,o.yy);
    uint e0=c.RF(1,b[0].xx^a[0].yy)^a[0].xx;
    uint e1=c.RF(1,b[1].xx^a[1].yy)^a[1].xx;
    uint e2=c.RF(1,b[2].xx^a[2].yy)^a[2].xx;
    uint sb1=c.RP(0,e0^e1),sb2=c.RP(0,e0^e2),key0=0;
    fo0(i,4)
    {
        int cnt=0;
        fo0(j,256)
        {
            bool ok1=(c.sbox[j]^c.sbox[j^1])==(sb1>>i*8&255);
            bool ok2=(c.sbox[j]^c.sbox[j^2])==(sb2>>i*8&255);
            if(ok1&&ok2)key0|=uint(j)<<i*8,cnt++;
        }
        if(!cnt)return ERR("no solution found");
        if(cnt>1)return ERR("solution not unique");
    }
    keys[0]=key0;
    keys[1]=c.F(0,key0)^e0;
    keys[2]=c.RF(2,b[0].yy^c.F(0,key0))^c.F(1,c.F(0,key0)^keys[1]);
    keys[3]=0;
    for(auto&o:b)std::swap(o.xx,o.yy);
    fo0(i,a.size())if(c.encrypt(a[i].xx,a[i].yy,keys,3)!=b[i])return ERR("not 3 round");
    return OK;
}
bool solve_4round(const cipher&c,encrypt_func f,uint*keys)
{
    const int C=3;
    ciphers a;
    a.pb(mp(0,0));
    a.pb(mp(0,0x1010101));
    a.pb(mp(1,0x2020202));
    fo0(i,4)fo1(j,C)a.pb(mp(j<<i*8,0));
    ciphers b=f(a);
    for(auto&o:b)std::swap(o.xx,o.yy);
    auto get=[&](uint x,uint y){
        fo0(i,a.size())if(a[i]==mp(x,y))return b[i];
        assert(0);
    };
    uint kt1=0,kt2=0;
    int yoc[4][256];
    mset(yoc,0);
    fo0(xp,4) // guess pos of key1
    {
        uint odiff[C];
        fo0(i,C)odiff[i]=c.RP(2,get(i+1<<xp*8,0).xx^(i+1<<xp*8)^b[0].xx);
        //fo0(i,C)out,odiff[i],' ';out,'\n';
        std::vector<int>xo;
        fo0(xv,256)
        {
            uint diff[C];
            fo0(i,C)diff[i]=c.F(1,xv<<xp*8)^c.F(1,(xv^i+1)<<xp*8);
            //fo0(i,C)out,diff[i],' ';out,'\n';
            int ok_mask=0;
            std::vector<pii>yu;
            fo0(yp,4) // guess pos of key2
            {
                fo0(yv,256)
                {
                    bool flag=1;
                    fo0(i,C)flag&=(c.sbox[yv]^c.sbox[yv^(diff[i]>>yp*8&255)])==(odiff[i]>>yp*8&255);
                    //if(yp&&flag)dbg,xp,xv,yp,yv;
                    if(flag)ok_mask|=1<<yp,yu.pb(mp(yp,yv));
                }
            }
            if(ok_mask==15)
            {
                for(pii&a:yu)yoc[a.xx][a.yy]++;
                xo.pb(xv);
            }
        }
        if(xo.size()==0)return ERR("no solution");
        if(xo.size()>1)return ERR("solution not unique");
        kt1|=uint(xo[0])<<xp*8;
    }
    fo0(i,4)
    {
        int c=0;
        fo0(j,256)if(yoc[i][j]==4)c++;
        if(c==0)return ERR("no solution");
        if(c>1)return ERR("solution not unique");
        fo0(j,256)if(yoc[i][j]==4)kt2|=uint(j)<<i*8;
    }
    keys[0]=c.RF(0,c.F(2,kt2)^b[0].xx);
    keys[1]=kt1^c.F(0,keys[0]);
    keys[2]=kt2^c.F(1,kt1);
    keys[3]=c.RF(3,c.F(1,kt1)^b[0].yy)^b[0].xx;
    for(auto&o:b)std::swap(o.xx,o.yy);
    fo0(i,a.size())if(c.encrypt(a[i].xx,a[i].yy,keys,4)!=b[i])return ERR("not 3 round");
    return OK;
}
void test_basic()
{
    cipher c;
    c.ran(1);
    std::mt19937_64 ran(2);
    uint keys[4];
    fo0(i,4)keys[i]=ran();
    fo0(_,10)
    {
        uint a=ran(),b=ran()&3;
        assert(c.RF(b,c.F(b,a))==a);
    }
    fo0(_,10)
    {
        uint a=ran(),b=ran(),n=ran()%4+1;
        auto x=c.encrypt(a,b,keys,n);
        auto y=c.decrypt(x.xx,x.yy,keys,n);
        assert(a==y.xx&&b==y.yy);
    }
}
void test_solve()
{
    cipher c;
    const int seed=11;
    c.ran(seed);
    std::mt19937_64 ran(seed+1);
    uint keys[4],rkeys[4];
    fo0(i,4)keys[i]=ran();
    auto ufunc=[&](const int rounds){
        return [c,keys,rounds](ciphers s)->ciphers{
            ciphers res;
            for(auto&o:s)res.pb(c.encrypt(o.xx,o.yy,keys,rounds));
            //out,c.S(s[0].yy^keys[0])^c.S(s[1].yy^keys[0]),'\n'; // round 3
            return res;
        };
    };
    fo0(_,10)
    {
        c.ran(seed+_);
        fo0(i,4)keys[i]=ran();
        assert(solve_1round(c,ufunc(1),rkeys)&&keys[0]==rkeys[0]);
    }
    fo0(_,10)
    {
        c.ran(seed+_);
        fo0(i,4)keys[i]=ran();
        assert(solve_2round(c,ufunc(2),rkeys)&&keys[0]==rkeys[0]&&keys[1]==rkeys[1]);
    }
    fo0(_,10)
    {
        c.ran(seed+_);
        fo0(i,4)keys[i]=ran();
        //fo0(i,4)out,keys[i],' ';out,'\n';
        assert(solve_3round(c,ufunc(3),rkeys)&&keys[0]==rkeys[0]&&keys[1]==rkeys[1]&&keys[2]==rkeys[2]);
    }
    fo0(_,10)
    {
        c.ran(seed+_);
        fo0(i,4)keys[i]=ran();
        //fo0(i,4)out,keys[i],' ';out,'\n';
        //out,'/',keys[1]^c.F(0,keys[0]),'\n';
        //out,'/',keys[2]^c.F(1,keys[1]^c.F(0,keys[0])),'\n';
        assert(solve_4round(c,ufunc(4),rkeys)&&keys[0]==rkeys[0]&&keys[1]==rkeys[1]&&keys[2]==rkeys[2]&&keys[3]==rkeys[3]);
    }
}
constexpr char boxfn[]="box.txt";
int main()
{
    //test_basic();
    //test_solve();
    cipher c;
    c.template open<boxfn>();
    std::map<std::pair<uint,uint>,std::pair<uint,uint>>known;
    auto chk=[&](std::function<bool(const cipher&,encrypt_func,uint*)>solve,int reqcnt,int rd){
        uint keys[4];
        bool ok=solve(c,[&](ciphers s){
            ciphers res;
            for(auto&o:s)
            {
                if(!known.count(o))
                {
                    out,o.xx,' ',o.yy,'\n';
                    out.flush();
                    uint a,b;in,a,b;
                    known[o]=mp(a,b);
                }
                res.pb(known[o]);
            }
            return res;
        },keys);
        if(!ok)return;
        fo0(i,reqcnt-known.size())
        {
            out,0,' ',0,'\n';
            out.flush();
            uint a,b;in,a,b;
        }
        out,"keys";
        fo0(i,4)out,' ',keys[i];
        out,'\n';
        out.flush();
        uint a,b;in,a,b;
        auto o=c.decrypt(a,b,keys,rd);
        out,o.xx,' ',o.yy,'\n';
        out.flush();
        exit(0);
    };
    chk(solve_1round,2,1);
    chk(solve_2round,2,2);
    chk(solve_3round,4,3);
    chk(solve_4round,52,4);
    assert(0);
}
日期: 2021-09-18