• AtCoder Grand Contest 044 简要题解


    从这里开始

      因为比赛的时候在路上,所以又成功错过下分和被神仙 jerome_wei 吊起来打(按在地上摩擦)的好机会。

    Problem A Pay to Win

      把这个过程倒过来。不难发现到下一次除之前,要么是加到 $lfloor n/d floor d$ 要么是 $lceil n/d ceil d$,或者直接减到 0.

      直接用 map 记忆化的复杂度为 $O(T log^3 N log log N)$。具体的来说,每个状态只可能是 $lfloor frac{n}{2^a3^b5^c} floor$ 或者 $lceilfrac{n}{2^a3^b5^c} ceil$,对于 $ leftlceilfrac{lfloor frac{n}{u} floor }{v} ight ceil  $ 的情况有 $left lfloorfrac{n}{uv} ight floor = leftlfloorfrac{lfloor frac{n}{u} floor }{v} ight floor leqslant leftlceilfrac{lfloor frac{n}{u} floor }{v} ight ceil  leqslant leftlceilfrac{lceilfrac{n}{u} ceil}{v} ight ceil = leftlceilfrac{n}{uv} ight ceil$ 

    Code

    #include <bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    #define pli pair<ll, int>
    
    template <typename T>
    bool vmin(T& a, T b) {
    	return (a > b) ? (a = b, true) : false;
    }
    
    const ll llf = 1e18;
    
    ll n;
    int T, A, B, C, D;
    
    map<ll, ll> F;
    vector<pair<int, int>> tr;
    
    ll dfs(ll n) {
    	if (n == 0) {
    		return 0;
    	} else if (n == 1) {
    		return D;
    	} else if (F.count(n)) {
    		return F[n];
    	}
    	ll ret = llf;
    	if (n <= ret / D) {
    		ret = n * D;
    	}
    	for (auto t : tr) {
    		int d = t.first;
    		int c = t.second;
    		ll nn = n / d * d;
    		vmin(ret, dfs(nn / d) + (n - nn) * D + c);
    		nn = (n + d - 1) / d * d;
    		vmin(ret, dfs(nn / d) + (nn - n) * D + c);
    	}
    	return F[n] = ret;
    }
    
    void solve() {
    	cin >> n >> A >> B >> C >> D;
    	F.clear();
    	tr = vector<pair<int, int>> {make_pair(2, A), make_pair(3, B), make_pair(5, C)};
    	cout << dfs(n) << '
    ';
    }
    
    int main() {
    	cin >> T;
    	while (T--) {
    		solve();
    	}
    	return 0;
    }

    Problem B Joker

      容易发现初始最短路之和为 $O(n^3)$。一个人离开后暴力更新会产生改变的位置,时间复杂度不会超过初始最短路之和。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    typedef bool boolean;
    
    template <typename T>
    T smin(T x) {
    	return x;
    }
    template <typename T, typename ...K>
    T smin(T a, const K &...args) {
    	return min(a, smin(args...));
    }
    
    const int N = 505;
    
    const int mov[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};
    
    int n;
    int ans = 0;
    int f[N][N];
    int vis[N][N];
    bool occupy[N][N];
    
    queue<int> qx0, qy0, qx1, qy1;
    void update(int x, int y) {
    	static int dfc = 0;
    	++dfc;
    	qx0.push(x);
    	qy0.push(y);
    	while (!qx0.empty() || !qx1.empty()) {
    		int ex = -1, ey = -1;
    		if (!qx0.empty()) {
    			ex = qx0.front();
    			ey = qy0.front();
    			qx0.pop();
    			qy0.pop();
    		} else {
    			ex = qx1.front();
    			ey = qy1.front();
    			qx1.pop();
    			qy1.pop();
    		}
    		if (vis[ex][ey] == dfc) {
    			continue;
    		}
    		vis[ex][ey] = dfc;
    		int w = f[ex][ey] + occupy[ex][ey];
    		for (int i = 0; i < 4; i++) {
    			int nx = ex + mov[i][0];
    			int ny = ey + mov[i][1];
    			if (nx >= 1 && nx <= n && ny >= 1 && ny <= n && w < f[nx][ny]) {
    				f[nx][ny] = w;
    				if (!occupy[ex][ey]) {
    					qx0.push(nx);
    					qy0.push(ny);
    				} else {
    					qx1.push(nx);
    					qy1.push(ny);
    				}
    			}
    		}
    	}
    }
    
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) {
    		for (int j = 1; j <= n; j++) {
    			f[i][j] = smin(i - 1, j - 1, n - i, n - j);
    			occupy[i][j] = true;
    		}
    	}
    	for (int i = 1, _ = n * n, t, x, y; i <= _; i++) {
    		scanf("%d", &t);
    		x = (t - 1) / n + 1;
    		y = t - (x - 1) * n;
    		ans += f[x][y];
    		occupy[x][y] = false;
    		update(x, y);
    	}
    	printf("%d
    ", ans);
    	return 0;
    }

    Problem C Strange Dance

      建一个 Trie 树,维护位置 $i$ 上的是谁。

      对于 S 操作就是一个全局交换 2,3 子树。

      对于 R 操作做一次循环位移,然后暴力递归有进位的子树。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    
    int ans[540000];
    
    typedef class TrieNode {
    	public:
    		TrieNode *ch[3];
    		bool swp12;
    		int id;
    
    		void swap12() {
    			swp12 ^= 1;
    			swap(ch[1], ch[2]);
    		}
    		bool leaf() {
    			return !ch[0];
    		}
    		void push_down() {
    			if (swp12) {
    				ch[0]->swap12();
    				ch[1]->swap12();
    				ch[2]->swap12();
    				swp12 = false;
    			}
    		}
    
    		void get_ans(int base, int v) {
    			if (leaf()) {
    				ans[id] = v;
    				return;
    			}
    			push_down();
    			ch[0]->get_ans(base * 3, v);
    			ch[1]->get_ans(base * 3, v += base);
    			ch[2]->get_ans(base * 3, v += base);
    		}
    } TrieNode;
    
    TrieNode pool[1000000];
    TrieNode *_top = pool;
    
    TrieNode *newnode() {
    	return _top++;
    }
    
    typedef class Trie {
    	public:
    		TrieNode* rt;
    
    		void build(TrieNode*& p, int r, int base, int v) {
    			p = newnode();
    			if (r == 0) {
    				p->id = v;
    				return;
    			}
    			build(p->ch[0], r - 1, base * 3, v);
    			build(p->ch[1], r - 1, base * 3, v += base);
    			build(p->ch[2], r - 1, base * 3, v += base);
    		}
    		void build(int n) {
    			build(rt, n, 1, 0);
    		}
    
    		void swap12() {
    			rt->swap12();
    		}
    		
    		void update(TrieNode*& p) {
    			if (p->leaf()) {
    				return;
    			}
    			p->push_down();
    			swap(p->ch[0], p->ch[1]);
    			swap(p->ch[0], p->ch[2]);
    			update(p->ch[0]);
    		}
    		void update() {
    			update(rt);
    		}
    
    		void get_ans() {
    			rt->get_ans(1, 0);
    		}
    } Trie;
    
    int n;
    char T[200005];
    Trie tr;
    
    int main() {
    	scanf("%d", &n);
    	scanf("%s", T + 1);
    	int m = strlen(T + 1);
    	tr.build(n);
    	for (int i = 1; i <= m; i++) {
    		char c = T[i];
    		if (c == 'S') {
    			tr.swap12();
    		} else {
    			tr.update();
    		}
    	}
    	tr.get_ans();
    	int all = 1;
    	for (int i = 1; i <= n; i++) {
    		all *= 3;
    	}
    	for (int i = 0; i < all; i++) {
    		printf("%d ", ans[i]);
    	}
    	return 0;
    }

    Problem D Guess the Password

      考虑询问 62 次长度为 128 的全 a 串,全 b 串.....然后可以知道每种字符有多少个。

      注意到如果有一个长度为 $l$ 的串是原串的子序列,那么如果在这个串中插入一个字符 $c$ 使得询问的结果减少 1,这意味着插入后仍然是原串的子序列。

      考虑如果我们已经知道两个字符集不相交的串 $s, t$ 分别是原串的子序列,我们怎么把它们合并。考虑在 $s$ 的每个位置依次插入 $t$ 的下一个未确定字符,然后判断它是否是原串的子序列。

      然后做一个简单归并就好了。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    
    int query(string s) {
    	cout << "? " << s << '
    ';
    	cout.flush();
    	int dis;
    	cin >> dis;
    	return dis;
    }
    
    int L;
    string charset;
    vector<string> strs;
    
    string merge(int l, int r) {
    	if (l == r) {
    		return strs[l];
    	}
    	int mid = (l + r) >> 1;
    	string sl = merge(l, mid);
    	string sr = merge(mid + 1, r);
    	string cur = "";
    	int should = L - sl.length();
    	int pr = 0, _pr = (signed) sr.size();
    	for (int i = 0; i < (signed) sl.size(); i++) {
    		while (pr < _pr && query(cur + sr[pr] + sl.substr(i)) == should - 1) {
    			cur += sr[pr++];
    			should--;
    		}
    		cur += sl[i];
    	}
    	while (pr < _pr) {
    		cur += sr[pr++];
    	}
    	return cur;
    }
    
    int main() {
    	for (int i = 0; i < 26; i++) {
    		charset += (char) ('a' + i);
    	}
    	for (int i = 0; i < 26; i++) {
    		charset += (char) ('A' + i);
    	}
    	for (int i = 0; i < 10; i++) {
    		charset += (char) ('0' + i);
    	}
    	L = 0;
    	for (int i = 0; i < 62; i++) {
    		char c = charset[i];
    		string s;
    		int cnt;
    		s.assign(128, c);
    		L += (cnt = 128 - query(s));
    		strs.push_back(s.substr(0, cnt));
    	}
    	string ans = merge(0, (signed) strs.size() - 1);
    	cout << "! " << ans << '
    ';
    	cout.flush();
    	return 0;
    }

    Problem E Random Pawn

      首先假设第一个和最后一个都是 $A$ 中最大的,如果不是这样的话可以通过旋转,然后再在后面增加一个来实现。

      因为到 $A$ 最大的地方一定会停止,因此现在问题变成了链上。

      设 $E_i$ 表示从 $i$ 出发的最大期望收益,显然有 $E_i = max{frac{E_{i - 1} + E_{i + 1}}{2} - B_i, A_i}$。

      考虑有 $B_i$ 非常地难处理,考虑把它搞掉。

      设 $F_i = E_i - C_i$,那么有 $F_i = max{frac{E_{i - 1} - C_{i - 1} + E_{i + 1} - C_{i + 1}}{2}+ frac{C_{i - 1} + C_{i + 1}}{2}  - C_i - B_i, A_i - C_i}$ 。

      因此 $C_i$ 满足 $ frac{C_{i - 1} + C_{i + 1}}{2}  - C_i - B_i = 0$。钦定 $C_0 = C_1 = 0$,然后可以简单构造出来。

      注意到 $C_{i + 1} - C_i - 2B_i = C_i - C_{i - 1}$ 因此 $C_i$ 大概是 $2B_i$ 做二次前缀和,因此范围大概是 $10^{12}$ 左右。

      考虑最终的序列中是硬点若干位置 $p$,使得 $F_p = A_p$。接下来假定 $A_i$ 都已经减去了 $C_i$。

      考虑知道一段最左端为 $l$,最右端为 $r$ 时怎么计算中间的贡献。不难发现中间的 $F$ 满足 $F_{i} - F_{i - 1} = F_{i + 1} - F_i$,因此有 $F_i = frac{(i - l) A_r + (r - i) A_l}{r - l}$。

      然后求一个和有 $sum_{l <i < r} F_i = frac{1}{2}(A_l + A_r) (r - l - 1)$。

      如果答案被计算两次,每一段各计算一次首尾的贡献,最终再计算一次开头和结尾的贡献。

      那么此时一段的贡献为 $(A_l + A_r) (r - l)$,不难发现这个是某个梯形面积的两倍。

      不难发现取 ${(i, A_i)}$ 的上凸壳的点的时候,这个面积能达到最大值。(因为显然这个时候 $F_i$ 达到了最大值)

    Code

    #include <bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    
    typedef class Point {
    	public:
    		ll x, y;
    
    		Point() {	}
    		Point(ll x, ll y) : x(x), y(y) {	}
    } Point;
    
    Point operator - (Point a, Point b) {
    	return Point(a.x - b.x, a.y - b.y);
    }
    ll cross(Point a, Point b) {
    	return a.x * b.y - a.y * b.x;
    }
    
    int n;
    
    int main() {
    	scanf("%d", &n);
    	vector<ll> A (n), B (n);
    	for (auto& x : A) {
    		scanf("%lld", &x);
    	}
    	for (auto& x : B) {
    		scanf("%lld", &x);
    	}
    	int id = 0;
    	for (int i = 1; i < n; i++) {
    		if (A[i] > A[id]) {
    			id = i;
    		}
    	}
    	rotate(A.begin(), A.begin() + id, A.end());
    	rotate(B.begin(), B.begin() + id, B.end());
    	A.push_back(A[0]);
    	B.push_back(B[0]);
    	
    	vector<ll> C (n + 1, 0);
    	for (int i = 2; i <= n; i++) {
    		C[i] = 2 * (C[i - 1] + B[i - 1]) - C[i - 2];
    	}
    
    	int tp = 0;
    	vector<Point> stk (n + 3);
    	for (int i = 0; i <= n; i++) {
    		Point P (i, A[i] - C[i]);
    		while (tp >= 2 && cross(stk[tp] - stk[tp - 1], P - stk[tp]) >= 0)
    			tp--;
    		stk[++tp] = P;
    	}
    
    	ll res = 0;
    	for (int i = 2; i <= tp; i++) {
    		res += (stk[i - 1].y + stk[i].y) * (stk[i].x - stk[i - 1].x);
    	}
    	res += stk[1].y + stk[tp].y;
    	for (int i = 0; i <= n; i++) {
    		res += 2 * C[i];
    	}
    	res -= A[0] * 2;
    	double ans = 1.0 * res / (2 * n);
    	printf("%.12lf
    ", ans);
    	return 0;
    }

    Problem F Name-Preserving Clubs

      假设有 $k$ 个集合,那么考虑建一个 $k imes n$ 的矩阵,每一位填 0 或者 1,表示这个集合中是否包含这个元素。

      首先考虑任意两个集合都不同的情况。

      如果称一个上述矩阵是好的,那么当且仅当任意打乱它的列(不能和原来相同),不存在一种方式使得打乱行和最初的矩阵相同。不难发现,这和题目中的一个 name-preserving configuration 一一对应。

      不难证明一个好的矩阵任意两列都不同,因为如果存在两列相同,我们交换这两列, 它和原来一模一样,这和定义矛盾。

      假设一个矩阵 $A$ 是好的,可以注意到下面两个性质:

    • $A$ 的转置 $A^{T}$ 是好的。
    • 考虑 $2^k$ 种不同的列,由其中所有不存在于 $A$ 的列构成的矩阵 $A^{C}$ 也是好的。

      前者如果不成立,那么对应的行列操作可以应用到 $A$ 上使得操作后和它自己相同。因为没有任意一行或者一列是相同的,所以行列都至少操作 1 次,因此这是满足定义的。

      注意到行列操作是独立的,因此先打乱行,再打乱列是等价的。

      对于后者,考虑如果不成立,那么打乱行后,使得和原来的列集合相同,可以推出,做这些打乱行操作,可以使得 $A$ 不是好的。

    推论 设 $c(k, n)$ 表示本质不同的 $k imes n$ 的好的矩阵的数量,那么有 $c(k, n) = c(n, k), c(k, n) = c(k, 2^{k} - n)$ 

      证明由上述讨论易得。

      设 $g(n)$ 表示最小的 $k$ 使得 $c(k, n) > 0$,那么有:

    性质1 $2^{g(n)} - n geqslant g(g(n))$

      证明 不断应用推论可得 $c(g(n), n) = c(g(n), 2^{g(n)} - n) = c(2^{g(n)} - n, g(n))$,然后由定义可得。

      设函数 $G(n)$ 满足 $G(1) = 0$,$G(n)$ 是最小的 $k$ 满足 $2^{k} - n geqslant G(k)$。

    引理1 对于 $n > 1$,那么有 $0 leqslant G(i) - G(i - 1) leqslant 1$。

       证明 首先不难用归纳法证明 $G(i) < i$。然后 $G(i) geqslant G(i - 1)$ 比较显然,这里略去证明。

      考虑用归纳法,当 $n = 2,3$ 的时候显然。

      考虑 $n = i(i > 3)$ 的情形。因为 $2^{G(i - 1)} - (i - 1) geqslant G(G(i - 1))$,$2^{G(i - 1)} geqslant 2$,所以有 $2^{G(i - 1) + 1} - i geqslant 2^{G(i - 1)} + 1 - (i - 1) geqslant G(G(i - 1)) + 1 geqslant G(G(i - 1) + 1)$ 。

    引理2 对于 $k geqslant G(n)$,那么都有 $2^k - n geqslant G(k)$

       证明 考虑用归纳法,当 $k = G(n)$ 显然成立。

       当 $k > G(n)$ 的时候因为有 $2^{k - 1} geqslant 1$,所以有 $2^{k} - n geqslant 2^{k -1} + 1 - n geqslant G(k  - 1) + 1 geqslant G(k)$。 

    引理3 $c(k, n) > 0$ 当且仅当 $G(n) leqslant k leqslant 2^n - G(n)$。

      证明 必要性显然,考虑充分性。

      考虑使用归纳法,当 $n = 1$ 的时候显然成立。下面当 $n > 1$ 的时候

      如果 $G(n) leqslant k < n$,那么可以用推论使得变为 $k > n$ 的情形。现在我们来证明它满足条件,因为 $k geqslant G(n)$,所以有 $2^k - n geqslant G(k)$,所以有 $n leqslant 2^n - G(k)$。因为 $k leqslant 2^n - G(n)$,所以 $2^n - k geqslant G(n)$,因此 $n geqslant G(k)$,因此有 $G(k) leqslant n leqslant 2^n - G(k)$。

      如果 $2^{n - 1} < k$,那么可以用推论使得变为 $k < 2^{n - 1}$ 的情形。我们还是来证明它满足条件,因为 $2^n - k < k$,所以只用证明 $G(n) leqslant 2^n - k$。因为 $2^n - G(n) geqslant k$,移项可得它成立。

      现在考虑 $n leqslant k leqslant 2^{n - 1}$。

      考虑构造一个集合 ${{1}, {1, 2}, {2, 3}, {3, 4}, cdots, {n - 1, n}}$。然后对于剩下 $k - n$ 个填任意大小大于等于 $3$ 的集合。

      可以手动验证当 $n = 2, n = 3$ 可行,当 $n geqslant 4$ 的时候,满足大小大于等于 $3$ 的集合至少占了一半,因此一定可行。

    引理4 当 $6 leqslant k leqslant  n leqslant 2^{k - 1}$ 时, $c(k, n) > 1000$

      证明的思路大概是考虑先构造一个大小为 $k - 2$ 的集合,包含 ${1, 2}, {2, 3}, cdots, {k - 2, k -1}$,然后将其中一个或者其中 $k - 3$ 个取补集。剩下的 $n - k + 2$ 个集合随便塞大小不为 2 或者 $k - 2$ 的集合。

      然后考虑两个集合可能相同的情况。由于 yyf 非常菜,目前还不会,所以咕咕咕咕。结论是 $n = 4$ 的时候答案加上 1,$n = 7$ 的时候答案加上 2.

      对于剩下 $k leqslant 5, n leqslant 2^{k - 1}$ 的情况写一个爆搜就行了。如果您的爆搜比较慢,请打表。

    Code

    #include <bits/stdc++.h>
    using namespace std;
    
    /*
    #include <bits/stdc++.h>
    using namespace std;
    
    const int Nmx = 5;
    
    const int fac[6] = {1, 1, 2, 6, 24, 120};
    int shuf[Nmx + 1][120][1 << Nmx];
    
    void prepare() {
      auto arrange = [&] (int v, int l, const vector<int>& p) {
        int rt = 0;
        for (auto x : p) {
          if (v & 1) {
            rt |= 1 << x;
          }
          v >>= 1;
        }
        return rt;
      };
      for (int l = 1; l <= Nmx; l++) {
        vector<int> p (l);
        for (int i = 0; i < l; i++) {
          p[i] = i;
        }
        for (int id = 0; next_permutation(p.begin(), p.end()); id++) {
          for (int i = 0; i < (1 << l); i++) {
            shuf[l][id][i] = arrange(i, l, p);
          }
        }
      }
    }
    
    
    bool check(int n, const vector<int>& tb) {
      static int vis[1 << Nmx], dfc = 0;
      ++dfc;
      for (auto x : tb) {
        vis[x] = dfc;
      }
      for (int i = 0; i < fac[n] - 1; i++) {
        bool flag = true;
        for (int j = 0; j < (signed) tb.size() && flag; j++) {
          flag = vis[shuf[n][i][tb[j]]] == dfc;
        }
        if (flag) {
          return false;
        }
      }
      return true;
    }
    
    int ans = 0;
    vector<int> stk;
    void dfs(int l, int d, int k, int ls) {
      if (d == k) {
        ans += check(l, stk);
        if (ans > 1000 * fac[l]) {
          throw 1;
        }
        return;
      }
      for (int s = ls + 1; s < (1 << l); s++) {
        stk[d] = s;
        dfs(l, d + 1, k, s);
      }
    }
    
    int n, k;
    int table[6][20];
    
    int main() {
      prepare();
      for (int k = 1; k <= 5; k++) {
        for (int n = k; n <= (1 << (k - 1)); n++) {
          ::k = k;
          ::n = n;
          ::ans = 0;
          stk.resize(n);
          try {
            dfs(k, 0, n, -1);
          } catch(int) {
            ans = 1001 * fac[k];
          }
          cout << k << " " << n << '
    ';
          ans /= fac[k];
          table[k][n] = ans;
        }
      }
      cout << "{{}";
      for (int i = 1; i <= 5; i++) {
        cout << ",
     {";
        cout << table[i][0];
        for (int j = 1; j <= 16; j++) {
          cout << ", " << table[i][j];
        }
        cout << "}";
      }
      cout << "};
    ";
      return 0;
    }
    */
    
    const int table[6][20] = {{},
      {1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 4, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 36, 108, 220, 334, 384, 0, 0, 0, 0, 0, 0, 0, 0},
      {0, 0, 0, 0, 0, 976, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001}};
    
    #define ll long long
    
    vector<int> G (105);
    void prepare() {
      G[1] = 0;
      for (int n = 2; n <= 100; n++) {
        for (int k = 1; k < n; k++) {
          if ((1 << k) >= n && ((1 << k) - n) >= G[k]) {
            G[n] = k;
            break;
          }
        }
      }
    }
    
    int g(ll n) {
      if (n <= 100) {
        return G[n];
      }
      for (int k = 1; ; k++) {
        if ((1ll << k) >= n && ((1ll << k) - n) >= g(k)) {
          return k;
        }
      }
      assert(false);
      return -1;
    }
    
    int solve(ll n, ll k) {
      if (n < k) {
        swap(n, k);
      }
      if (k < 63 && (1ll << k) - n < n) {
        return solve((1ll << k) - n, k);
      }
      if (k > 5) {
        return 1001;
      }
      return k == 0 ? 1 : table[k][n];
    }
    
    int main() {
      ll n;
      prepare();
      scanf("%lld", &n);
      int ans = solve(n, g(n));
      ans += (n == 4);
      ans += (n == 7) * 2;
      printf("%d
    ", (ans > 1000) ? -1 : ans);
      return 0;
    }

  • 相关阅读:
    LINQ学习心得分享(一)LINQ简介和基础学习
    线程实用解析(三)线程的同步
    线程实用解析(一)线程初识
    线程实用解析(四)异步操作
    Serialization全解析
    常用排序算法解析
    正则表达式全解析+常用示例
    线程实用解析(二)创建调用有参函数的线程和线程池简介
    线程实用解析(五)BackgroundWorker和Timer
    metro 拖动元素 元素 GIS
  • 原文地址:https://www.cnblogs.com/yyf0309/p/agc044.html
Copyright © 2020-2023  润新知