• 2020杭电多校第二场题解


    2020 Multi-University Training Contest 2


    1001 Total Eclipse

    并查集。由于每次选择最大的连通块,所以连通块每次选择最小的点,删除后选择新的连通块组继续操作。

    对于每个连通块,用并查集反向处理连通块即可。

    • 将当前最大的点加入图,并删除该点,连通块数加一
    • 遍历该点的边,每与一个不同的集合合并,连通块数减一
    • 每次贡献为连通块数 * (该点的权值 - 下一个点的权值)

    本来应该是签到题结果最后一小时才出,而且还WA3。

    确实是思维不足,把题目想得太复杂了,而且队友错误思想很容易形成干扰。

    这种题目最好还是多独立思考,或许冷静后很快就能想出。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    typedef pair<int, int> pii;
    const int maxn = 1e5 + 5;
    int b[maxn], f[maxn];
    bool vis[maxn];
    vector<int> E[maxn];
    vector<int> V[maxn];
    
    int main() {
    	int t;
    	scanf("%d", &t);
    
    	function<int(int)> find;
    	find = [&](int x)->int {
    		while (x != f[x])
    			x = f[x] = f[f[x]];
    		return x;
    	};
    
    	while (t--) {
    		int n, m;
    		scanf("%d%d", &n, &m);
    		for (int i = 1; i <= n; i++) {
    			scanf("%d", &b[i]);
    			E[i].resize(0);
    			V[i].resize(0);
    			vis[i] = false;
    			f[i] = 0;
    		}
    		int u, v;
    		for (int i = 1; i <= m; i++) {
    			scanf("%d%d", &u, &v);
    			E[u].push_back(v);
    			E[v].push_back(u);
    		}
    
    		int k = 0;
    		function<void(int)> dfs;
    		dfs = [&](int now) {
    			vis[now] = true;
    			V[k].push_back(now);
    			for (auto it : E[now]) {
    				if (vis[it]) continue;
    				dfs(it);
    			}
    		};
    
    		LL ans = 0;
    		for (int i = 1; i <= n; i++) {
    			if (vis[i]) continue;
    			k++;
    			dfs(i);
    			V[k].push_back(0);
    			sort(V[k].begin(), V[k].end(), [&](const int o1, const int o2) {
    				return b[o1] > b[o2];
    			});
    			LL cnt = 0;
    			for (int j = 0; j < (int)V[k].size();) {
    				if (!V[k][j]) break;
    				int p = j;
    				while (++j < (int)V[k].size() && V[k][j] == V[k][j - 1])
    					;
    				for (int g = p; g < j; g++) {
    					int now = V[k][g];
    					f[now] = now;
    					cnt++;
    					for (auto it : E[now]) {
    						if (!f[it])
    							continue;
    						if (find(now) != find(it))
    							f[find(now)] = find(it),
    							cnt--;
    					}
    				}
    				ans = ans + cnt * (b[V[k][p]] - b[V[k][j]]);
    			}
    		}
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    

    1005 New Equipments

    只要求出每个二次函数上最小 (n) 个点的值,得到 (n^2) 个点,然后进行匹配就能得到最优解。
    可以使用最小费用最大流求解问题,只要根据点所在函数和横坐标建边,跑 (n) 次最大流即可。

    最小费用最大流模板题,比赛时没时间开题,还是前期被卡后期时间不足的问题。

    #include <cstdio>
    #include <cstring>
    #include <vector>
    #include <algorithm>
    #include <queue>
    #define SZ(v) (int)v.size()
    #define pii pair<ll,ll>
    #define fi first
    #define se second
    #define ll long long
    using namespace std;
    const ll INF = 1e16;
    const int maxn = 3010;
    const int maxm = 100010;
    struct MCMF {
    	struct Edge {
    		ll v, cap, cost, rev;
    	};
    	ll flow, cost, s, t, n;
    	ll dist[maxn], H[maxn], pv[maxn], pe[maxn];
    	std::vector<Edge> G[maxn];
    	bool dijkstra() {
    		std::priority_queue<pii, std::vector<pii>, std::greater<pii> > q;
    		std::fill(dist, dist + n + 1, INF);
    		dist[s] = 0; q.push({ 0, s });
    		while (!q.empty()) {
    			pii x = q.top(); q.pop();
    			ll& u = x.se;
    			if (dist[u] < x.fi) continue;
    			for (int i = 0; i < SZ(G[u]); ++i) {
    				Edge& e = G[u][i];
    				ll& v = e.v;
    				pii y(dist[u] + e.cost + H[u] - H[v], v);
    				if (e.cap > 0 && dist[v] > y.fi) {
    					dist[v] = y.fi;
    					pe[v] = i, pv[v] = u;
    					q.push(y);
    				}
    			}
    		}
    
    		if (dist[t] == INF) return false;
    		for (int i = 0; i <= n; ++i) H[i] += dist[i];
    
    		ll f = INF;
    		for (int v = t; v != s; v = pv[v]) f = std::min(f, G[pv[v]][pe[v]].cap);
    
    		flow += f;
    		cost += f * H[t];
    
    		for (int v = t; v != s; v = pv[v]) {
    			Edge& e = G[pv[v]][pe[v]];
    			e.cap -= f;
    			G[v][e.rev].cap += f;
    		}
    
    		return true;
    	}
    	void solve(int s, int t) {
    		this->s = s, this->t = t;
    		flow = cost = 0;
    		std::fill(H, H + n + 1, 0);
    		while (dijkstra());
    	}
    	void ctu() {
    		while (dijkstra());
    	}
    	void init(int n) {
    		this->n = n;
    		for (int i = 0; i <= n; ++i) G[i].clear();
    	}
    	void addEdge(int u, int v, int cap, ll cost) {
    		G[u].push_back({ v, cap, cost, SZ(G[v]) });
    		G[v].push_back({ u, 0, -cost, SZ(G[u]) - 1 });
    	}
    
    } mcmf;
    struct point {
    	ll x, y, id;
    	point() {}
    	point(ll x_, ll y_, ll id_) : x(x_), y(y_), id(id_) {}
    	bool operator < (const point& k) const {
    		return y == k.y ? x < k.x : y < k.y;
    	}
    };
    int n; ll m;
    ll a[55], b[55], c[55];
    vector<point> p;
    int uni[maxn];
    ll ans[55];
    int main() {
    	int t; scanf("%d", &t);
    	while (t--) {
    		scanf("%d %lld", &n, &m);
    		for (int i = 0; i < n; ++i) scanf("%lld %lld %lld", &a[i], &b[i], &c[i]);
    		p.clear();
    		int tot = n;
    		tot += 2;
    		for (int i = 0; i < n; ++i) {
    			double dx = -0.5 * b[i] / a[i];
    			int l = floor(dx), r = ceil(dx);
    			if (l == r) ++r;
    			if (l > m) l = m;
    			if (r <= 0) r = 1;
    			int cnt = tot;
    			while (cnt > 0) {
    				if (l > 0) {
    					p.emplace_back(point(l, a[i] * l * l + b[i] * l + c[i], i));
    					--l; --cnt;
    				}
    				if (r <= m) {
    					p.emplace_back(point(r, a[i] * r * r + b[i] * r + c[i], i));
    					++r; --cnt;
    				}
    				if (l <= 0 && r > m) break;
    			}
    		}
    		sort(p.begin(), p.end());
    		int s = 0, ed = 3000;
    		mcmf.init(ed + 1);
    		for (int i = 0; i < p.size(); i++) {
    			uni[i] = p[i].x;
    		}
    		int cntt = p.size();
    		sort(uni, uni + cntt);
    		cntt = unique(uni, uni + cntt) - uni;
    		for (int i = 0; i < p.size(); i++) {
    			p[i].x = lower_bound(uni, uni + cntt, p[i].x) - uni;
    		}
    		for (int i = 1; i <= n; i++) {
    			mcmf.addEdge(1, 1 + i, 1, 0);
    		}
    		for (int i = 0; i < p.size(); i++) {
    			mcmf.addEdge(1 + p[i].id + 1, n + 2 + p[i].x, 1, p[i].y);
    		}
    		for (int i = 0; i < cntt; i++) {
    			mcmf.addEdge(i + n + 2, 3000, 1, 0);
    		}
    		for (int i = 0; i < n; i++) {
    			mcmf.addEdge(0, 1, 1, 0);
    			if (i == 0) mcmf.solve(s, ed);
    			else mcmf.ctu();
    
    			if (i == n - 1) printf("%lld
    ", mcmf.cost);
    			else printf("%lld ", mcmf.cost);
    		}
    	}
    	return 0;
    }
    

    1006 The Oculus

    预处理出 (fib[i]\%mod) 的值,且令 (fib[i]\%mod) 的值不同

    已知斐波那契数为 ([1,2e6+5]),所以可以预处理 (mod) 是否每个斐波数的模数不同

    根据斐波那契数编码,将 (a、b、c) 求余 (mod) 的值计算出来出来

    由于 (fib[i]\%mod) 的值各不相同,则存在唯一值使得 ((c+fib[i])\%mod=a*b\%mod)

    比赛时想法不是很准确,然后WA2,只不过最后还是出了,下次还是要思考充分一些。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    const LL mod = 3799912185593857;
    const int maxn = 2e6 + 5;
    LL fib[maxn];
    int a[maxn];
    
    int main() {
    	fib[0] = fib[1] = 1;
    	for (int i = 2; i < maxn; i++) {
    		fib[i] = fib[i - 1] + fib[i - 2];
    		if (fib[i] >= mod) fib[i] -= mod;
    	}
    
    	int t;
    	scanf("%d", &t);
    	while (t--) {
    		int n, x, p, q;
    		scanf("%d", &p);
    		LL a = 0;
    		for (int i = 1; i <= p; i++) {
    			scanf("%d", &x);
    			if (x) {
    				a = a + fib[i];
    				if (a >= mod) a -= mod;
    			}
    		}
    		scanf("%d", &q);
    		LL b = 0;
    		for (int i = 1; i <= q; i++) {
    			scanf("%d", &x);
    			if (x) {
    				b = b + fib[i];
    				if (b >= mod) b -= mod;
    			}
    		}
    		scanf("%d", &n);
    		LL c = 0;
    		for (int i = 1; i <= n; i++) {
    			scanf("%d", &x);
    			if (x) {
    				c = c + fib[i];
    				if (c >= mod) c -= mod;
    			}
    		}
    		LL ans = __int128(a) * b % mod;
    		for (int i = 1; i <= max(n, p + q + 1); i++) {
    			LL temp = c + fib[i];
    			if (temp >= mod) temp -= mod;
    			if (temp == ans) {
    				printf("%d
    ", i);
    				break;
    			}
    		}
    	}
    	return 0;
    }
    

    1007 In Search of Gold

    • 由于求树上最小的最长路径考虑用二分,二分最小直径为 (mid)

    • (dp) 验证最小直径

      (dp[i][j]) 表示以 (i) 为根且有 (j)(a) 边的子树中,所有符合条件的直径上,最长的到 (i) 点的点的距离

      以根节点为 (now) 的树为例,新搜索了一棵根节点为 (v) 的子树

      • (now-v)(a),将已知的 (dp[now][i])(dp[v][j]) 合并
        • 若合并大于 (mid),则 (now-v = a), 有 (i+j+1)(a) 边的树中必定存在直径非法
        • 若合并小于等于 (mid),则取 (max(dp[now][i],dp[v][j]+a)) 为最远的点
      • (now-v)(b)
        • 若合并大于 (mid),则 (now-v = b), 有 (i+j)(a) 边的树中必定存在直径非法
        • 若合并小于等于 (mid),则取 (max(dp[now][i],dp[v][j]+b)) 为最远的点
      • (a,b) 边的两种情况取 (min),为最小的最远的点

      验证 (dp[root][1]) 即可

    比赛时也要类似想法,但并未尝试,主要还是时间不足,加上没有充足信心。

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long LL;
    const int maxn = 2e4 + 5;
    int head[maxn], tot;
    struct Edge {
        int v;
        int next;
        int a;
        int b;
    } edge[maxn << 1];
    inline void AddEdge(int u, int v, int a, int b) {
        edge[++tot].v = v;
        edge[tot].a = a;
        edge[tot].b = b;
        edge[tot].next = head[u];
        head[u] = tot;
    }
    
    int m_size[maxn];
    LL dp[maxn][21];
    int main() {
        int t;
        scanf("%d", &t);
        while (t--) {
            int n, k;
            scanf("%d%d", &n, &k);
            memset(head, 0, sizeof(int) * (n + 1));
            tot = 0;
            int u, v, a, b;
            for (int i = 1; i < n; i++) {
                scanf("%d%d%d%d", &u, &v, &a, &b);
                AddEdge(u, v, a, b);
                AddEdge(v, u, a, b);
            }
    
            function<void(int now, int fa)> dfs;
            LL left = 0, right = LL(n) * (0X3f3f3f3f), mid, ans = 1e18;
            LL cnt[22];
            dfs = [&](int now, int fa) {
                if (!edge[head[now]].next && fa) {
                    m_size[now] = dp[now][0] = 0;
                    return;
                }
    
                m_size[now] = 0;
                dp[now][0] = 0;
                for (int i = head[now]; i; i = edge[i].next) {
                    int v = edge[i].v;
                    int a = edge[i].a;
                    int b = edge[i].b;
                    if (v == fa)
                        continue;
                    dfs(v, now);
                    memset(cnt, 0X3f, sizeof(cnt));
                    for (int i = 0; i <= m_size[now]; i++) {
                        for (int j = 0; j <= m_size[v] && i + j <= k; j++) {
                            if (dp[now][i] + dp[v][j] + a <= mid)
                                cnt[i + j + 1] = min(cnt[i + j + 1], max(dp[now][i], dp[v][j] + a));
                            if (dp[now][i] + dp[v][j] + b <= mid)
                                cnt[i + j] = min(cnt[i + j], max(dp[now][i], dp[v][j] + b));
                        }
                    }
                    m_size[now] = min(m_size[now] + m_size[v] + 1, k);
                    for (int i = 0; i <= m_size[now]; i++)
                        dp[now][i] = cnt[i];
                };
            };
            while (left <= right) {
                mid = (left + right) >> 1;
                dfs(1, 0);
                if (dp[1][k] <= mid) {
                    right = mid - 1;
                    ans = min(ans, mid);
                } else
                    left = mid + 1;
            }
            printf("%lld
    ", ans);
        }
        return 0;
    }
    

    1009 It's All Squares

    本题实质是一个算复杂度的问题 。显然最大图形为 (400*400),有 (4e6/4/400=2500) 个图形, (400*400*2500=4e8) 换言之,矩形每个可以用 (O(n*m)) 复杂度完成。

    对于每个多边形处理出 (x_{min},x_{max},y_{min},y_{max}) ,然后每行处理出所有竖的边界,在两个相邻边界内的就是在多边形内

    (对边界排序可以用基数,但是 (sort) 也可以 (qwq))。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 405;
    const int inf = 0X3f3f3f3f;
    int w[maxn][maxn];
    
    char s[maxn * maxn];
    int line[maxn][maxn], top[maxn];
    
    bool a[maxn * maxn];
    int cnt[maxn * maxn];
    
    int main() {
        int t;
        scanf("%d", &t);
        while (t--) {
            int n, m, q;
            scanf("%d%d%d", &n, &m, &q);
            for (int i = 1; i <= n; i++)
                for (int j = 1; j <= m; j++)
                    scanf("%d", &w[i][j]);
    
            for (int l = 1; l <= q; l++) {
                int x, y;
                scanf("%d%d", &x, &y);
                scanf("%s", s + 1);
                int len = strlen(s + 1);
                int min_x = x, max_x = x, min_y = y, max_y = y;
                for (int i = 1; i <= len; i++) {
                    if (s[i] == 'L')
                        x--;
                    else if (s[i] == 'R')
                        x++;
                    else if (s[i] == 'D')
                        y--;
                    else
                        y++;
                    min_x = min(min_x, x);
                    max_x = max(max_x, x);
                    min_y = min(min_y, y);
                    max_y = max(max_y, y);
                    if (s[i] == 'L')
                        line[x + 1][++top[x + 1]] = y;
                    else if (s[i] == 'R')
                        line[x][++top[x]] = y;
                }
    
                int g = 0;
                for (int i = min_x + 1; i <= max_x; i++) {
                    if (!top[i])
                        continue;
                    sort(line[i] + 1, line[i] + top[i] + 1);
    
                    for (int j = 1; j <= top[i]; j += 2) {
                        for (int k = line[i][j] + 1; k <= line[i][j + 1]; k++) {
    
                            if (!a[w[i][k]]) {
                                a[w[i][k]] = true;
                                cnt[++g] = w[i][k];
                            }
                        }
                    }
                    top[i] = 0;
                }
                for (int i = 1; i <= g; i++)
                    a[cnt[i]] = false;
                printf("%d
    ", g);
            }
        }
        return 0;
    }
    

    1010 Lead of Wisdom

    (n) 个装备 (k)((n,kleq 50)) ,所以装备方案最多为 (3^{16}*2),所以直接爆搜+剪枝。

    剪枝可以剪下界,(事实上,不剪也可以过)用每种装备最大的 (a,b,c,d) 代表每种装备的最优取值,用后缀表示最优装备的后缀,在搜索过程中若采用最优后缀仍无法超过已知最优解,则可以剪枝。

    前期剪枝出了一些问题,然后debug花了比较多时间,期间WA4。总体还是因为签到没过导致压力大,过于焦急。

    下次debug时要注意冷静,否则会事倍功半。

    #include<bits/stdc++.h>
    #define ll long long
    #define maxn 100010
    using namespace std;
    ll ma[100][5];
    ll sum[100][5];
    ll val[100][5];
    ll ans = 0;
    int n, k;
    vector<int>b[100];
    void sol(int x, ll a1, ll a2, ll a3, ll a4) {
    	ans = max(ans, (a1 + 100) * (a2 + 100) * (a3 + 100) * (a4 + 100));
    	if (x == k) return;
    	if (ans >= (a1 + sum[x][0] + 100) * (a2 + sum[x][1] + 100) * (a3 + sum[x][2] + 100) * (a4 + sum[x][3] + 100)) return;
    	for (int i = 0; i < b[x + 1].size(); i++) {
    		int j = b[x + 1][i];
    		sol(x + 1, a1 + val[j][0], a2 + val[j][1], a3 + val[j][2], a4 + val[j][3]);
    	}
    	sol(x + 1, a1, a2, a3, a4);
    }
    
    int main() {
    	int t;
    	scanf("%d", &t);
    	while (t--) {
    		scanf("%d%d", &n, &k);
    		memset(sum, 0, sizeof(sum));
    		memset(val, 0, sizeof(val));
    		memset(ma, 0, sizeof(ma));
    		for (int i = 0; i <= k; i++) b[i].clear();
    		for (int i = 0; i < n; i++) {
    			int x;
    			scanf("%d", &x);
    			for (int j = 0; j < 4; j++) {
    				scanf("%lld", &val[i][j]);
    				ma[x][j] = max(ma[x][j], val[i][j]);
    			}
    			b[x].push_back(i);
    		}
    		for (int i = k - 1; i >= 0; i--)
    			for (int j = 0; j < 4; j++) 
    				sum[i][j] = sum[i + 1][j] + ma[i + 1][j];
    		ans = 0;
    		sol(0, 0, 0, 0, 0);
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    

    1012 String Distance

    由于 (m) 串只有 (20) ,首先预处理 (n) 串每个字符后面第 (i) 个字符的位置。

    对于每次询问搜索 (LCS) ,用 (dp[i][j]) 表示 (LCS)(i) 位是 (m) 串第 (j) 位时,对应字符在 (n) 串的位置,若在([left,right]) 范围内,即合理。

    [egin{gather*} dp[i+1][j]=min(w[dp[i][j-k]][p[k]-'a'])(1leq k <j) end{gather*} ]

    比赛时稍微出现小问题WA1,发现问题后就解决了。

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 1e5 + 5;
    const int inf = 0X3f3f3f3f;
    char s[maxn], p[30];
    int w[maxn][26];
    int dp[21][21];
    
    int main() {
    	int t;
    	scanf("%d", &t);
    	while (t--) {
    		scanf("%s%s", s + 1, p + 1);
    		int n = strlen(s + 1);
    		int m = strlen(p + 1);
    		for (int i = 0; i < 26; i++)
    			w[n][i] = inf;
    		for (int i = n - 1; i >= 0; i--) {
    			for (int j = 0; j < 26; j++)
    				w[i][j] = w[i + 1][j];
    			w[i][s[i + 1] - 'a'] = i + 1;
    		}
    		int q;
    		scanf("%d", &q);
    		while (q--) {
    			int left, right;
    			scanf("%d%d", &left, &right);
    			int ans = 0;
    			memset(dp, 0X3f, sizeof(dp));
    			for (int i = 1; i <= m; i++)
    				dp[1][i] = w[left - 1][p[i] - 'a'];
    			for (int i = 1; i <= m; i++) {
    				for (int j = 1; j <= m; j++) {
    					if (dp[i][j] <= right) {
    						ans = max(ans, i);
    						for (int k = j + 1; k <= m; k++)
    							dp[i + 1][k] = min(dp[i + 1][k], w[dp[i][j]][p[k] - 'a']);
    					}
    				}
    			}
    			ans = right - left + 1 - ans + m - ans;
    			printf("%d
    ", ans);
    
    		}
    	}
    	return 0;
    }
    

    这场总体表现比上一场要好一些,部分问题有所改善,但前期被卡还是难顶。

    每场比赛都会反映队伍总体的状态,希望我们能在比赛中调整好心态,不有太大的压力。

  • 相关阅读:
    Vocabulary Recitation 2020/05/06
    java set TreeSet详解
    vue快速入门~必备基础知识(一)下~过滤器
    vue快速入门~必备基础知识(一)上
    vue入门(二)基于前面的基础的一个小Demo
    Annotation注释详解~
    泛型~详解~
    Collections工具类详解
    TreeMap实现类和SortMap接口
    Map集合-主要HashMap和Hashtable
  • 原文地址:https://www.cnblogs.com/st1vdy/p/13368701.html
Copyright © 2020-2023  润新知