• 2022 年牛客多校第八场补题记录


    A Puzzle: X-Sums Sudoku

    题意:考虑一宫大小为 2 n × 2 m 2^n\times 2^m 2n×2m 的方形数独, 求横排字典序最小( 4 × 2 4\times 2 4×2 的数独如下)的数度中第 x x x 行或列的前或后 X X X 个数的和,其中 X X X 为第 x x x 行或列的第一个数字。

    在这里插入图片描述

    其中第二行 8 = 3 + 4 + 1 8=3+4+1 8=3+4+1,因为第一个数字为 3 3 3 代表取 3 3 3 个数字。第二列最下方的 34 34 34 表示取本列后 7 7 7 个数字的和。 n , m ≤ 30 n,m \leq 30 n,m30 T T T 测, T ≤ 1 × 1 0 5 T \leq 1\times 10^5 T1×105

    解法:可以参考以下的打表代码:

    #include 
    using namespace std;
    const int N = 1 << 7;
    bool viscol[N][N*N], visrow[N][N*N], vispalace[N][N*N];
    int ans[N * N][N * N];
    int main()
    {
        int n, m;
        scanf("%d%d", &n, &m);
        n = 1 << n;
        m = 1 << m;
        for (int i = 0; i < n * n * m * m;i++)
        {
            int row = i / (n * m), col = i % (n * m);
            int palace_row = row / n, palace_col = col / m;
            int palace_id = palace_row * n + palace_col;
            for (int j = 0; j < n * m; j++)
                if(!viscol[col][j] && !visrow[row][j] && !vispalace[palace_id][j])
                {
                    viscol[col][j] = visrow[row][j] = vispalace[palace_id][j] = 1;
                    ans[row][col] = j;
                    break;
                }
        }//以上为暴力
        for (int i = 0; i < n * m;i++)
        {
            if(i == 0)
            {
                printf("+");
                for (int j = 0; j < n * m;j++)
                    printf("--%c", "-+"[j % m == m - 1]);
                printf("\n");
            }
            printf("|");
            for (int j = 0; j < n * m;j++)
            {
                assert(((i / n) ^ j ^ (i % n * m)) == ans[i][j]);//O(1)解法
                printf("%02X%c", (i / n) ^ j ^ (i % n * m), " |"[j % m == m - 1]);
            }
            printf("\n");
            if (i % n == n - 1)
            {
                printf("+");
                for (int j = 0; j < n * m;j++)
                    printf("--%c", "-+"[j % m == m - 1]);
                printf("\n");
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    通过打表或者观察样例可以得到,若将全部数字减一,并且下标均为 0-base,则第 i i i 行第 j j j 列的数字为 ⌊ i 2 n ⌋ ⊕ j ⊕ 2 m ( i   m o d   2 n ) \displaystyle \left \lfloor \dfrac{i}{2^n}\right \rfloor \oplus j \oplus 2^m(i \bmod 2^n) 2nij2m(imod2n)。其中, ⌊ i 2 n ⌋ \left \lfloor \dfrac{i}{2^n}\right \rfloor 2ni 表示了横行宫的贡献, j j j 为列贡献, 2 m ( i   m o d   2 n ) 2^m(i \bmod 2^n) 2m(imod2n) 为宫内行贡献。同时容易注意到,整个数独是中心对称的。因而如果要算第 x x x 列从下往上的答案,可以转化到第 2 n + m − x + 1 2^{n+m}-x+1 2n+mx+1 列的答案,从右往左同理。

    对于横行, X = ⌊ x 2 n ⌋ ⊕ 2 m ( x   m o d   2 n ) X=\left \lfloor \dfrac{x}{2^n}\right \rfloor \oplus 2^m(x \bmod 2^n) X=2nx2m(xmod2n)。其答案为 X + 1 + ∑ i = 0 X ( ⌊ x 2 n ⌋ ⊕ 2 m ( x   m o d   2 n ) ) ⊕ i \displaystyle X+1+\sum_{i=0}^X\left(\left \lfloor \dfrac{x}{2^n}\right \rfloor \oplus 2^m(x \bmod 2^n)\right) \oplus i X+1+i=0X(2nx2m(xmod2n))i。对于此类 ∑ i i ⊕ x \sum_{i}i \oplus x iix,其中 x x x 为一定值的,可以分位考虑,考虑第 j j j 位为 1 1 1 0 0 0 的个数。若 x x x 的第 j j j 位为 0 0 0 则计入 1 1 1 的个数,否则计入 0 0 0 的个数。

    对于纵列, X = x X=x X=x,其答案为 X + 1 + ∑ i = 0 X ( ⌊ i 2 n ⌋ ⊕ 2 m ( i   m o d   2 n ) ) ⊕ X \displaystyle X+1+\sum_{i=0}^X\left(\left \lfloor \dfrac{i}{2^n}\right \rfloor \oplus 2^m(i \bmod 2^n)\right) \oplus X X+1+i=0X(2ni2m(imod2n))X。不难发现, ⌊ i 2 n ⌋ ∈ [ 0 , 2 m − 1 ] \left \lfloor \dfrac{i}{2^n}\right \rfloor \in [0,2^m-1] 2ni[0,2m1],而 2 m ( i   m o d   2 n ) 2^m (i \bmod 2^n) 2m(imod2n) 对答案的贡献一定在第 m m m 个二进制位之上。因而枚举到第 j j j 位时,需要根据当前位置进行平移——当 j ≥ m j \geq m jm 时计算的时 i   m o d   2 n i \bmod 2^n imod2n,而 j < m jj<m 计算的为 ⌊ i 2 n ⌋ \left \lfloor \dfrac{i}{2^n}\right \rfloor 2ni

    因而单次询问复杂度为 O ( n + m ) \mathcal O(n+m) O(n+m)

    #include 
    using namespace std;
    void print(__int128_t x)
    {
        if(!x)
        {
            printf("0\n");
            return;
        }
        string ans = "";
        while(x)
        {
            ans += x % 10 + 48;
            x /= 10;
        }
        reverse(ans.begin(), ans.end());
        printf("%s\n", ans.c_str());
    }
    long long count(long long n, int digit)
    {
        long long block = n >> (digit + 1), res = n - (block << (digit + 1));
        return (block << digit) + max(res - (1ll << digit), 0ll);
    }
    char buf[40];
    int main()
    {
        int t, n, m;
        long long x;
        scanf("%d", &t);
        while(t--)
        {
            scanf("%d%d%s%lld", &n, &m, buf, &x);
            x--;
            if (buf[0] == 'b')
            {
                buf[0] = 't';
                x = (1ll << (n + m)) - x - 1;
            }
            else if (buf[0] == 'r')
            {
                buf[0] = 'l';
                x = (1ll << (n + m)) - x - 1;
            }
            if (buf[0] == 'l')
            {
                x = (x >> n) ^ ((x - ((x >> n) << n)) << m);
                __int128_t ans = x + 1;
                for (int k = 0; k < n + m;k++)
                {
                    __int128_t now = count(x + 1, k);
                    if (x >> k & 1)
                        now = x + 1 - now;
                    ans += now << k;
                }
                print(ans);
            }
            else
            {
                __int128_t ans = x + 1;
                for (int k = 0; k < n + m;k++)
                {
                    __int128_t now = count(x + 1, k < m ? n + k : k - m);
                    if (x >> k & 1)
                        now = x + 1 - now;
                    ans += now << k;
                }
                print(ans);
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    D Poker Game: Decision

    题意:桌面上有 6 6 6 张扑克牌,Alice 和 Bob 手上各有 2 2 2 张且明牌。二人以最优决策依次从桌上抽取一张牌直到二人各有五张牌,最终根据德州扑克的规则比大小。问最终谁会赢。

    解法:首先写清楚德州扑克的大小比较。由于此处张数较少,可以暴力枚举每一种抽牌情况,暴力 dfs 计算当前的最优决策。设 dfs 返回值为 − 1 , 0 , 1 -1,0,1 1,0,1 为 Bob 胜利、平局、Alice 胜利,Alice 会从中选择尽可能大的状态转移,而 Bob 会选择最小的。

    #pragma GCC optimize(3)
    #include
    #define IL inline
    #define LL long long
    #define pb push_back
    #define abs(x,y) (x<y?y-x:x-y)
    using namespace std;
    const int N=5e4+3,M=5e4+3;
    struct kk{
    	int op,r;
    	bool operator<(const kk &a) const{
    		return r>a.r;
    	}
    }a[3],b[3],c[10];
    struct hh{//比较牌的大小
    	int rk;kk a[6];
    	int operator<(const hh &b) const{
    		if(rk^b.rk) return rk<b.rk;
    		for(int i=1;i<=5;++i)
    		  if(a[i].r^b.a[i].r) return a[i].r<b.a[i].r;
    		return 2;
    	}
    	void get_rk(){
    		sort(a+1,a+6);kk b[6];
    		int f1=1,f2=1;
    		for(int i=2;i<=5;++i) if(a[i].op^a[i-1].op) f1=0;
    		for(int i=2;i<=5;++i) if(a[i].r^a[i-1].r-1) f2=0;
    		if(f1&&f2&&a[1].r==13) rk=12;
    		else if(f1&&f2) rk=11;
    		else if(f1&&a[1].r==13&&a[2].r==4&&a[3].r==3&&a[4].r==2&&a[5].r==1) rk=10;
    		else if(a[1].r==a[2].r&&a[2].r==a[3].r&&a[3].r==a[4].r) rk=9;
    		else if(a[2].r==a[3].r&&a[3].r==a[4].r&&a[4].r==a[5].r) rk=9,swap(a[1],a[5]);
    		else if(a[1].r==a[2].r&&a[2].r==a[3].r&&a[4].r==a[5].r) rk=8;
    		else if(a[1].r==a[2].r&&a[3].r==a[4].r&&a[4].r==a[5].r) rk=8,swap(a[1],a[5]),swap(a[2],a[4]);
    		else if(f1) rk=7;
    		else if(f2) rk=6;
    		else if(a[1].r==13&&a[2].r==4&&a[3].r==3&&a[4].r==2&&a[5].r==1) rk=5;
    		else if(a[1].r==a[2].r&&a[2].r==a[3].r) rk=4;
    		else if(a[2].r==a[3].r&&a[3].r==a[4].r) rk=4,swap(a[1],a[2]),swap(a[2],a[3]),swap(a[3],a[4]);
    		else if(a[3].r==a[4].r&&a[4].r==a[5].r){
    			rk=4;for(int i=1;i<=5;++i) b[i]=a[i];
    			a[1]=b[3],a[2]=b[4],a[3]=b[5],a[4]=b[1],a[5]=b[2];
    		}
    		else if(a[1].r==a[2].r&&a[3].r==a[4].r) rk=3;
    		else if(a[1].r==a[2].r&&a[4].r==a[5].r) rk=3,swap(a[3],a[5]);
    		else if(a[2].r==a[3].r&&a[4].r==a[5].r){
    			rk=3;for(int i=1;i<5;++i) swap(a[i],a[i+1]);
    		}
    		else if(a[1].r==a[2].r) rk=2;
    		else if(a[2].r==a[3].r) rk=2,swap(a[1],a[3]);
    		else if(a[3].r==a[4].r) rk=2,swap(a[1],a[3]),swap(a[2],a[4]);
    		else if(a[4].r==a[5].r){
    			rk=2;for(int i=1;i<=5;++i) b[i]=a[i];
    			a[1]=b[4],a[2]=b[5],a[3]=b[1],a[4]=b[2],a[5]=b[3];
    		}
    		else rk=1;
    	}
    }A,B,na,nb;
    int cn,to[129];
    IL int in(){
    	char c;int f=1;
    	while((c=getchar())<'0'||c>'9')
    	  if(c=='-') f=-1;
    	int x=c-'0';
    	while((c=getchar())>='0'&&c<='9')
    	  x=x*10+c-'0';
    	return x*f;
    }
    IL void get(kk &a){
    	char s[4];scanf("%s",s+1);
    	a.op=to[s[2]],a.r=to[s[1]];
    }
    int bo[10],mp[800],pm[100];
    IL int cmp(hh a,hh b){
    	a.get_rk(),b.get_rk();
    	int x=b<a;
    	if(!x) return 0;
    	if(x==1) return 2;
    	return 1;
    }
    int dfs(int pos,int val){//暴力搜索
    	if(~mp[val]) return mp[val]; 
    	if(pos==7) return mp[val]=cmp(na,nb);
    	mp[val]=pos&1?0:2;
    	for(int i=1;i<=6;++i)
    	  if(!bo[i]){
    	  	int now=val;
    	  	if(pos&1) na.a[(pos+1>>1)+2]=c[i],now+=pm[i-1];
    	  	else nb.a[(pos>>1)+2]=c[i],now+=pm[i-1]*2;
    	  	bo[i]=1;
    	  	int op=dfs(pos+1,now);
    	  	bo[i]=0;
    	  	if(pos&1) mp[val]=max(mp[val],op);
    	  	else mp[val]=min(mp[val],op);
    	  }
    	return mp[val];
    }
    IL void solve(){
    	na.rk=nb.rk=0;memset(mp,-1,sizeof(mp));
    	get(na.a[1]),get(na.a[2]),get(nb.a[1]),get(nb.a[2]);
    	for(int i=1;i<=6;++i) get(c[i]);
    	int ans=dfs(1,0);
    	if(!ans) puts("Bob");
    	else if(ans==1) puts("Draw");
    	else puts("Alice");
    }
    int main()
    {
    	pm[0]=1;for(int i=1;i<=6;++i) pm[i]=pm[i-1]*3;
    	to['S']=1,to['H']=2,to['C']=3,to['D']=4;
    	to['A']=13,to['2']=1,to['3']=2,to['4']=3,to['5']=4,to['6']=5,to['7']=6,to['8']=7,to['9']=8,
    	to['T']=9,to['J']=10,to['Q']=11,to['K']=12;
    	int T=in();
    	while(T--) solve();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116

    F Longest Common Subsequence

    题意:给定两个长度分别为 n , m n,m n,m 的序列 S , T S,T S,T,问其最长公共子序列长度。其中 S S S T T T 都是通过 x i + 1 = f ( x i ) = ( a x i 2 + b x i + c )   m o d   p x_{i+1}=f(x_i)=(ax_i^2+bx_i+c) \bmod p xi+1=f(xi)=(axi2+bxi+c)modp 迭代产生。 ∣ S ∣ , ∣ T ∣ ≤ 1 × 1 0 6 |S|,|T| \leq 1\times 10^6 S,T1×106

    解法:容易注意到,若 S i = T j S_i=T_j Si=Tj,则从这两个位置开始,后面都一定完全一样,因而对答案的贡献为 min ⁡ ( n − i + 1 , m − j + 1 ) \min(n-i+1,m-j+1) min(ni+1,mj+1)。因而可以直接暴力使用 map记忆 S S S 中每个元素的第一次出现位置。这样的时间复杂度为 O ( n log ⁡ n ) \mathcal O(n \log n) O(nlogn)

    基于以上性质,可以发现二者匹配的一定是一个后缀。因而对两个串进行翻转然后跑对两个串依次做一次字典串,利用 KMP 也可以得到答案。这样的复杂度为 O ( n ) \mathcal O(n) O(n)

    #include 
    using namespace std;
    const int N = 1000000;
    long long s[N + 5], t[N + 5];
    
    int main()
    {
        int caset, n, m;
        long long p, a, b, c, x;
        scanf("%d", &caset);
        while(caset--)
        {
            map<long long, int> pos;
            scanf("%d%d%lld%lld%lld%lld%lld", &n, &m, &p, &x, &a, &b, &c);
            for (int i = 1; i <= n;i++)
            {
                s[i] = x = (a * x % p * x % p + b * x % p + c) % p;
                if (pos.count(s[i]) == 0)
                    pos[s[i]] = i;
            }
            int ans = 0;
            for (int i = 1; i <= m;i++)
            {
                t[i] = x = (a * x % p * x % p + b * x % p + c) % p;
                if (pos.count(x))
                    ans = max(ans, min(m - i + 1, n - pos[x] + 1));
            }
            printf("%d\n", ans);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    G Lexicographic Comparison

    题意:给定两个长度均为 n n n 的排列 A , P A,P A,P。有以下 q q q 次三类操作:

    1. 交换 A x A_x Ax A y A_y Ay
    2. 交换 P x P_x Px P y P_y Py
    3. 查询 A P x AP^x APx A P y AP^y APy 的大小。其中 A P i AP^i APi 表示排列 A A A P P P 置换操作下迭代 i i i 轮得到的置换。 x , y ≤ 1 × 1 0 18 x,y \leq 1\times 10^{18} x,y1×1018

    n , q ≤ 1 × 1 0 5 n,q \leq 1\times 10^5 n,q1×105

    解法:考虑维护 P P P 置换构成的若干个环。显然根据环大小,同时存在的只有 O ( n ) \mathcal O(\sqrt n) O(n ) 个环。而同一环大小在同一置换次数下一定都处于同一环状态,因而只需要保留环上出现位置最早的那一个环即可。真正在查询操作的时候,枚举每一个环大小,找到出现最早的不同的置换环(若环大小为 i i i,只要满足 x   m o d   i ≠ y   m o d   i x \bmod i \neq y \bmod i xmodi=ymodi 则必然是不同状态),单独考虑它即可。

    因而问题转化为,如何快速的维护这些环,需要支持环的拆分和合并,以及快速查询环上第 k k k 个元素。可以考虑使用平衡树维护。将所有的环依照下标最小元素展开,形成一个平衡树森林。

    合并操作(交换原本不在同一个环上的 x , y x,y x,y )需要让后一段序列平移到前面:首先找到 x , y x,y x,y 所在环的第一个元素 s x , s y s_x,s_y sx,sy,利用环大小求出该环的范围 [ s x , t x ] [s_x,t_x] [sx,tx] [ s y , t y ] [s_y,t_y] [sy,ty]。具体操作为,将 [ y , t y ] [y,t_y] [y,ty] 插入到 x x x 的后面,再将 [ s y , p r e y ] [s_y,pre_y] [sy,prey] 插入到现在 t y t_y ty 的后面。拆分操作(交换同一个环上的 x , y x,y x,y)也是进行区间平移操作:找到 x , y x,y x,y 所在环 [ s , t ] [s,t] [s,t],将 y y y 产生的新环从中取出,再将其平移出去。具体实现可以参看代码。

    因而查询复杂度 O ( n ) \mathcal O(\sqrt n) O(n ),修改操作 O ( log ⁡ n ) \mathcal O(\log n) O(logn)

    #include 
    using namespace std;
    const int N = 100000;
    int n, B, A[N + 5], P[N + 5], Qsiz[N + 5];
    set<int> S1[320], S2;
    class Splay
    {
        struct node
        {
            int ch[2];
            int father;
            int minid;
            int siz;
            node()
            {
                ch[0] = ch[1] = 0;
                father = minid = siz = 0;
            }
        } NIL;
        vector<node> t;
        int tot;
        void new_node(int &place, int father)
        {
            place = ++tot;
            node temp = NIL;
            temp.father = father;
            temp.siz = 1;
            temp.minid = place;
            t.push_back(temp);
        }
    	void update(int place)
        {
            t[place].minid = place;
            t[place].siz = 1;
            for (int i = 0; i <= 1;i++)
                if (t[place].ch[i])
                {
                    t[place].minid = min(t[place].minid, t[t[place].ch[i]].minid);
                    t[place].siz += t[t[place].ch[i]].siz;
                }
        }
        void rotate(int now)
        {
            int pre = t[now].father;
            int p = t[pre].father;
            int dir = (t[pre].ch[0] == now);
            t[now].father = p;
            t[pre].father = now;
            t[t[now].ch[dir]].father = pre;
            if (p)
                t[p].ch[t[p].ch[1] == pre] = now;
            t[pre].ch[dir ^ 1] = t[now].ch[dir];
            t[now].ch[dir] = pre;
            update(pre);
        }
        void splay(int place, int tar = 0)
        {
            while (t[place].father != tar)
            {
                int pre = t[place].father;
                if (t[pre].father != tar)
                {
                    if ((t[t[pre].father].ch[0] == pre) ^ (t[pre].ch[0] == place))
                        rotate(place);
                    else
                        rotate(pre);
                }
                rotate(place);
            }
            update(place);
        }
        int get_root(int x)
        {
            splay(x);
    		return t[x].minid;
        }
        void add(int x)//只是在 set 中添加,不会在平衡树上真的添加
        {
            splay(x);
            Qsiz[t[x].minid] = t[x].siz;
            if (t[x].siz <= B)
                S1[t[x].siz].insert(t[x].minid);
            else
                S2.insert(t[x].minid);
        }
        void del(int x)//只是在 set 中删除,不会在平衡树上真的删除
        {
            splay(x);
            if (t[x].siz <= B)
                S1[t[x].siz].erase(t[x].minid);
    		else
    			S2.erase(t[x].minid);
        }
        int get(int place, int dir)//找到place的上一位或下一位
        {
            splay(place);
            int now = place;
            while (t[now].ch[dir])
                now = t[now].ch[dir];
            splay(now);
            return now;
        }
        int get_rank(int now, long long p)
        {
            splay(now);
            p = (p - 1) % t[now].siz + 1;
            p = (1 + t[now].siz - p) % t[now].siz + 1;
            while (1)
            {
                if (t[t[now].ch[0]].siz + 1 == p)
                    break;
                else if (t[t[now].ch[0]].siz >= p)
                    now = t[now].ch[0];
                else
                {
                    p -= t[t[now].ch[0]].siz + 1;
                    now = t[now].ch[1];
                }
            }
            splay(now);
            return now;
        }
        void moveFront(int x)
        {
            splay(x);
            int y = t[x].ch[0];
            if (!y)
                return;
            t[x].ch[0] = 0;
            update(x);
            int r = get(x, 1);
            t[r].ch[1] = y;
            update(r);
            t[y].father = r;
            splay(x);
        }
        void split(int x, int y)//分割环
        {
            del(x);
            moveFront(x);
            int z = P[y];//从P[y]进行切割,即将摘除[y,z]
            splay(z), splay(y, z);//将y的子树(y产生的新环)移动到z
            t[z].ch[1] = 0;//拆分
            update(z);
            t[y].father = 0;
            add(x), add(y);
        }
        void merge(int x, int y)//合并环
        {
            del(x), del(y);
            moveFront(x), moveFront(y);
            int z = get(x, 1);
            t[z].ch[1] = y;//将y接到x所在树上
            update(z);
            t[y].father = z;
            add(x);
        }
    
    public:
        Splay(int n)
        {
            t.push_back(NIL);
            tot = 0;
            for (int i = 1; i <= n; i++)
            {
                new_node(i, 0);
                add(i);
            }
        }
        void update_cyc(int x, int y)
        {
            if (x == y)
                return;
            int p = get_root(x), q = get_root(y);
            if (p == q)
                split(x, y);
            else
                merge(x, y);
            swap(P[x], P[y]);
        }
        int query(long long x, long long y, int t)
        {
            moveFront(t);
            int px = get_rank(t, x), py = get_rank(t, y);
            if (A[px] > A[py])
                return 1;
            else if (A[px] < A[py])
                return -1;
            else
                return 0;
        }
    };
    char buf[10];
    int main()
    {
    	int caset, q;
        long long x, y;
        scanf("%d",&caset);
    	while (caset--)
        {
            scanf("%d%d", &n, &q);
            B = sqrt(n) + 1;
            for (int i = 1; i <= n; i++)
                A[i] = P[i] = i;
            Splay solve(n);
            auto query = [&](long long x, long long y)
            {
                if (x == y)
                    return 0;
                int t = n + 1;
                for (int i = 1; i <= B; i++)
                {
                    if (S1[i].empty() || x % i == y % i)
                        continue;
                    t = min(t, *S1[i].begin());
                }
                for (int i : S2)
                {
                    int s = Qsiz[i];
                    if (x % s == y % s)
                        continue;
                    t = min(t, i);
                }
                if (t > n)
                    return 0;
                return solve.query(x, y, t);
            };
            while (q--)
            {
                scanf("%s%lld%lld", buf, &x, &y);
                if (buf[0] == 'c')
                {
                    int t = query(x, y);
                    if (t == -1)
                        printf("<\n");
                    else if (t == 1)
                        printf(">\n");
    				else
    					printf("=\n");
                }
                else if (buf[5] == 'a')
                    swap(A[x], A[y]);
                else
                    solve.update_cyc(x, y);
            }
            for (int i = 1; i <= B; i++)
                S1[i].clear();
            S2.clear();
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251

    I Equivalence in Connectivity

    题意:给定 k k k n n n 的点的图。对于第 i i i 个图,其由 p i p_i pi 图删除或者新增一条边构成(保证 p i < i p_ipi<i),问这 k k k 张图依据连通性可以分成多少组。 n , k ≤ 1 × 1 0 5 n,k \leq 1\times 10^5 n,k1×105

    解法:显然可以注意到,对图的操作序列可以构成一棵树。首先简化问题:若每次只在上一张图上进行删除或新增操作,如何实现。

    显然维护连通性可以用并查集,但是并查集无法维护删除操作,因而考虑只能添加不能删除。那么使用线段树分治:将这个操作序列建立一颗线段树,维护每条边出现的时间,必定是线段树上一段或几段的连续序列。然后再去遍历线段树,将添加的边下放到叶子节点,即可完成每个叶子节点的状态更新(并查集更新)。对于树上遍历的回溯操作,即是进行并查集的撤销操作。因而需要维护一个可撤销的并查集。

    注意:只需要一个并查集即可。因为线段树在遍历的每一个时刻只会面对一个操作局面,因而只需要在这个状态下不断的添加边或者回溯到历史版本即可。

    接下来要处理的问题是如何将这么多的状态的并查集去归类合并。一个精巧的办法是,给图上每个点一个随机权值,每个连通块的权值为连通块内所有点的权值异或和,再将所有连通块的权值和加起来作为哈希值。那么这样合并两个连通块只需要将两个连通块的权值异或起来即可,便于合并操作。此题卡单哈希,因而需要将这个随机数取得更大或者使用更多次的哈希。

    最后,本问题是建立在树上的(操作序列为一棵树),但是容易发现,对于一条边的增加和删除,只不过是变成了子树内全部都要增加与子树内全部都要删除。那么只需要对操作树进行一次 dfs 序列化,即可将问题转化到序列上,因而整个问题就圆满解决。整体时间复杂度 O ( n log ⁡ 2 n ) \mathcal O(n \log^2 n) O(nlog2n)

    #include
    #define IL inline
    #define LL long long
    #define pb push_back
    #define abs(x,y) (x<y?y-x:x-y)
    using namespace std;
    const LL N=1e5+3;
    struct hh{
    	LL to,nxt;
    }e[N<<1];
    struct kk{
    	LL op,x,y;
    	bool operator<(const kk &a) const{
    	return x^a.x?x<a.x:y<a.y;}
    }a[N],hsh[N];
    struct zz{
    	LL ld,lm,x,y;
    };
    LL n,m,k,num,fir[N],dfn[N],fa[N],siz[N],f1[N],f2[N],rev[N];
    map<LL,LL>mp;
    vector<LL>ans[N];
    IL LL in(){
    	char c;LL f=1;
    	while((c=getchar())<'0'||c>'9')
    	  if(c=='-') f=-1;
    	LL x=c-'0';
    	while((c=getchar())>='0'&&c<='9')
    	  x=x*10+c-'0';
    	return x*f;
    }
    IL LL mod(LL x){return x;}
    IL LL find(LL x){
    	while(x^fa[x]) x=fa[x];
    	return x;
    }
    struct segment{
    	#define ls k<<1
    	#define rs k<<1|1
    	#define pb push_back
    	vector<kk>e[N<<2];vector<kk>fn[N<<2];
    	void clear(LL k,LL l,LL r){
    		e[k].clear(),fn[k].clear();
    		if(l==r) return;
    		LL mid=l+r>>1;
    		clear(ls,l,mid),clear(rs,mid+1,r);
    	}
    	void ins(LL k,LL l,LL r,LL ll,LL rr,kk x){
    		if(ll>rr) return;
    		if(l>=ll&&r<=rr){e[k].pb(x);return;}
    		LL mid=l+r>>1;
    		if(ll<=mid) ins(ls,l,mid,ll,rr,x);
    		if(rr>mid) ins(rs,mid+1,r,ll,rr,x);
    	}
    	IL void merge(LL x,LL y,LL k,LL &sum1,LL &sum2){
    		LL fx=find(x),fy=find(y);
    		if(fx^fy){
    			if(siz[fx]<siz[fy]) swap(fx,fy);
    			fn[k].pb((kk){f1[fx],fx,fy});
    			sum1=mod(sum1-mod(f1[fx]+f1[fy])),
    			sum2=mod(sum2-mod(f2[fx]+f2[fy])),
    			fa[fy]=fx,siz[fx]+=siz[fy],f1[fx]^=f1[fy],f2[fx]^=f2[fy];
    			sum1=mod(sum1+f1[fx]),sum2=mod(sum2+f2[fx]);
    		}
    	}
    	void dfs(LL k,LL l,LL r,LL sum1,LL sum2){
    		for(LL i=e[k].size()-1;~i;--i) merge(e[k][i].x,e[k][i].y,k,sum1,sum2);
    		LL mid=l+r>>1;
    		if(l==r) hsh[rev[l]]=(kk){0,sum1,sum2};
    		else dfs(ls,l,mid,sum1,sum2),dfs(rs,mid+1,r,sum1,sum2);
    		for(LL i=fn[k].size()-1;~i;--i){
    			LL x=fn[k][i].x,y=fn[k][i].y;
    			f2[x]^=f2[y],f1[x]^=f1[y],siz[x]-=siz[y],fa[y]=y;
    		}
    	}
    }T;
    IL void clear(){
    	memset(fir+1,num=0,8*k);
    	mp.clear(),T.clear(1,1,k);
    	for(LL i=1;i<=k;++i) ans[i].clear();
    }
    IL void add(LL x,LL y){e[++num]=(hh){y,fir[x]},fir[x]=num;}
    IL LL get(LL x,LL y){
    	if(x<y) swap(x,y);
    	return 1ll*(x-1)*n+y-1;
    }
    void dfs(LL u){
    	rev[dfn[u]=++num]=u;LL cnt=get(a[u].x,a[u].y);
    	if(u^1){
    		if(a[u].op==1) mp[cnt]=num;
    		else{
    			T.ins(1,1,k,mp[cnt],num-1,a[u]);
    			mp.erase(cnt);
    		}
    	}
    	for(LL i=fir[u],v;v=e[i].to;i=e[i].nxt) dfs(v);
    	if(u^1){
    		if(a[u].op==1){
    			T.ins(1,1,k,mp[cnt],num,a[u]);
    			mp.erase(cnt);
    		}
    		else mp[cnt]=num+1;
    	}
    }
    void solve(){
    	char s[10];LL x,y,z,sum1=0,sum2=0;
    	k=in(),n=in(),m=in();
    	clear();
    	for(LL i=1;i<=m;++i) x=in(),y=in(),mp[get(x,y)]=1;
    	for(LL i=2;i<=k;++i){
    		x=in(),scanf("%s",s+1),add(x,i);
    		a[i]=(kk){(s[1]=='a')?1:-1,in(),in()};
    	}
    	num=0,dfs(1);
    	for(map<LL,LL>:: iterator it=mp.begin();it!=mp.end();++it)
    	  T.ins(1,1,k,it->second,k,(kk){0,it->first/n+1,it->first%n+1});
    	for(LL i=1;i<=n;++i) fa[i]=i,siz[i]=1,f1[i]=rand(),f2[i]=rand(),sum1=mod(sum1+f1[i]),sum2=mod(sum2+f2[i]);
    	T.dfs(1,1,k,sum1,sum2);LL cnt=0;map<kk,LL>mp;
    	for(int i=1;i<=k;++i)
    	  if(!mp.count(hsh[i])) mp[hsh[i]]=++cnt,ans[cnt].pb(i);
    	  else ans[mp[hsh[i]]].pb(i);
    	printf("%d\n",cnt);
    	for(int i=1;i<=cnt;++i){
    		printf("%d ",ans[i].size());
    		for(int j=0;j<ans[i].size();++j)
    	  	printf("%d ",ans[i][j]);
    	  putchar('\n');
    	}
    }
    int main()
    {
    	srand(time(0));
    	int T=in();
    	while(T--) solve();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    K Symmetry: Convex

    题意:给定 n n n 个点的凸多边形 C n C_n Cn,输出前 i i i 个点构成的凸包的对称轴。 n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n1×105

    解法:找对称轴可以使用 Manacher。本题先预处理整个凸包构成的串的 Manacher(注意不要倍长),然后依次加入点去考虑。

    容易发现一个性质:随着 i i i 的增大, ∠ C i C 0 C 1 \angle C_iC_0C_1 CiC0C1 是在不断变大的。除非该对称轴是其角平分线,则该角必然与另一个角大小相同(对称)。使用一个 map记录每个角的大小,然后去枚举 ∠ C i C 0 C 1 \angle C_iC_0C_1 CiC0C1 角对称的角在哪里,使用 Manacher 预处理出来的回文半径再附带考虑几个新添加的角和边即可。代码中有更详细的解释。

    #include 
    using namespace std;
    
    using _T = long long; // 全局数据类型,可修改为 long long 等
    
    constexpr _T eps = 0;
    constexpr long double PI = 3.1415926535897932384l;
    
    // 点与向量
    template<typename T> struct point
    {
    	T x, y;
    
    	bool operator==(const point &a) const { return (abs(x - a.x) <= eps && abs(y - a.y) <= eps); }
    	bool operator<(const point &a) const
    	{
    		if (abs(x - a.x) <= eps)
    			return y < a.y - eps;
    		return x < a.x - eps;
    	}
    	bool operator>(const point &a) const { return !(*this < a || *this == a); }
    	point operator+(const point &a) const { return {x + a.x, y + a.y}; }
    	point operator-(const point &a) const { return {x - a.x, y - a.y}; }
    	point operator-() const { return {-x, -y}; }
    	point operator*(const T k) const { return {k * x, k * y}; }
    	point operator/(const T k) const { return {x / k, y / k}; }
    	T operator*(const point &a) const { return x * a.x + y * a.y; } // 点积
    	T operator^(const point &a) const { return x * a.y - y * a.x; } // 叉积,注意优先级
    	int toleft(const point &a) const
    	{
    		const auto t = (*this) ^ a;
    		return (t > eps) - (t < -eps);
    	}									   // to-left 测试
    	T len2() const { return (*this) * (*this); }				  // 向量长度的平方
    	T dis2(const point &a) const { return (a - (*this)).len2(); } // 两点距离的平方
    
    	// 涉及浮点数
    	long double len() const { return sqrtl(len2()); }																	   // 向量长度
    	long double dis(const point &a) const { return sqrtl(dis2(a)); }													   // 两点距离
    	long double ang(const point &a) const { return acosl(max(-1.0, min(1.0, ((*this) * a) / (len() * a.len())))); }		   // 向量夹角
    	point rot(const long double rad) const { return {x * cos(rad) - y * sin(rad), x * sin(rad) + y * cos(rad)}; }		   // 逆时针旋转(给定角度)
    	point rot(const long double cosr, const long double sinr) const { return {x * cosr - y * sinr, x * sinr + y * cosr}; } // 逆时针旋转(给定角度的正弦与余弦)
    };
    
    using Point = point<_T>;
    
    // 直线
    template<typename T> struct line
    {
    	point<T> p, v; // p 为直线上一点,v 为方向向量
    
    	bool operator==(const line &a) const { return v.toleft(a.v) == 0 && v.toleft(p - a.p) == 0; }
    	int toleft(const point<T> &a) const { return v.toleft(a - p); } // to-left 测试
    
      // 涉及浮点数
    	point<T> inter(const line &a) const { return p + v * ((a.v ^ (p - a.p)) / (v ^ a.v)); } // 直线交点
    	long double dis(const point<T> &a) const { return abs(v ^ (a - p)) / v.len(); }			// 点到直线距离
    	point<T> proj(const point<T> &a) const { return p + v * ((v * (a - p)) / (v * v)); }	// 点在直线上的投影
    };
    
    using Line = line<_T>;
    
    vector<int> Manacher(vector<long long> &s)
    {
        int n = s.size();
        vector<int> p(n, 1);
        for (int i = 0, r = 0, m = 0; i < n; i++)
        {
            if (i < r)
                p[i] = min(p[m * 2 - i], r - i);
            while (i >= p[i] && i + p[i] < n && s[i - p[i]] == s[i + p[i]])
                p[i]++;
            if (i + p[i] > r)
            {
                m = i;
                r = i + p[i];
            }
        }
        return p;
    }
    
    Line prep(Point a, Point b)//中垂线,经过的点为(A.x+B.x, A.y+B.y) 最后输出要还原
    {
        Point dir = (Point){b.y - a.y, a.x - b.x}, p = a + b;
        return (Line){p, dir};
    }
    void print(Line l)
    {
        long long a = -l.v.y, b = l.v.x;
        long long c = -a * l.p.x - b * l.p.y;
        a <<= 1;
        b <<= 1;
        long long d = __gcd(abs(c), __gcd(abs(a), abs(b)));
        printf("%lld %lld %lld\n", a / d, b / d, c / d);
    }
    
    int main()
    {
        int t, n;
        scanf("%d", &t);
        while(t--)
        {
            scanf("%d", &n);
            vector<Point> C(n);
            for (int i = 0; i < n;i++)
                scanf("%lld%lld", &C[i].x, &C[i].y);
            vector<long long> s;
            for (int i = 0; i < n;i++)
            {
                s.push_back(C[i].dis2(C[(i + 1) % n]));
                s.push_back((C[i] - C[(i + 1) % n]) * (C[(i + 2) % n] - C[(i + 1) % n]));
            }
            map<long long, vector<int>> ang;
            for (int i = 1; i < 2 * n; i += 2)
                ang[s[i]].push_back(i);
            auto para = Manacher(s);
            for (int i = 2; i < n;i++)
            {
                vector<Line> ans;
                if (para[i] >= i && s[0] == C[i].dis2(C[0]))// i01角平分线,用i的对踵点/边(para数组中的第i位)判断。若刚好能覆盖到0,则除了01和0i的边其余均相等。
                    ans.push_back(prep(C[1], C[i]));
                auto lastang = (C[1] - C[0]) * (C[i] - C[0]);
                if (para[i - 1] >= i && lastang == (C[0] - C[i]) * (C[i - 1] - C[i]))//0i的中垂线。用0i的对踵点(para中第i-1位判断)是否能覆盖到0,剩下要判断的就是i01角和0i边
                    ans.push_back(prep(C[0], C[i]));
                //接下来将整个凸包分成[0,j] 侧和 [j,i] 侧,0与j角对称相等
                for (auto j : ang[lastang])
                {
                    if (j >= 2 * i - 1)
                        break;
                    if (para[(j - 1) / 2] < (j - 1) / 2 + 1) //确保[0,j]侧匹配(对称),即是找这一段的中点,查询其覆盖半径能否到达0
                        continue;
                    if (j == 2 * i - 3) //(i-1)i0的角平分线
                    {
                        if (C[i].dis2(C[0]) == C[i].dis2(C[i - 1]))
                            ans.push_back(prep(C[0], C[i - 1]));
                    }
                    else
                    {
                        int outercen = (2 * i - 2 + j + 3) / 2;
                        if (para[outercen] >= (2 * i - 2 - j - 3) / 2 + 1)//外侧[j,i]可以匹配
                        {
                            int oriid = (j + 1) / 2;
                            if (C[i].dis2(C[0]) == C[oriid].dis2(C[oriid + 1]) && (C[0] - C[i]) * (C[i - 1] - C[i]) == (C[oriid] - C[oriid + 1]) * (C[oriid + 2] - C[oriid + 1]))
                            //最后的一条边和一个角判断。这一段由于是新加的因而需要手动判断
                                ans.push_back(prep(C[0], C[oriid]));
                        }
                    }
                }
                printf("%d\n", ans.size());
                for (auto j : ans)
                    print(j);
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
  • 相关阅读:
    宏集干货 | 手把手教你通过CODESYS V3进行PLC编程(三)
    对话框管理器第五章:将非模态对话框转为模态
    具有交叉验证的加权向量均值算法优化核极限学习机(INFO_KELM)的回归预测MATLAB(源代码)
    学会Dockerfile
    简易的安卓天气app(二)——适配器、每小时数据展示
    纯前端读写文件?
    Word控件Spire.Doc 【图像形状】教程(7): 如何使用 C# 在 Word 中替换图像
    鸿蒙应用开发-第一章-CSS3的grid布局
    js 设计模式 (面向对象编程)(第一部分)
    期货开户手续费的组成和收费模式
  • 原文地址:https://blog.csdn.net/m0_52048145/article/details/126337967