7.23 2020 Multi-University Training Contest 2题解及补题

7.23 2020 Multi-University Training Contest 2题解及补题

比赛过程

1010题最后用暴力过的,想过1001,但是没选对方法。

题解

1001 Total Eclipse

题意

简单点就是:每次选一个连通块使里面所有点的值减一,当某个点减为零时其点连的所有边都会删去,求多少次操作使所有点变成0。

解法

先对每一个边排序:排序方案是每一条边连这两个点, 两个点有各自的权值,排序方案是按照这两个权值中小的那个权值作为排序的依据。索引到每一个边的作用是让这条边的大的权值点合并到小的权值的点上去。那么如果当前的合并方案不是最合理的怎么办:比如如果是5权值的节点,邻居有4,2,1权值的节点,如果当前边是5和2的连边,但是最合理的是先让5链接到4权值再进一步降低,因为边的排序方案是让边的两个权值中较小的那个权值降序排列,所以5-4的边一定会比5-2的边更早被遍历到,所以按照边的出现的顺序正常下降是不会有问题的。

对于每一个边,让大的点的权值改为较小的点的权值,这次记操作次数为改变的差值贡献给答案。至于怎么实现这次改动让之后的遍历到的边也能更新当前的权值:用并查集,因为更新权值的更新后一定对应着某一个点的初始权值,那么我就把需要降低到哪一个点,就把当前大的点的父亲指向那个相对小的点的编号,所以在用边去找到点的权值的时候必须要做的就是先去找到两个点对应的父亲节点是谁,父亲节点的含义是父亲节点的权值没有发生过变化,而当前的节点权值和父亲节点的权值一样(已经降低到父亲节点一样的权值了).

代码

#include <algorithm>
#include <bitset>
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <limits>
#include <list>
#include <map>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
#include <cwchar>
#include <cwctype>
using namespace std;
typedef long long ll;
#define IO ios::sync_with_stdio(false), cin.tie(0),cout.tie(0)
#define FOR(a,b,c) for(int a=b;a<=c;a++)
#define MAXN 9999
#define MAXSIZE 1010
const int maxn = 6e5+60;

ll a[maxn];

struct edge{
    int minid,maxid;
    ll minv,maxv;
    bool operator<(const edge& e)const{
        return minv > e.minv;
    }
}b[maxn];

int fa[maxn];

inline int Fa(int x){
    if(fa[x] != x) fa[x] = Fa(fa[x]);
    return fa[x];
}

void solve(){
    int n,m;
    scanf("%d %d",&n, &m);
    for(int i=1;i<=n;i++){
        scanf("%d",&a[i]);
        fa[i] = i;
    }

    int s, d;
    for(int i=0;i<m;i++){
        scanf("%d %d",&s, &d);
        if(a[s]<a[d])
            swap(s,d);
        b[i].minid = d;
        b[i].maxid = s;
        b[i].minv = a[d];
        b[i].maxv = a[s];
    }

    sort(b, b+m);
    ll ans = 0;
    for(int i=0;i<m;i++){
        edge nowe = b[i];
        int MINid = Fa(nowe.minid);
        int MAXid = Fa(nowe.maxid);
        ans+=a[MAXid] - a[MINid];
        a[MAXid] = a[MINid];
        fa[MAXid] = MINid;
    }
    for(int i=1;i<=n;i++){
        if(fa[i]==i)ans+=a[i];
    }
    printf("%lld\n",ans);

}

int main(){
    // IO;
    int T;
    cin>>T;
    while(T--){
        solve();
    }
}


1005 New Equipments

题意

给出\(n\) 个工人和 \(m\) 件装备,装备的编号为 \(1,2,3...m\)
对于工人 \(i\),他有三个参数 \(a_i,b_i,c_i\),当为这个工人装备了第 \(j\)个装备时,需要花费 \(a_i×j^2+b_i×j+c_i\) 的费用。
当为 \(k\)个工人装备上装备时,最小花费是多少。对所有的 \(k\) 的情况均需要输出

解法

由于每个工人的代价为二次函数,我们可以利用二元一次函数的性质求出代价的最小值,然后向两边扩展,总共找出\(n\)个代价最小的机器,将这个工人和这\(n\)台机器之间连一条容量为1,费用为\(a_i×j^2+b_i×j+c_i\)的边,对每个工人都这么操作,然后跑费用流即可,由于要分别求出安排\([1 ⋯⋯ n]\)个工人的最小代价,所以在残余网络上跑\(n\)次spfa即可

代码

#include <algorithm>
#include <bitset>
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <limits>
#include <list>
#include <map>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
#include <cwchar>
#include <cwctype>
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define IO ios::sync_with_stdio(false), cin.tie(0),cout.tie(0)
#define FOR(a,b,c) for(int a=b;a<=c;a++)
#define MAXN 9999
#define MAXSIZE 1010
const int N = 5010;
const int M = 10010;
const int manx = 55;
const ll INF = 1000000000000000010;

struct Edge {
    int to, nex;
    ll w, c;
};

Edge edge[2 * M];
int T, n, m, mx, p[manx][manx];
ll a[manx], b[manx], c[manx];
vector<int> alls;
int cnt, s, t, pre[N], lst[N], head[N], inq[N];
ll mc, dis[N], mf, f[N];
queue<int> q;

void init()
{
    mc = mf = 0;
    alls.clear();
    cnt = -1;
    memset(head, -1, sizeof(head));
}

void add_edge(int u, int v, ll w, ll c)
{
    edge[++cnt].to = v;
    edge[cnt].w = w;
    edge[cnt].c = c;
    edge[cnt].nex = head[u];
    head[u] = cnt;
}

int spfa(int s, int t)
{
    for (int i = 0; i < N; i++) {
        dis[i] = f[i] = INF;
        inq[i] = 0;
    }
    while (!q.empty()) q.pop();
    q.push(s);
    inq[s] = 1, dis[s] = 0, pre[t] = -1;
    while (!q.empty()) {
        int u = q.front();
        q.pop();
        inq[u] = 0;
        for (int i = head[u]; -1 != i; i = edge[i].nex) {
            int v = edge[i].to;
            ll c = edge[i].c, w = edge[i].w;
            if (0 == w || dis[v] <= dis[u] + c) continue;
            dis[v] = dis[u] + c;
            pre[v] = u;
            lst[v] = i;
            f[v] = min(f[u], w);
            if (1 == inq[v]) continue;
            inq[v] = 1;
            q.push(v);
        }
    }
    if (-1 == pre[t]) return 0;
    return 1;
}

void insert(int u, int v, ll w, ll c)
{
    add_edge(u, v, w, c);
    add_edge(v, u, 0, -c);
}

ll fx(int i, ll x)
{
    return x * x * a[i] + x * b[i] + c[i];
}

int fid(int x)
{
    return lower_bound(alls.begin(), alls.end(), x) - alls.begin() + 1;
}

int main()
{
    scanf("%d", &T);
    while (T--) {
        init();
        scanf("%d%d", &n, &mx);
        for (int i = 1; i <= n; i++) {
            scanf("%lld%lld%lld", &a[i], &b[i], &c[i]);
            int res = 0, c = 1;
            if (b[i] >= 0) res = 1;
            else {
                int lm = floor(-1.0 * b[i] / (2 * a[i]));
                int rm = ceil(-1.0 * b[i] / (2 * a[i]));
                if (lm >= 1) {
                    if (fx(i, lm) < fx(i, rm)) res = lm;
                    else res = rm;
                }
                else res = rm;
            }
            p[i][c] = res;
            int l = res - 1, r = res + 1;
            while (c < n) {
                if (fx(i, l) < fx(i, r)) {
                    if (l >= 1 && l <= mx) p[i][++c] = l--;
                    else p[i][++c] = r++;
                }
                else {
                    if (r >= 1 && r <= mx) p[i][++c] = r++;
                    else p[i][++c] = l--;
                }
            }
        }
        for (int i = 1; i <= n; i++)
            for (int k = 1; k <= n; k++) alls.push_back(p[i][k]);
        sort(alls.begin(), alls.end());
        alls.erase(unique(alls.begin(), alls.end()), alls.end());
        m = alls.size(), s = n + m + 1, t = s + 1;
        for (int i = 1; i <= n; i++) {
            insert(s, i, 1, 0);
            for (int k = 1; k <= n; k++) {
                insert(i, n + fid(p[i][k]), 1, fx(i, p[i][k]));
            }
        }
        for (int i = 1; i <= m; i++) insert(n + i, t, 1, 0);
        for (int i = 1; i <= n; i++) {
            if (!spfa(s, t)) break;
            mf += f[t];
            mc += f[t] * dis[t];
            int now = t;
            while (now != s) {
                edge[lst[now]].w -= f[t];
                edge[lst[now] ^ 1].w += f[t];
                now = pre[now];
            }
            printf("%lld", mc);
            printf(i == n ? "\n" : " ");
        }
    }
    return 0;
}

1006 The Oculus

题意

给你两个由斐波那契数列的和构成的数字A和B(即 $A = b_1 * F_1 + b_2 * F_2+...+b_n * F_n, b_i \in (0,1) $,输入的数据为b数组),以及他们的乘积C的斐波那契数列和的表现形式,但其中c的非首位有一个1被改成了0,求出这个被改位置。

解法

假设我们要求的位置为k,则\(A \times B - C = F_k\)。用unsigned long long来存 ,自然溢出即可。

代码

#include <bits/stdc++.h>
#define IO ios::sync_with_stdio(0), cin.tie(0)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 2e6 + 5;
const int inf = ~0u >> 1;
typedef pair<int,int> P;
#define REP(i, a, n) for (int i = a; i < (n); ++i)
#define PER(i, a, n) for (int i = (n) - 1; i >= a; --i)
ull f[maxn];
int main() {
    IO;
    f[1] = 1;
    f[2] = 2;
    REP(i, 3, maxn) {
        f[i] = f[i - 1] + f[i - 2];
    }
    int t;
    cin >> t;
    while (t--) {
        ull A = 0, B = 0, C = 0, n;
        cin >> n;
        REP(i, 1, n + 1) {
            int x;
            cin >> x;
            if (x) {
                A += f[i];
            }
        }
        cin >> n;
        REP(i, 1, n + 1) {
            int x;
            cin >> x;
            if (x) {
                B += f[i];
            }
        }
        cin >> n;
        REP(i, 1, n + 1) {
            int x;
            cin >> x;
            if (x) {
                C += f[i];
            }
        }
        ull ans = A * B - C;
        REP(i, 1, maxn) {
            if (ans == f[i]) {
                cout << i << endl;
                break;
            }
        }
    }
    return 0;
}

1007 In Search of Gold

题意

题中给出n个点n-1条边的树,每条边有两个可能值a,b,要求恰好选择k条边使其权值为a,其他边的权值为b,求这个树的最小直径

解法

树的直径:树上最远两点(叶子结点)的距离。要求直径的最小值,也就是最远两点的距离的最小值,这就转化成了最小化最大值的问题,
所以我们可以使用二分答案来解决。在二分答案的过程中,我们每次肯定都要检查mid是否为最小值,具体方法就是求出
树上任意一条路径的长度,检查是否他们都小于mid,这样实际上只需要满足对于每一个点,其子树中经过他的最长路径
最短即可。接下来就是树上的背包问题,由于第一次遇到树上dp,感觉自己还需要深入了解,这里借鉴了大佬的代码。
推荐博客:树的直径的dp求法

代码

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int MAX = 2e4 + 10;

struct edge {
    int nxt, to;
    ll a, b;
} e[MAX << 1];
int head[MAX], tot;
void add(int u, int v, ll a, ll b) { e[++tot] = edge{head[u], v, a, b}; head[u] = tot; }

int N, K;
int siz[MAX];
ll f[MAX][22];

void dfs(int u, int fa, ll lim) {
    siz[u] = 0;
    for (int i = 0; i <= K; i++) f[u][i] = 0;
    for (int i = head[u], v; i; i = e[i].nxt)
        if ((v = e[i].to) != fa) {
            dfs(v, u, lim);
            int now = min(siz[u] + siz[v] + 1, K);
            ll t[22]; for (int j = 0; j <= now; j++) t[j] = lim + 1;
            for (int j = 0; j <= siz[u]; j++)
                for (int k = 0; k <= siz[v] && j + k <= K; k++) {
                    if (f[u][j] + f[v][k] + e[i].a <= lim)
                        t[j + k + 1] = min(t[j + k + 1], max(f[u][j], f[v][k] + e[i].a));
                    if (f[u][j] + f[v][k] + e[i].b <= lim)
                        t[j + k] = min(t[j + k], max(f[u][j], f[v][k] + e[i].b));
                }
            for (int j = 0; j <= now; j++) f[u][j] = t[j];
            siz[u] = now;
        }

}

int main() 
{
    int T; 
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &N, &K);
        ll l = 1, r = 0;
        for (int i = 1; i < N; i++) {
            int u, v; ll a, b; scanf("%d%d%lld%lld", &u, &v, &a, &b);
            add(u, v, a, b); add(v, u, a, b);
            r += max(a, b);
        }
        ll ans = r;
        while (l <= r) {
            ll mid = (l + r) / 2;
            dfs(1, 0, mid);
            if (f[1][K] <= mid) r = mid - 1, ans = mid;
            else l = mid + 1;
        }
        printf("%lld\n", ans);
        for (int i = 1; i <= tot; i++) head[i] = 0; tot = 0;
    }

    return 0;

}

1010 Lead of Wisdom

题意

\(k\)种物品,每种物品最多只能佩戴一件。对于第\(i\)个物品,它有四个属性\(a_i,b_i,c_i,d_i\),假设玩家穿戴的 物品集合为\(S\),玩家的伤害率\(DMG\)可以通过以下公式计算:

\(DMG=(100+∑_{i∈S}a_i)(100+∑_{i∈S}b_i)(100+∑_{i∈S}c_i)(100+∑_{i∈S}d_i)\),然后最大化DMG。

解法

暴力,优化在只有一种技能的分类不需要dfs

代码

#include <algorithm>
#include <bitset>
#include <cctype>
#include <cerrno>
#include <clocale>
#include <cmath>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <limits>
#include <list>
#include <map>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <utility>
#include <vector>
#include <cwchar>
#include <cwctype>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 111;
struct node {
    int a, b, c, d;
};
vector<node> s[100];
ll ans = 0;
int flag[100];
int n, kk;
inline ll cal(int a, int b, int c, int d) {
    return (100ll + a) * (100ll + b) * (100ll + c) * (100ll + d);
}
void dfs(int m, int a1, int b1, int c1, int d1) {
    while (!flag[m]) 
        m++;
    if (m == kk) {
        for (auto w : s[m]) {
            ans = max(ans, cal(a1 + w.a, b1 + w.b, c1 + w.c, d1 + w.d));
        }
        return;
    }
    for (auto w : s[m]) {
        dfs(m + 1, a1 + w.a, b1 + w.b, c1 + w.c, d1 + w.d);
    }
}
int main() {
    int t;
    scanf("%d", &t);
    while (t--) {
        ans = 0;
        scanf("%d%d", &n, &kk);
        for (int i = 1; i <= kk; ++i) s[i].clear(), flag[i] = 0;
        kk = 0;
        for (int i = 1; i <= n; ++i) {
            int m, a, b, c, d;
            scanf("%d%d%d%d%d", &m, &a, &b, &c, &d);
            kk = max(kk, m);
            flag[m] = 1;
            s[m].push_back({a, b, c, d});
        }
        dfs(1, 0, 0, 0, 0);
        printf("%lld\n", ans);
    }
    return 0;
}

1012 String Distance

题意

解法

代码

//将内容替换成代码
posted @ 2020-08-02 20:27  cugbacm03  阅读(184)  评论(0)    收藏  举报