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}\) 的,所以我们分段枚举:

  1. 枚举 \(|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]))
        }
    
  2. 枚举 \(|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\)

如果这题要求的是每个节点都是孤立点,那就是没有上司的舞会的简化版了属于是。

现在有可能有边相连,那么我们尝试再加一下状态:

  1. \(dp[x][0]\):以 \(x\) 为根节点,且不选择 \(x\) 的情况下的最大节点数
  2. \(dp[x][2]\):以 \(x\) 为根节点,选择 \(x\) 且不选择任何 \(x\) 的儿子的最大节点数
  3. \(dp[x][1]\):以 \(x\) 为根节点,选择 \(x\) 且恰好选了 \(x\) 的一个儿子的最大节点数(没儿子的情况下就记为 1 就行了)

那么,我们有:

\[\begin{cases} dp[x][0]=\sum\limits_{y\in G[x]}\max(dp[y][0],dp[y][1], dp[y][2]) \\ dp[x][2]=1+\sum\limits_{y\in G[x]}dp[y][0] \\ dp[x][1]=\max\limits_{y\in G[x]}(dp[x][2]-dp[y][0]+dp[y][2]) \end{cases} \]

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;
}
posted @ 2022-08-12 21:29  cyhforlight  阅读(131)  评论(0编辑  收藏  举报