2022 杭电多校 第八场 题解
1001 A题 Theramore(思维)
给定一张长度为 \(n\) 的 01串,我们可以进行若干次操作,每次操作都选定一个长度为奇数的连续子串并将其翻转(如 01011 变为 11010),要求我们在进行若干次操作后使得该串的字典序最小。
\(n\leq 10^5\)
只能对奇数串反转,这意味奇数位的值永远不会到达偶数位上。例如选定了一个起始位置 \(i\),长度 \(2k+1\) 的子串 \([i,i+2k]\),那么 \(i,i+2k\) 都是同奇同偶,位置 \(i\) 上的这个数无论进行多少次操作,最终的位置 \(j\) 的奇偶性仍然和 \(i\) 一致。
那么,我们只需要将原串拆成奇偶两部分,那么这两部分都是可以随意操控的(每次只选择长度为 3 的串进行反转,就相当于奇数位上面的两个数临项交换,中间夹着的那个偶数位的数不动,或者反过来),因此我们直接在两部分上都是将 1 往后放,最后重新组起来就行,时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
char s[N];
void solve()
{
scanf("%s", s + 1);
int a[2] = {0, 0};
int n = strlen(s + 1);
for (int i = 1; i <= n; ++i)
if (s[i] == '0') a[i % 2]++;
for (int i = 1; i <= n; ++i)
if (a[i % 2])
putchar('0'), a[i % 2]--;
else
putchar('1');
puts("");
}
int main()
{
int T;
scanf("%d", &T);
while (T--) solve();
return 0;
}
1004 D题 Quel'Thalas(签到)
给定 \(n\),此时平面上有 \((n+1)^2\) 个点,其坐标为 \((i,j)(0\leq i,j\leq n)\),保证 \(i,j\) 是整数。
问,现在至少需要画多少条直线,才能使得除了 \((0,0)\) 外的所有点都至少被一条线覆盖(注意,\((0,0)\) 不能被覆盖)。
\(T\leq 50,0\leq n\leq 50\)
本题可能具体证明有些复杂,但是手玩几组样例(外加看一下飞速上涨的通过人数),基本上可以断定,答案就是 \(2n\)。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T, n;
cin >> T;
while (T--) {
cin >> n;
cout << 2 * n << endl;
}
return 0;
}
1007 G题 Darnassus(数学,最小生成树)
给定一个长度为 \(n\) 的排列 \(\{p_n\}\),将每个位置视为一个点并建立无向图,点 \(i,j\) 间的边权为 \(|i-j|*|p_i-p_j|\),随后求出该图的最小生成树(的权值和)。
\(T\leq 5,n\leq 5*10^4\)
本题需要放缩一下:因为如果只连 \(i,i+1\) 之间的边,那么每条边的边权都是小于等于 \(n-1\) 的,也就是说,该图存在着一种仅包含边权小于等于 \(n-1\) 的边的最小生成树(根据 Kruskal 算法的运行流程可知,我们将这些边从小到大排,选取了前面的 \(n-1\) 条,而既然已经有了一种边权最大值不超过 \(n-1\) 的方案,那么说明更优的方案一定不包含大于 \(n-1\) 的边)。
那么,我们只需要找出所有长度小于等于 \(n-1\) 的边,就可以正常跑最小生成树算法了。因为 \(w(i,j)=|i-j|*(p_i-p_j)\),正常枚举(所有边)是 \(O(n^2)\) 的,所以需要通过上面的性质来优化不必要的枚举。
我们发现,\(w(i,j)=|i-j|*(p_i-p_j)<n\),所以 \(|i-j|,|p_i-p_j|\) 中至少有一个是小于等于 \(\sqrt{n}\) 的,所以我们分段枚举:
-
枚举 \(|i-j|<\sqrt{n}\):
for (int i = 1; i <= n; ++i) for (int j = i + 1; j - i < sqrt(n); ++j) { //(i,j, (j - i) * abs(p[i] - p[j])) }
-
枚举 \(|p_i-p_j|<\sqrt{n}\):
int x[N]; for (int i = 1; i <= n; ++i) x[p[i]] = i; for (int i = 1; i <= n; ++i) for (int j = i + 1; j - i < sqrt(n); ++j) { //(x[i], x[j], (j - 1) * abs(x[i] - x[j])) }
枚举完后,我们得到了 \(O(n\sqrt{n})\) 级别的边,在此基础上跑一次 Kruskal 即可(为了应对杭电的土豆评测机,得尽量消掉一个 log 外加常数,例如用桶代替排序(还必须得是前向星,vector 不行),把并查集的优化都加上去,开读入优化啥的)。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 50010;
int n, p[N], x[N];
vector<pair<int, int> > G[N];
void addEdge(int x, int y, int z) {
if (z < n) G[z].push_back(make_pair(x, y));
}
//Union Set
int fa[N];
int find(int x) {
if (x != fa[x]) fa[x] = find(fa[x]);
return fa[x];
}
//main
LL solve() {
//read
cin >> n;
for (int i = 1; i <= n; ++i) cin >> p[i];
//buildEdge
for (int i = 1; i <= n; ++i) G[i].clear();
for (int i = 1; i <= n; ++i) x[p[i]] = i;
for (int i = 1, s = sqrt(n + 0.5); i <= n; ++i)
for (int j = i + 1; j - i <= s && j <= n; ++j) {
addEdge( i , j , (j - i) * abs(p[i] - p[j]));
addEdge(x[i], x[j], (j - i) * abs(x[i] - x[j]));
}
//Kruskal
for (int i = 1; i <= n; ++i) fa[i] = i;
int cnt = n; LL res = 0;
for (int z = 1; z < n; ++z)
for (auto p : G[z]) {
int x = find(p.first), y = find(p.second);
if (x != y) {
fa[x] = y, res += z;
if (--cnt == 1) return res;
}
}
return -1;
}
int main() {
int T;
cin >> T;
while (T--) cout << solve() << endl;
return 0;
}
1008 H题 Orgrimmar(树形DP)
给定一棵 \(n\) 个节点的树,要求从中选出一些节点,并只保留这些节点之间的边,要求每个节点至多与一条边相连,问能选出的节点数量的最大值。
\(T\leq 10,n\leq 5*10^5\)
如果这题要求的是每个节点都是孤立点,那就是没有上司的舞会的简化版了属于是。
现在有可能有边相连,那么我们尝试再加一下状态:
- \(dp[x][0]\):以 \(x\) 为根节点,且不选择 \(x\) 的情况下的最大节点数
- \(dp[x][2]\):以 \(x\) 为根节点,选择 \(x\) 且不选择任何 \(x\) 的儿子的最大节点数
- \(dp[x][1]\):以 \(x\) 为根节点,选择 \(x\) 且恰好选了 \(x\) 的一个儿子的最大节点数(没儿子的情况下就记为 1 就行了)
那么,我们有:
dfs 扫一遍,复杂度 \(O(n)\),多组数据也能过。
#include<bits/stdc++.h>
using namespace std;
const int N = 500010;
int n, dp[N][3];
vector<int> G[N];
void dfs(int x, int f) {
for (int y : G[x])
if (y != f) dfs(y, x);
dp[x][0] = 0, dp[x][1] = dp[x][2] = 1;
//dp[x][0]
for (int y : G[x])
if (y != f) dp[x][0] += max(dp[y][0], max(dp[y][1], dp[y][2]));
//dp[x][2]
for (int y : G[x])
if (y != f) dp[x][2] += dp[y][0];
for (int y : G[x])
if (y != f)
dp[x][1] = max(dp[x][1], dp[x][2] - dp[y][0] + dp[y][2]);
}
int solve()
{
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
G[i].clear();
for (int i = 1; i < n; ++i) {
int x, y;
scanf("%d%d", &x, &y);
G[x].push_back(y);
G[y].push_back(x);
}
dfs(1, 0);
return max(dp[1][0], max(dp[1][1], dp[1][2]));
}
int main()
{
int size(512<<20); // 512M
__asm__ ( "movq %0, %%rsp\n"::"r"((char*)malloc(size)+size));
// YOUR CODE
int T;
scanf("%d", &T);
while (T--) printf("%d\n", solve());
// OVER
exit(0);
}
1011 K题 Stormwind(枚举)
给定一个 \(n*m\) 的方格矩阵,问现在至多可以横竖切几刀,使得每一块的面积都不小于 \(k\)?
\(T\leq 100,1\leq n,m,k\leq 10^5,nm\geq k\)
说实话,我觉得这题要比 1001 好写多了。
我们考虑一下竖的每一块需要至少多长(例如说 \(i\)),那么竖着至多 \(\lfloor\frac{m}{i}\rfloor\) 块,同时横着每一块的长度也得至少 \(\lceil\frac{k}{i}\rceil\)。根据这个算出横着需要多少块,根据这个算出切的块数,最后找一个最大值就行(注意边界条件,例如发现 \(\lceil\frac{k}{i}\rceil>n\),也就是不切也不够用的情况)。
枚举 \(i\),单组数据复杂度 \(O(m)\),多组复杂度 \(O(Tm)\)。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
LL solve() {
LL n, m, k, ans = 0;
cin >> n >> m >> k;
for (LL i = 1; i <= m; ++i) {
LL j = (k + i - 1) / i;
LL a = m / i - 1, b = n / j - 1;
if (b >= 0) ans = max(ans, a + b);
}
return ans;
}
int main()
{
int T;
cin >> T;
while (T--) cout << solve() << endl;
return 0;
}