Problem A Cell Phone Network

题目链接:https://ac.nowcoder.com/acm/contest/1069/A/

题意:已知与信号塔相邻的草地能收到信号。给你N-1个草地(A,B)的相邻关系,问:最少需要建多少个信号塔能实现所有草地都有信号。
思路:贪心,考虑每一个叶子节点,我们可以发现。如果要覆盖一个叶子节点,最优的方案显然是在它的父亲节点上建立一个信号塔。因此,我们每次找一个节点,该节点满足:它的子树(叶子节点)已经被完全覆盖了。然后,在这个节点的父亲节点建立信号塔,并且覆盖相邻的节点。重复以上操作。我们可以在建树时,当回溯上来以后(或当前点是叶子节点时)判断该点是否被覆盖过,若没有则覆盖其父亲和父亲的相邻节点。显然,当回溯到当前节点时,其子树已经被完全覆盖。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20005;
typedef long long ll;
struct edge {
    int u, v;
}e[MAXN << 1];
int n, t = 0, cnt = 0, res = 0;
int par[MAXN], f[MAXN];
bool vis[MAXN], visp[MAXN], vist[MAXN];
inline void Add(int u, int v) {
    e[++t] = (edge){f[u], v};
    f[u] = t;
}
inline void init() {
    memset(f, 0, sizeof(f));
    memset(par, 0, sizeof(par));
    memset(vis, false, sizeof(vis));
    memset(visp, false, sizeof(visp));
}
void DFS(int u) {
    vis[u] = true;
    for (int i = f[u]; i; i = e[i].u) {
        int v = e[i].v;
        if (!vis[v]) {
            par[v] = u;
            DFS(v);
        }
    }
    if (!visp[u]) {
        res++;
        int p = par[u];
        visp[p] = true;
        for (int i = f[p]; i; i = e[i].u)
            visp[e[i].v] = true;
    }
}
int main() {
    init();
    int u, v;
    scanf("%d", &n);
    for (int i = 0; i < n - 1; i++) {
        scanf("%d%d", &u, &v);
        Add(u, v);
        Add(v, u);
    }
    par[1] = 1;
    DFS(1);
    printf("%d\n", res);
    return 0;
}

Problem B iCow

题目链接:https://ac.nowcoder.com/acm/contest/1069/B/

题意:现有一个MP3,其播放顺序及规则为:

  1. 从分值最大的开始放
  2. 播放后分之清零
  3. 清零的分数平均分给除它外的其余乐曲
  4. 若有余数则按照读入的顺序依次分给除它外的其余乐曲

给你初始分值,让你求第一到第k次的播放顺序。
思路:直接模拟。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
typedef long long ll;
int p[MAXN];
int main() {
    int n, t;
    scanf("%d%d", &n, &t);
    for (int i = 1; i <= n; i++)
        scanf("%d", &p[i]);
    while (t--) {
        int max_ = 0, j = 1;
        for (int i = 1; i <= n; i++)
            if (p[i] > max_)
                max_ = p[j = i];
        printf("%d\n", j);
        int avg = p[j] / (n - 1);
        for (int i = 1; i <= n; i++)
            if (i != j)
                p[i] += avg;
        if (p[j] % (n - 1)) {
            int rem = p[j] % (n - 1);
            for (int i = 1; i <= n && rem; i++) {
                if (i != j) {
                    p[i]++;
                    rem--;
                }
            }
        }
        p[j] = 0; 
    }
    return 0;
}

Problem C 阶乘之和

题目链接:https://ac.nowcoder.com/acm/contest/1069/C/

题意:求1~n的阶乘之和。
思路:直接上大数模板。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100;
int s[MAXN], spt[MAXN], len = 1;
void Add(int a[], int la, int b[], int lb) {
    int c = 0;
    len = max(la, lb);
    for (int i = 0; i < len; i++) {
        a[i] += b[i] + c;
        c = a[i] / 10;
        a[i] %= 10;
    }
    if (c)
        a[len++] = c;
}
int main() {
    int n, t = 1, c = 0;
    scanf("%d", &n);
    s[0] = spt[0] = 1;
    for (int i = 2; i <= n; i++) {
        for (int j = 0; j < t; j++) {
            s[j] = s[j] * i + c;
            c = s[j] / 10;
            s[j] %= 10;
        }
        while (c) {
            s[t++] = c % 10;
            c /= 10;
        }
        Add(spt, len, s, t);
    }
    for (int i = len - 1; i >= 0; i--)
        printf("%d", spt[i]);
    printf("\n");
    return 0;
}

Problem D Artificial Lake

题目链接:https://ac.nowcoder.com/acm/contest/1069/D/

题意:给出N个连续的平台,他们各有宽度,且高度不同。先向高度最低的平台灌水,直到灌满溢出,流向其他的平台,直至所有平台都被覆盖。已知每分钟注入高为1宽为1的水,求每个平台恰好被高为1的水覆盖的时间。
思路:先找到最低的平台,然后开始灌水。然后将最低的填满以后水溢出,相当于该平台消失,更新左右平台,寻找下一流向哪个平台,每次找最近的最低的平台h,然后将水填到h高度。向外扩展的时候,有时会遇到高度递减的情况,这时并不能填水,但要把这些高度都记录进栈,然后遇到一个比水面高的平台h时,模拟倒水,水会挨个淹没最低的平台,即需要从栈顶一个一个出栈计算淹没时间,直至栈顶平台高度>h,此时h入栈。重复执行就可算出答案。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const int inf = 0x3f3f3f3f;
struct edge {
    int h, id;
    long long w;
}sp[MAXN], stp[MAXN];
long long cnt[MAXN];
int main() {
    int n, j, top, min_ = inf;;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        sp[i].id = i;
        scanf("%lld%d", &sp[i].w, &sp[i].h);
        if (min_ > sp[i].h)
            min_ = sp[j = i].h;
    }
    top = 0;
    sp[0].h = sp[n + 1].h = inf;
    stp[top] = sp[0];
    stp[++top] = sp[j];
    long long t = 0;
    int l = j, r = j, p;
    for (int i = 1; i <= n; i++) {
        long long w = 0;
        if (sp[l - 1].h < sp[r + 1].h)
            p = --l;
        else p = ++r;
        while (sp[p].h > stp[top].h) {
            stp[top].w += w;
            cnt[stp[top].id] = t + stp[top].w;
            t += (min(sp[p].h, stp[top - 1].h) - stp[top].h) * stp[top].w;
            w = stp[top].w;
            top--;
        }
        sp[p].w += w;
        stp[++top] = sp[p];
    }
    for (int i = 1; i <= n; i++)
        printf("%lld\n", cnt[i]);
    return 0;
}

Problem E Haybale Guessing

题目链接:https://ac.nowcoder.com/acm/contest/1069/E/

题意:给出一些区间的最小值,问最早哪个问题会产生矛盾,输出。
思路:区间染色问题,可以用并查集来做。先二分出现矛盾的地方p,然后将1~p的询问值按大到小排序,若对于最小值相同的区间中出现不相交的两个区间,那么矛盾出现。那么合法的情况就是这些最小值相同的区间必然是两两相交的,那么记录最小的左端点L和最大的右端点R,然后将[L,R]与L-1合并。所以在判断的时候还有判一下当前最小值相同的区间的交集是否之前出现过,若出现过,则矛盾。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000005;
struct node {
    int l, r, num;
    bool operator < (const node &s) const {
        return s.num < num;
    }
};
int n, q;
int father[MAXN];
node a[MAXN], tmp[MAXN];
int find(int x) {
    while (father[x] != x)
        x = father[x];
    return x;
}
int cmp(node a, node b) {
    return a.num > b.num;
}
bool check(int tot) {
    for (int i = 1; i <= n; i++)
        father[i] = i;
    for (int i = 1; i <= tot; i++)
        tmp[i] = a[i];
    sort(tmp + 1, tmp + 1 + tot);
    for (int i = 1, j; i <= tot; i = j + 1) {
        j = i;
        int l = tmp[i].l;
        int r = tmp[i].r;
        int L = tmp[i].l;
        int R = tmp[i].r;
        while (j < tot && tmp[j].num == tmp[j + 1].num) {
            j++;
            l = max(l, tmp[j].l);
            r = min(r, tmp[j].r);
            L = min(L, tmp[j].l);
            R = max(R, tmp[j].r);
        }
        if (l > r || l > find(r))
            return 0;
        while (L <= R) {
            if (find(R) == R) {
                father[R] = find(L - 1);
                R--;

            } else
                R = father[R];
        }
    }
    return 1;
}
int main() {
    while (~scanf("%d%d", &n, &q)) {
        for (int i = 1; i <= q; i++)
            scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].num);
        int l = 1, r = q, ans = 0;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (check(mid))
                l = mid + 1;
            else {
                ans = mid;
                r = mid - 1;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

Problem F Telephone Lines

题目链接:https://ac.nowcoder.com/acm/contest/1069/F/

题意:有p条电缆,目的是建一条最小花费电缆,使得1能够到达n。然后官方给你提供K条电缆(让你免费建k条边),剩下的电缆我们自己拿钱,问我们自己拿钱中的那条最贵的电缆最小是多少。
思路:二分mid,每次判断是否能使1到N的路径上第K+1大的边权小于mid。那么只需要把升级价格大于mid的边权值赋为1,其余权值赋为0,跑最短路得到dis[n]与K进行比较,若小于等于K,说明该答案可行,缩小r继续二分,否则缩小l。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int n, p, k, cnt, tot;
int f[1005], dis[1005], s[100005];
bool vis[1005];
struct edge {
    int u, v, s, nx;
} e[100005];
void Add(int x, int y) {
    e[++cnt].u = x;
    e[cnt].v = y;
    e[cnt].nx = f[x];
    f[x] = cnt;
}
void Spfa() {
    for (int i = 1; i <= n; i++)
        dis[i] = inf, vis[i] = 0;
    vis[1] = 1, dis[1] = 0;
    queue<int> Q;
    Q.push(1);
    while (!Q.empty()) {
        int x = Q.front();
        Q.pop();
        vis[x] = 0;
        for (int i = f[x]; i; i = e[i].nx) {
            int y = e[i].v, z = e[i].s;
            if (dis[y] > dis[x] + z) {
                dis[y] = dis[x] + z;
                if (!vis[y]) {
                    vis[y] = 1;
                    Q.push(y);
                }
            }
        }
    }
}
int main() {
    int u, v, w;
    scanf("%d%d%d", &n, &p, &k);
    for (int i = 1; i <= p; i++) {
        scanf("%d%d%d", &u, &v, &w);
        s[++tot] = w;
        s[++tot] = w;
        Add(u, v);
        Add(v, u);
    }
    int ans = -1;
    int l = 0, r = 1000000;
    while (l <= r) {
        int mid = (l + r) >> 1;
        for (int i = 1; i <= cnt; i++) {
            if (s[i] <= mid)
                e[i].s = 0;
            else e[i].s = 1;
        }
        Spfa();
        if (dis[n] <= k) {
            ans = mid;
            r = mid - 1;
        } else l = mid + 1;
    }
    printf("%d\n", ans);
    return 0;
}

Problem G Election Time

题目链接:https://ac.nowcoder.com/acm/contest/1069/G/

题意:n头牛要竞选总统,进行两轮投票,第一轮选出票数为前k名的进行第二轮投票,选出最高者为总统。
思路:对结构体数组进行针对票数的从大到小排序,选出前k项,然后再对这k个排序,选最大的那个。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
typedef long long ll;
struct edge {
    ll first,second;
    int id;
}s[MAXN];
bool cmp1(const edge &x, const edge &y){
    return x.first > y.first;
}
bool cmp2(const edge &x, const edge &y){
    return x.second > y.second;
}
int main() {
    int n, k, ans;
    scanf("%d%d", &n, &k);
    for (int i = 0; i < n; i++){
        scanf("%lld%lld", &s[i].first, &s[i].second);
        s[i].id = i + 1;
    }
    sort(s, s + n, cmp1);
    sort(s, s + k, cmp2);
    printf("%d\n", s[0].id);
    return 0;
}

Problem H Costume Party

题目链接:https://ac.nowcoder.com/acm/contest/1069/H/

题意:有一组数组n,要求这n个数两两组合能拼凑出多少对和(ai+aj)小于或等于M的组合。
思路:先排序,然后直接暴力。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 20005;
typedef long long ll;
int p[MAXN];
int main() {
    int n, m, ans = 0;
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i++)
        scanf("%d", &p[i]);
    sort(p, p + n);
    for (int i = 0; i < n - 1; i++) {
        for (int j = i + 1; j < n; j++) {
            if (p[i] + p[j] <= m)
                ans++;
            else break;
        }
    }
    printf("%d\n", ans);
    return 0;
}

Problem I Cantor表

题目链接:https://ac.nowcoder.com/acm/contest/1069/I/

题意:根据表中的数据求出第N项。
思路:放正,找规律。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100;
int main() {
    int n, i, ans;
    scanf("%d", &n);
    for (i = 1; i * (i + 1) / 2 < n; i++);
    ans = n - i * (i - 1) / 2;
    if (i & 1)
        printf("%d/%d\n", i - ans + 1, ans);
    else printf("%d/%d\n", ans, i - ans + 1);
    return 0;
}

Problem J Running

题目链接:https://ac.nowcoder.com/acm/contest/1069/J/

题意:Bessie想成为一名运动牛,它每分钟跑的距离为Di,初始时疲劳度为0,每跑一分钟它就增加1疲劳度,但是它的疲劳度不能超过M。当它选择休息时,它的疲劳度每分钟减少1,但是它必须休息到疲劳度为0时才能继续跑。当N分钟过去后,它的疲劳度必须为0,否则它就没有足够的能量维持生活。求Bessie能跑的最远距离。
思路:定义dp[i][j]为第i分钟疲劳度为j时能够跑的最远距离,若第i分钟疲劳度j不为0,说明上一分钟在跑步,所以dp[[i][j]=dp[i-1][j-1]+a[i];
若第i分钟疲劳度为0,那么上一分钟疲劳度可能也为0,或者歇息了j分钟,由此可得以下状态转移方程:
dp[i][0]=max(dp[i-1][0],dp[i-j][j])
dp[i][j]=dp[i-1][j-1]+a[i];

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10005;
typedef long long ll;
int dp[MAXN][505], a[MAXN];
int main() {
    int n, m;
    scanf("%d %d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    memset(dp, 0, sizeof(dp));
    for (int i = 1; i <= n; i++) {
        dp[i][0] = dp[i - 1][0];
        for (int j = 1; j <= m; j++) {
            if (j < i)
                dp[i][0] = max(dp[i][0], dp[i - j][j]);
            dp[i][j] = dp[i - 1][j - 1] + a[i];
        }
    }
    printf("%d\n", dp[n][0]);
    return 0;
}

Problem K Cow Contest

题目链接:https://ac.nowcoder.com/acm/contest/1069/K/

题意:给出M轮双牛比赛的结果,确定可以从结果中精确确定等级的奶牛数量。
思路:Floyed算法,Floyed不仅能求任意两点的最短路,还能求一个点能否到另一个点。
f[i][j]=f[i][j]|(f[i][k]&f[k][j])表示i能否走到j,即要么一开始i能到j,要么i能到k,k再能到j。
那么这里表示的是i能否赢j。用floyed求出每个点与个点的关系,只要这个点和其他n-1个点的关系都确定了,就能确定他的排名。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
int f[105][105];
int main() {
    int n, m, a, b, ans = 0;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &a, &b);
        f[a][b] = 1;
    }
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                f[i][j] |= f[i][k] & f[k][j];
    for (int i = 1; i <= n; i++) {
        int res = 1;
        for (int j = 1; j <= n; j++)
            if (i != j)
                res &= f[i][j] | f[j][i];
        ans += res;
    }
    printf("%d\n", ans);
    return 0;
}

Problem L 幂次方

题目链接:https://ac.nowcoder.com/acm/contest/1069/L/

题意:将n化成2的幂次方。
思路:递归先找出n的最高的2次方(找出最大的a,满足2^a <=n),如果a是0或2输出,1不处理,否则就证明a还可以分解,递归。然后再判断n是否还有其他的2幂次方,还有就继续执行上面的过程。

Accepted Code:

#include <bits/stdc++.h>
using namespace std;
void f(int n) {
    int i;
    for (i = 20; i >= 0; i--)
        if (n & (1 << i))
            break;
    printf("2");
    if (i <= 2 && i != 1)
        printf("(%d)", i);
    if (i > 2) {
        printf("(");
        f(i);
        printf(")");
    }
    if (n & ~(1 << i)) {
        int temp = n & ~(1 << i);
        printf("+");
        f(temp);
    }
    return;
}
int main() {
    int n;
    scanf("%d", &n);
    f(n);
    return 0;
}