23.7.16 清华大学训练营预选赛部分题解

J - Histogram Matching

题目大意

你有一个宽度为 n n n 的图形,用 a i a_i ai 表示 i i i 位置上的高度,此为你要找的目标,再给你一个宽度为 m m m 的图形(保证 m ≥ n m\ge n mn),用 b i b_i bi 表示 i i i 位置上的高度,你需要在这个图形上确定一个区间 [ l , r ] [l,r] [l,r] 使在其一定高度以上的图形按顺序重新拼起来之后与第一个图形一样,高度小于等于你选的高度的位置会被暂时忽略,你选的高度可以为负数,问你是否有方案可以满足,并输出可行方案最后形成拼接的图形的有哪几个

解题思路

先想想如果暴力去做应该怎么做:

枚举左端点,可以确定选择的高度,然后暴力向后验证是否可行

然后时间复杂度是 O ( n m 2 ) O(nm^2) O(nm2),必然超时

考虑如何优化:

本来是想着可不可以用分治一类的想法在每 1 e 4 1e4 1e4 的区间内处理完之后再合并答案

然后并没有想到可以怎么合并答案,但本着万一能过的想法,不合并直接提交,于是就ac了…

     #include <bits/stdc++.h>
     using namespace std;
     const int N = 1e5 + 9;
     int n, m, a[N], b[N], c[N];
     void sol(int l, int r) {
        for (int i = l; i <= r - n + 1; ++ i) {
            int k = i, num = 0, p = b[i] - a[1];
            for (int j = 1; j <= n; ++ j) {
                while (b[k] - p <= 0) {
                    ++ k;
                    if (r - k + 1 < n - num) break;
                }
                if (r - k + 1 < n - num) break;
                if (b[k] - p == a[j]) {
                    c[++ num] = k;
                    ++ k;
                    if (r - k + 1 < n - num) break;
                    continue;
                }
                break;
            }
            if (num == n) {
                printf("YES\n");
                for (int i = 1; i <= n; ++ i) printf("%d ", c[i]);
                exit(0);
            }
        }
     }
     int main() {
        scanf("%d", &n);
        for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
        scanf("%d", &m);
        for (int i = 1; i <= m; ++ i) scanf("%d", &b[i]);
        if (m > 1e4) {
            for (int i = 1; i <= m / 1e4; ++ i)
                sol((i - 1) * 1e4 + 1, i * 1e4);
        } else sol(1, m);
        printf("NO");
        return 0;
     }

只能说数据太水了

可以发现每次在往后暴力验证可行性的时候有很多无效操作,从这里出发思考能否优化

因为数据范围较大,可用st表预处理出区间最大值,然后在查询时二分寻找后方第一个大于所给高度的点验证

时间复杂度为 O ( n m ∗ l o g m ) O(nm*logm) O(nmlogm),可以用一些奇妙的构想发现必然无法跑满

code

 #include <bits/stdc++.h>
 using namespace std;
 const int N = 1e5 + 9;
 int n, m, a[N], b[N], c[N], mx[N][18];
 int pd(int l, int r, int p) {
 //判断[l,r]最大值是否比选的高度p大,存在往左判断,不存在往右判断,就可以找到第一个大于p的位置
    int res = log2(r - l + 1);
    if (max(mx[l][res], mx[r - (1 << res) + 1][res]) > p)
        return 1;
    return 0;
 }
 int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
    scanf("%d", &m);
    for (int i = 1; i <= m; ++ i) scanf("%d", &b[i]), mx[i][0] = b[i];
    for (int j = 1; j <= 16; ++ j)//预处理st表
        for (int i = 1; i <= m; ++ i)
            if (i + (1 << (j - 1)) <= m)
                mx[i][j] = max(mx[i][j - 1], mx[i + (1 << (j - 1))][j - 1]);
            else mx[i][j] = mx[i][j - 1];
    for (int i = 1; i <= m - n + 1; ++ i) {
        int k = i + 1, num = 0, p = b[i] - a[1];
        c[++ num] = i;//存储可能答案
        for (int j = 2; j <= n; ++ j) {
            int l = k, r = m;
            while (l <= r) {
                int mid = (l + r) >> 1;
                if (pd(l, mid, p)) r = mid - 1;
                else l = mid + 1;
            }
            if (b[l] - p == a[j]) {
                c[++ num] = l;
                k = l + 1;
                if (m - k + 1 < n - num) break;
                continue;
            }
            break;//如果没有找到下一个位置直接退出
        }
        if (num == n) {//匹配上了,输出
            printf("YES\n");
            for (int i = 1; i <= n; ++ i) printf("%d ", c[i]);
            exit(0);
        }
    }
    printf("NO");
    return 0;
 }

I - Collecting Artifacts

题目大意

有一棵大小为 n n n 的树,有 m m m 种物品,每个点上有一种或者没有物品,每条边有边权,问最少要走多少路才能集齐所有物品或者无法集齐

解题思路

由于 m m m 只有 6 6 6,会容易想到可以状压,用 1 < < ( i − 1 ) 1<<(i-1) 1<<(i1) 表示取了第 i i i 种物品

考虑 f i , j f_{i,j} fi,j 表示经过 i i i,收集装压后为 j j j 的物品

然后经过尝试你会发现还是无法维护,因此需要再开一维标记情况

可以发现最后需要走的点一定也是一棵树,答案是这棵数边权之和的两倍减去直径

第三维用 k k k 表示, k = 0 k=0 k=0 表示在当前点子树内每条边走两遍的答案

k = 1 k=1 k=1 表示在当前点子树内某个点的子树内每条边走两遍并且往上走一遍的答案

k = 2 k=2 k=2 表示减去直径后的答案,即当前点子树内的最优答案

最后统计答案就可以了,最后最后,再维护亿点点细节就好啦

code

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 9;
struct lol {int x, y, z;} e[N << 1];
int n, m, a[N], ans, top[N];
long long f[N][1 << 7][3], sum = 0x3f3f3f3f3f3f3f3f;
void ein(int x, int y, int z) {
    e[++ ans].x = top[x];
    e[ans].y = y;
    e[ans].z = z;
    top[x] = ans;
}
void dfs(int x, int fa) {
    for (int i = top[x]; i; i = e[i].x) {
        int y = e[i].y, z = e[i].z;
        if (y == fa) continue;
        dfs(y, x);
        for (int j = 0; j < (1 << m); ++ j) {
            if (a[x] != -1 && (j & (1 << a[x])) == 0) continue;
            int g = ((1 << m) - 1) ^ j;
            for (int k = g; k >= 0; k = (k - 1) & g) {
                f[x][j | k][0] = min(f[x][j | k][0], f[x][j][0] + f[y][k][0] + z * 2);
                f[x][j | k][1] = min(f[x][j | k][1], f[x][j][1] + f[y][k][0] + z * 2);
                f[x][j | k][1] = min(f[x][j | k][1], f[x][j][0] + f[y][k][1] + z);
                f[x][j | k][2] = min(f[x][j | k][2], f[x][j][2] + f[y][k][0] + z * 2);
                f[x][j | k][2] = min(f[x][j | k][2], f[x][j][1] + f[y][k][1] + z);
                f[x][j | k][2] = min(f[x][j | k][2], f[x][j][0] + f[y][k][2] + z * 2);
                if (k == 0) break;
            }
        }
        for (int j = 0; j < (1 << m) - 1; ++ j)//巧妙的处理,比较抽象
			for (int k = 0; k <= 2; ++ k)
				for (int h = 0; h < m; ++ h) {
					if (j & (1 << h)) continue;
					f[x][j][k] = min(f[x][j][k], f[x][j ^ (1 << h)][k]);
				}
    }
    sum = min(sum, f[x][(1 << m) - 1][2]);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; ++ i) scanf("%d", &a[i]), -- a[i];//记得减1方便处理
    for (int i = 1, u, v, w; i < n; ++ i)
        scanf("%d%d%d", &u, &v ,&w), ein(u, v, w), ein(v, u, w);
    memset(f, 0x3f, sizeof f);//初始化
    for (int i = 1; i <= n; ++ i)
        if (a[i] == -1)//没有物品
            for (int j = 0; j <= 2; ++ j)
                f[i][0][j] = 0;
        else for (int j = 0; j <= 2; ++ j)
                f[i][1 << a[i]][j] = 0;
    dfs(1, 0);
    if (sum == 0x3f3f3f3f3f3f3f3f) printf("-1\n");
    else printf("%lld\n", sum);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值