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]\) 即可
- 若 \(now-v\) 取 \(a\),将已知的 \(dp[now][i]\) 和 \(dp[v][j]\) 合并
比赛时也要类似想法,但并未尝试,主要还是时间不足,加上没有充足信心。
#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]\) 范围内,即合理。
比赛时稍微出现小问题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;
}
这场总体表现比上一场要好一些,部分问题有所改善,但前期被卡还是难顶。
每场比赛都会反映队伍总体的状态,希望我们能在比赛中调整好心态,不有太大的压力。