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\n", 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\n", 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\n", 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\n", 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\n", g);
        }
    }
    return 0;
}

1010 Lead of Wisdom

\(n\) 个装备 \(k\)\((n,k\leq 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\n", ans);
	}
	return 0;
}

1012 String Distance

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

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

\[\begin{gather*} dp[i+1][j]=min(w[dp[i][j-k]][p[k]-'a'])(1\leq 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\n", ans);

		}
	}
	return 0;
}

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

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

posted @ 2020-07-23 20:58  st1vdy  阅读(501)  评论(0编辑  收藏  举报