8.21 模拟赛

版权声明:_ https://blog.csdn.net/lunch__/article/details/81910395

r e d b a g 的神题直接踩爆我了

T1

这个题看起来比较简单但是模型转化还是比较难想的

考场上我大概想到把答案记为总和减去选定的 n 个互不相同的宽

但是没有跳出这个模型 题解的思想巧妙多了

把每个权值离散后看作一个点,每对限制关系记为一条边

题目就可以转化为在每一条边上选择一个端点,不可以重复选择

求最小权值和

由于答案保证有解 所以最后得到的多个联通块要么是树,要么是基环树

如果是基环树的话,环上的权值是一定要选的,如果是树,就把最大的权值去掉

这些操作我们可以用一个并查集来维护

维护一个集合的最大值和集合内权值和,以及当前集合是不是基环树

最后算答案的时候每个联通块如果不是基环树 就减去联通块最大权值

这样子就做完了 复杂度 O ( n l o g n ) 瓶颈在离散化的排序上

#include<bits/stdc++.h>

using namespace std;

template<class T> inline bool chkmin(T &_, T __) {return _ > __ ? _ = __, 1 : 0;}

typedef long long ll;

const int N = 5e5 + 10;
int a[N], b[N], tmp[N];
int n, cnt, s, t, vis[N];
ll all;

struct Union_Set {
    int fa[N], size[N], mx[N]; ll sum[N];

    void clear() {
        for(int i = 1; i <= cnt; ++ i)
            fa[i] = mx[i] = i, sum[i] = tmp[i], size[i] = 1;
    }

    int find(int x) {
        return fa[x] = x == fa[x] ? x : find(fa[x]);
    }

    void merge(int x, int y) {
        x = find(x), y = find(y);
        -- size[x];
        if(x == y) return;
        sum[x] += sum[y];
        size[x] += size[y];
        mx[x] = max(mx[x], mx[y]);
        fa[y] = x;
    }
}T;

int main() {
#ifndef ONLINE_JUDGE
    freopen("s.in", "r", stdin);
    freopen("s.out", "w", stdout);
#endif
    scanf("%d", &n);
    for(int i = 1; i <= n; ++ i) {
        scanf("%d%d", &a[i], &b[i]);
        tmp[++ cnt] = a[i], tmp[++ cnt]= b[i];
        all += a[i] + b[i];
    }
    sort(tmp + 1, tmp + cnt + 1);
    cnt = unique(tmp + 1, tmp + cnt + 1) - tmp - 1;
    T.clear();
    for(int i = 1; i <= n; ++ i) {
        a[i] = lower_bound(tmp + 1, tmp + cnt + 1, a[i]) - tmp;
        b[i] = lower_bound(tmp + 1, tmp + cnt + 1, b[i]) - tmp;
        T.merge(a[i], b[i]);    
    }
//  cout << T.sum[5] << ' ' << T.mx[5] << endl;
    for(int i = 1; i <= cnt; ++ i)
        if(!vis[T.find(i)]) {
            vis[T.find(i)] = 1;
            if(T.size[T.find(i)] >= 1)
                all -= T.sum[T.find(i)] - tmp[T.mx[T.find(i)]];
            else
                all -= T.sum[T.find(i)];
        }
    cout << all << endl;
    return 0;
}

T2这里写图片描述

这题不能说巧妙了… 简直是神仙题… 昨天晚上要走的时候才调出来 还是对着代码打的
坑点也蛮多的 反正只有我这种菜逼会被坑

第一问 设状态 f [ i ] [ j ] 表示选前 i 个物品达到 j 的容量的最小物品数 直接转移就好了
二维背包忘的差不多了…搞得我按一维的打一直调不出来

第二问 对物品排序后,正着做一遍倒着做一遍,中位数对于前一半是向上取整,后一半是向下取整,只要 f 1 [ i ] [ j ] + f 2 [ i + 1 ] [ v j ] == a n s 1 就可以用 i 更新答案

第三问 二分一个答案,把物品数量相同的放一起,变成一个多重背包,然后把物品数限制在二分的那个答案那里就可以了,这里实际上复杂度并不高,因为如果它要让你的物品种类多,那答案的范围就会小,如果答案范围大,物品种类数就会小,所以是跑不满 O ( n 2 l o g v )

第四问 最神仙的就是这一问了…看了 L S T e t e 的代码才勉强理解

就是之前考过一道贪玩蓝月的题,就是这种思路,用一个栈模拟队列来维护一个支持动态删减物品的背包,首先用双栈模拟队列的操作,进队的话就把元素都放到第一个栈,出队的话如果第二个栈有元素,那么就弹出第二个栈的栈顶,没有的话就第一个栈所有元素压进第二个栈,再弹出栈顶,这样就完成了一个队列的基本操作,我们考虑用这个模拟的队列来维护这个背包,添加物品比较简单,做一遍 O ( v ) d p 就好了,因为 f [ i 1 ] [ j ] 就是 f [ i ] [ j ] 删去第 i 个物品后的状态,这样子直接删的转移是 O ( v ) 的,如果是把第一个栈的元素全部放到第二个栈中,每压过去一个元素对于这个东西做一个 d p ,最后复杂度看不懂…..反正能过就对了

Codes

#include<bits/stdc++.h>

using namespace std;

const int N = 5e3 + 10;
int f1[N][N], f2[N][N];
int n, v, a[N], fuck;

namespace Sub1 {
    void Solve() {
        memset(f1, 0x3f, sizeof(f1));
        f1[0][0] = 0;
        for(int i = 1; i <= n; ++ i) 
            for(int j = 0; j <= v; ++ j) { 
                f1[i][j] = f1[i - 1][j];
                if(j >= a[i]) f1[i][j] = min(f1[i][j], f1[i - 1][j - a[i]] + 1);
            }
        printf("%.12f ", 1.0 * v / (fuck = f1[n][v]));
    }
}

namespace Sub2 {
    void Solve() {
        memset(f2, 0x3f, sizeof(f2));
        f2[n + 1][0] = 0;
        for(int i = n; i >= 1; -- i) 
            for(int j = 0; j <= v; ++ j) { 
                f2[i][j] = f2[i + 1][j];
                if(j >= a[i]) f2[i][j] = min(f2[i][j], f2[i + 1][j - a[i]] + 1);
            }
        for(int i = 1; i <= n; ++ i)    
            for(int j = 0; j <= v; ++ j)
                if(f1[i][j] + f2[i + 1][v - j] == f1[n][v] && f1[i][j] == (f1[n][v] + 1) / 2) 
                    return void(printf("%d ", a[i]));
    }
}

namespace Sub3 {
    int f3[N], tmp[N], cnt[N], num;

    bool check(int x) {
        memset(f3, 0x3f, sizeof(f3));
        f3[0] = 0;
        for(int i = 1; i <= num; ++ i) 
            for(int k = min(cnt[i], x); k >= 1; -- k)
                for(int j = v; j >= tmp[i]; -- j)
                    f3[j] = min(f3[j], f3[j - tmp[i]] + 1);
        return f3[v] == f1[n][v];
    }

    void Solve() {
        for(int i = 1; i <= n; ++ i) tmp[i] = a[i];
        num = unique(tmp + 1, tmp + n + 1) - tmp - 1;
        for(int i = 1; i <= n; ++ i)
            ++ cnt[lower_bound(tmp + 1, tmp + num + 1, a[i]) - tmp];
        int l = 1, r = 0, ans;
        for(int i = 1; i <= num; ++ i) r = max(r, cnt[i]);
        while(l <= r) {
            int mid = (l + r) >> 1;
            if(check(mid)) ans = mid, r = mid - 1;
            else l = mid + 1;
        }
        printf("%d ", ans);
    }
}

namespace Sub4 {
    int f1[N][N], f2[N][N], Sta1[N], Sta2[N], top1, top2;
    int ans = 1e9, l = 1, r = 0; 

    bool Check() {
        for(int i = 0; i <= v; ++ i) 
            if(f1[top1][i] + f2[top2][v - i] == fuck) 
                return true;
        return false;
    }

    void Clear1() {memset(f1, 0x3f, sizeof(f1)); f1[0][0] = 0;}
    void Clear2() {memset(f2, 0x3f, sizeof(f2)); f2[0][0] = 0;}

    void Solve() {
        Clear1(), Clear2();
        //cout << endl;
        while(l <= n && r <= n) {
            //cout << l << ' ' << r << endl;
            if(Check()) {
                ans = min(ans, a[r] - a[l]), ++ l; 
                if(!top2) {
                    Clear2();
                    for(int i = top1; i >= 1; -- i) {
                        Sta2[++ top2] = Sta1[i];
                        for(int j = 0; j <= v; ++ j) f2[top2][j] = f2[top2 - 1][j];
                        for(int j = v; j >= Sta2[top2]; -- j) f2[top2][j] = min(f2[top2][j], f2[top2][j - Sta2[top2]] + 1);
                    }
                    Clear1(); top1 = 0;
                }
                -- top2;
            }
            else {
                Sta1[++ top1] = a[++ r];
                for(int j = 0; j <= v; ++ j) f1[top1][j] = f1[top1 - 1][j];
                for(int j = v; j >= Sta1[top1]; -- j) f1[top1][j] = min(f1[top1][j], f1[top1][j - Sta1[top1]] + 1);
            //  for(int j = 0; j <= v; ++ j) cout << top1 << ' ' << j << ' ' << f1[top1][j] << endl;
            }
        }
        printf("%d\n", ans);
    }
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("b.in", "r", stdin);
    freopen("b.out", "w", stdout);
#endif
    scanf("%d%d", &n, &v);
    for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
    sort(a + 1, a + n + 1);
    Sub1::Solve(); Sub2::Solve(); Sub3::Solve(); Sub4::Solve();
    return 0;
}

T3这里写图片描述

这个题目也是比较巧妙了…

首先枚举需要我们花费最小的边,假设权值为 t

这样子我们做最短路的时候每条边权值都减去 t ,小于 0 就设为 0

把得到的最短路加上 k × t ,取其中答案的最小值即可

为什么这样做是对的 我们分类讨论一下

当经过的边小于 k 的时候,由于取的是最小值,显然是不会对答案有影响的

因为枚举 t = 0 的时候,这样子最优的答案已经被记录下来了

当经过的边大于 k 的时候 如果 t < k 条边的权值 那么可以让 t 更大来让总权值更小,即此时一定不会比最后的答案更优,所以这样子做 m D i j k s t r a 就好了

时间复杂度 O ( n m l o g ( n + m ) )

#include<bits/stdc++.h>

#define x first
#define y second
#define mp make_pair
#define rel(x) (x < 0 ? 0 : x)

using namespace std;
typedef long long ll;
typedef pair<ll, int> PII;

template<class T> inline bool chkmin(T &_, T __) {return _ > __ ? _ = __, 1 : 0;}

const int N = 3000 + 10;
int to[N << 1], head[N], nxt[N << 1], v[N << 1], e;
int n, m, k, tmp[N], cnt;
ll dis[N], ans = 1e18;

void add(int x, int y, int z) {
    to[++ e] = y; nxt[e] = head[x]; head[x] = e; v[e] = z;
}

ll dijistra(int x) {
    priority_queue<PII, vector<PII>, greater<PII> > q;
    for(int i = 1; i <= n; ++ i) dis[i] = 1e18;
    q.push(mp(dis[1] = 0, 1));
    while(!q.empty()) {
        PII k = q.top(); q.pop();
        if(k.x > dis[k.y]) continue;
        for(int i = head[k.y]; i; i = nxt[i])
            if(chkmin(dis[to[i]], dis[k.y] + rel(v[i] - x)))
                q.push(mp(dis[to[i]], to[i]));
    }
    return dis[n];
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("y.in", "r", stdin);
    freopen("y.out", "w", stdout);
#endif
    int x, y, z;
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= m; ++ i) {
        scanf("%d%d%d", &x, &y, &z);
        add(x, y, z), add(y, x, z);
        tmp[++ cnt] = z;
    }
    sort(tmp + 1, tmp + cnt + 1); 
    cnt = unique(tmp + 1, tmp + cnt + 1) - tmp - 1;
    for(int i = 0; i <= cnt; ++ i)
        ans = min(ans, dijistra(tmp[i]) + 1ll * tmp[i] * k);
    printf("%lld\n", ans);
    //cerr << 1.0 * clock() / CLOCKS_PER_SEC << endl;
    return 0;
}

总结一下还是自己太菜了吧…只会写一些自己知道的简单套路,遇到这种题目做不出改的也慢,以后尽量多思考一会儿,在实在想不出的情况再去看题解,不要为了速度浪费了效率…然后就是改题的时候还是专心点吧,一会儿做点其他的事完全改不进题目…

猜你喜欢

转载自blog.csdn.net/lunch__/article/details/81910395