2019, XII Samara Regional Intercollegiate Programming Contest 全部题解

英语巨烂的我,把两个签到题读成了不可写题…感觉给我一个中文题面,有机会ak…
A. Rooms and Passages
题意:有 n + 1个点在一排,有 n 条边连接,依次求点 i 往 点 n 的方向走最多能走多远,如果当前的边权值为 x (x > 0),且已经走过一条权值为 -x 的边,那么这条边不能走。
思路:倒着处理,标记每条正边在当前出现的最小坐标即可。

#include
#define ll long long
using namespace std;
const int maxn = 5e5 + 10;
int vis[maxn], a[maxn];
stack<int> s;
int main() {
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    int pre = 0;
    for (int i = n; i; i--) {
        if (a[i] > 0) {
            s.push(++pre);
            vis[a[i]] = i;
        }
        else {
            if (!vis[-a[i]])
                pre = min(pre + 1, n - i + 1), s.push(pre);
            else {
                pre = min(pre + 1, vis[-a[i]] - i);
                s.push(pre);
            }
        }
    }
    while (!s.empty())
        printf("%d ", s.top()), s.pop();
}

B. Rearrange Columns
题意:要求交换一些列,使得 # 互相连通,签到题

#include
#define ll long long
using namespace std;
const int maxn = 1010;
char s[2][maxn], str[2][maxn];
int main() {
    cin>>s[0]>>s[1];
    int n = strlen(s[0]);
    int t1 = 0, t2 = 0, t = 0;
    for (int i = 0; i < n; i++) {
        int f1 = 0, f2 = 0;
        if (s[0][i] == '#')
            f1 = 1;
        if (s[1][i] == '#')
            f2 = 1;
        if (f1 && f2)
            t++;
        else if (f1)
            t1++;
        else if (f2)
            t2++;
    }
    if (t1 && t2 && !t)
        return puts("NO"), 0;
    for (int i = 0; i < t1; i++)
        str[0][i] = '#', str[1][i] = '.';
    for (int i = 0; i < t; i++)
        str[0][i + t1] = str[1][i + t1] = '#';
    for (int i = 0; i < t2; i++)
        str[0][i + t1 + t] = '.', str[1][i + t1 + t] = '#';
    for (int i = t1 + t2 + t; i < n; i++)
        str[0][i] = str[1][i] = '.';
    if (strcmp(str[0], s[0]) == 0) {
        for (int i = 0; i < t2; i++)
            str[0][i] = '.', str[1][i] = '#';
        for (int i = 0; i < t; i++)
            str[0][i + t2] = str[1][i + t2] = '#';
        for (int i = 0; i < t1; i++)
            str[0][i + t2 + t] = '#', str[1][i + t2 + t] = '.';
    }
    puts("YES");
    cout<<str[0]<<endl;
    cout<<str[1]<<endl;
}

C. Jumps on a Circle
题意:有一个长度为 p 的环,你要走n次,第 i 次走 i 步,求能走多少个不同的点
思路:n很大,其实有用的n就是2 * p,因为走2 * p步肯定回到原点(等差数列求和是 p 的倍数),第 2 * p + 1其实就是第 1 步,所以我们对 n 取一个min(n , 2 * p)然后模拟走一遍就可以了

#include
#define ll long long
using namespace std;
const int maxn = 1e7 + 5;
bitset<maxn> a;
int main() {
    int p;
    ll n;
    cin>>p>>n;
    n = min(n, 2ll * p);
    int k = 0;
    for (int i = 0; i <= n; i++) {
        k += i;
        k %= p;
        a[k] = 1;
    }
    printf("%d\n", a.count());
}

D. Country Division
题意:给一棵树,q次询问,每次询问把树中一些点染红色,一些点染蓝色,求是否能够断开一些边,使得所有红点联通,所有蓝点联通,且任意红点和任意蓝点不联通。
思路:我们求出所有红点lca1,所有蓝点lca2,如果所有蓝点不在lca1的子树中或者所有红点不在lca2的子树中,那么就是yes。

#include
#define pb push_back
using namespace std;
const int maxn = 2e5 + 10;
int f[maxn][20], dep[maxn], L[maxn], R[maxn], cnt;
vector<int> G[maxn], a, b;
void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    L[u] = ++cnt;
    f[u][0] = fa;
    for (int i = 1; i < 20; i++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    for (auto v : G[u])
        if (v != fa)
            dfs(v, u);
    R[u] = cnt;
}
int LCA(int x, int y) {
    if (dep[x] < dep[y])
        swap(x, y);
    for (int i = 19; i >= 0; i--)
        if (dep[f[x][i]] >= dep[y])
            x = f[x][i];
    if (x == y)
        return x;
    for (int i = 19; i >= 0; i--)
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}
int ok(int u, vector<int> tmp) {
    for (auto v : tmp)
        if (L[v] >= L[u] && R[v] <= R[u])
            return 0;
    return 1;
}
int main() {
    int n, u, v, q, k1, k2, x;
    scanf("%d", &n);
    for (int i = 1; i < n; i++) {
        scanf("%d%d", &u, &v);
        G[u].pb(v);
        G[v].pb(u);
    }
    dfs(1, 0);
    scanf("%d", &q);
    while (q--) {
        scanf("%d%d", &k1, &k2);
        a.clear();
        b.clear();
        int lca1 = 0, lca2 = 0, mx = 0, mn = 0;
        for (int i = 1; i <= k1; i++) {
            scanf("%d", &x), a.pb(x);
            if (!mx || L[mx] < L[x])
                mx = x;
            if (!mn || L[mn] > L[x])
                mn = x;
        }
        lca1 = LCA(mx, mn);
        mx = 0, mn = 0;
        for (int i = 1; i <= k2; i++) {
            scanf("%d", &x), b.pb(x);
            if (!mx || L[mx] < L[x])
                mx = x;
            if (!mn || L[mn] > L[x])
                mn = x;
        }
        lca2 = LCA(mx, mn);
        if (ok(lca1, b))
            puts("YES");
        else if (ok(lca2, a))
            puts("YES");
        else
            puts("NO");
    }
}

E. Third-Party Software - 2
题意:选择最少的区间,使得这些区间能够覆盖[1, m]
思路:原题,写过几次了。

#include
using namespace std;
const int maxn = 2e5 + 10;
struct node{
    int x, y, id;
    bool operator<(const node& t) const {
        if (x == t.x)
            return y > t.y;
        return x < t.x;
    }
} a[maxn];
priority_queue<int, vector<int>, greater<int> > q;
int main()
{
    int n, m, mx = 0, p = 0, t;
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &a[i].x, &a[i].y), a[i].id = i;
    sort(a + 1, a + 1 + n);
    for (int i = 1; i <= n && mx < m; i++) {
        if (a[i].x > p + 1) {
            if (a[i].x > mx + 1)
                break;
            p = mx;
            q.push(t);
            i--;
        }
        else {
            if (a[i].y > mx)
                mx = a[i].y, t = a[i].id;
        }
        if (mx >= m)
            q.push(t);
    }
    if (mx < m)
        puts("NO");
    else {
        puts("YES");
        printf("%d\n", q.size());
        while (!q.empty()) {
            printf("%d ", q.top());
            q.pop();
        }
    }
}

F. Friendly Fire
题意:有 n 个怪兽,每个怪兽有攻击力 ai 和血量 bi,你可以选择两个怪兽,如果 ai >= bj,那么怪兽 i 能打死怪兽 j,让他们同时攻击对方(有可能两只怪兽同时死亡),使得死亡的怪兽的攻击力之和最大。
思路:我们对于每只怪兽,把它的 ai 权值更新到max型线段树中的 bi 点去,枚举每只怪兽 i,我们从线段树中查找 区间[1, b[i] ]的最大值mx,如果mx存在,那么更新答案 ans = max(ans, mx),如果mx >= b[i],那么 ans = max(ans, mx + a[i]),但是细节挺多,有可能查到的怪兽是 i ,这样是不合法的,怎么消除这个问题,留给你深思啦(这题我代码写的巨丑)。

#include
using namespace std;
const int maxn = 3e5 + 10, N = 1e9;
int mx[maxn * 35], cur[maxn * 35], ls[maxn * 35], rs[maxn * 35];
int a[maxn], b[maxn], cnt;
void up(int &o, int l, int r, int k, int v, int id, int opt) {
    if (!o)
        o = ++cnt;
    if (l == r) {
        if (opt)
            mx[o] = v, cur[o] = id;
        else if (mx[o] < v)
            mx[o] = v, cur[o] = id;
        return;
    }
    int m = (l + r) / 2;
    if (k <= m)
        up(ls[o], l, m, k, v, id, opt);
    else
        up(rs[o], m + 1, r, k, v, id, opt);

    mx[o] = max(mx[ls[o]], mx[rs[o]]);
    if (mx[ls[o]] == mx[o])
        cur[o] = cur[ls[o]];
    else
        cur[o] = cur[rs[o]];
}
int qu(int o, int l, int r, int ql, int qr) { // 查询最大值下标
    if (ql > qr)
        return 0;
    if (l >= ql && r <= qr)
        return cur[o];
    int m = (l + r) / 2, p = 0, tmp = 0;
    if (ql <= m)
        tmp = qu(ls[o], l, m, ql, qr);
    if (a[tmp] > a[p])
        p = tmp;
    if (qr > m)
        tmp = qu(rs[o], m + 1, r, ql, qr);
    if (a[tmp] > a[p])
        p = tmp;
    return p;
}
map<int, int> mp, mp2;
struct node{
    int a, b;
    node (int t1, int t2) {a = t1, b = t2; }
    bool operator<(const node& t)const {
        return a > t.a;
    }
};
vector<node> G[maxn];
int main()
{
    int n, rt = 0, sz = 0;
    scanf("%d", &n);
    int ans = 0, t1 = 1, t2 = 2;
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &a[i], &b[i]);
        if (!mp2[b[i]])
            mp2[b[i]] = ++sz;
        G[mp2[b[i]]].push_back(node(a[i], i));
        up(rt, 1, N, b[i], a[i], i, 0);
    }
    for (int i = 1; i <= sz; i++)
         sort(G[i].begin(), G[i].end());
    for (int i = 1; i <= n; i++) {
        int tmp = qu(rt, 1, N, 1, a[i]);
        if (tmp == i) {
            int v = 0;
            int t = mp2[b[i]], ttt = 0;
            if (G[t].size() > 1)
                v = G[t][1].a, ttt = G[t][1].b;
            up(rt, 1, N, b[i], v, ttt, 1);
        }
        int k = qu(rt, 1, N, 1, a[i]);
        if (k) {
            if (a[k] > ans)
                ans = a[k], t1 = i, t2 = k;
            if (a[k] >= b[i] && a[k] + a[i] > ans)
                ans = a[k] + a[i], t1 = i, t2 = k;
        }
        if (tmp == i)
            up(rt, 1, N, b[i], G[mp2[b[i]]][0].a, G[mp2[b[i]]][0].b, 1);
    }
    printf("%d\n%d %d\n", ans, t1, t2);
}

G. Akinator
题意:很绕,有 n 个人,有一个是小偷,要求你必须经过询问刚好 k 次,每次询问一堆人是否有小偷,找到这个小偷,同时求一个东西:如果你是第 m 次确定了第 i 个人的身份,那么这个人贡献的权值是:ai * m,我们要求最小的总的贡献sum / 总的ai,可以对ai进行排序。
思路:先对a数组排序,设d[l][r][k]为已经问了 k 次,再去确认区间 [l , r] 的人的身份的最小贡献,转移方程:dp[l][r][k] = min(d[ l ] [ i ][k + 1] + d[i + 1][ r ][k + 1]) (i <= i < r)

#include
#define ll long long
using namespace std;
int n, k;
ll d[101][101][101], a[101], sum, inf = 1e18;
ll dfs(int l, int r, int step) {
    if (step > k)
        return inf;
    if (l == r)
        return a[l] * step;
    if (d[l][r][step])
        return d[l][r][step];
    d[l][r][step] = inf;
    for (int i = l; i < r; i++)
        d[l][r][step] = min(d[l][r][step], dfs(l, i, step + 1) + dfs(i + 1, r, step + 1));
    return d[l][r][step];
}
int main() {
    cin>>n>>k;
    for (int i = 1; i <= n; i++)
        cin>>a[i], sum += a[i];
    if (ceil(log2(n)) > k)
        return puts("No solution"), 0;
    sort(a + 1, a + 1 + n);
    ll ans = dfs(1, n, 0);
    ll tmp = __gcd(ans, sum);
    printf("%lld/%lld\n", ans / tmp, sum / tmp);
}

H. Missing Number
轩少
I. Painting a Square
题意:有一个大方形,你有一个小方形刷子,问最小需要刷移动多少次,能把大方形刷满。
思路:我们发现我们每次刷,肯定是贴着大方形转圈圈的刷是最优的,每次刷一圈,大方形的边长就会缩小 2 * 小方形边长,然后就水题啦。

#include
#define ll long long
using namespace std;
ll dfs(ll a, ll b) {
    if (a <= b)
        return a;
    if (a <= b * 2)
        return (a - b) * 3 + b;
    return (a - b) * 4 + dfs(a - 2 * b, b);
}
int main()
{
    ll a, b;
    cin>>a>>b;
    cout<<dfs(a, b) - b;
}

J. The Power of the Dark Side - 2
轩少
K. Deck Sorting
题意:给你一个字符串s,有两个空柱子,你按顺序依次把这些字符放到两个柱子上,最后把两个柱子拼接,问是否能形成一个相同的字符必连续的目标串。
思路:因为最多只有3种字符,我们枚举目标串的排列,然后判断其是否可行,怎么判断:假设目标串排列是:RGB,我们把R放在第一个柱子,B放在第二个柱子,那么G有可能在R下面,也有可能在B上面,我们依次枚举s,如果出现了B,那么我们标记一下第二个柱子不能放G了,接下来如果出现了G,因为有了标记,那么G只能放第一个柱子,我们判断R放完了没有,如果放完了那么放第一个柱子下面,如果没放完,那么不合法。

#include
using namespace std;
string s;
int ok(string targ) {
    map<char, int> mp, mp2;
    for (auto c : s)
        mp[c]++;
    for (int i = 0; i < targ.length(); i++)
        mp2[targ[i]] = i + 1;
    int flag = 0;
    for (auto c : s) {
        mp[c]--;
        if (mp2[c] == 2) {
            if (mp[targ[0]] && flag)
                return 0;
        }
        else if (mp2[c] == 3) {
            flag = 1;
        }
    }
    return 1;
}
int main() {
    cin>>s;
    reverse(s.begin(), s.end());
    if (ok("RGB") || ok("RBG") || ok("GRB") || ok("GBR") || ok("BRG") || ok("BGR"))
        puts("YES");
    else
        puts("NO");
}

L. Inscribed Circle
轩少
M. Shlakoblock is live!
题意:有n个游戏,每个游戏有两个属性pi, vi,你可以选择一些游戏,使得vi + 1,使得所有的游戏 pi * vi 的总和除以总 vi 和最大。
思路:水题,先把游戏按照pi从大到小排序,我们从0开始选择枚举选择的游戏个数,然后把求出来的值更新到答案就行。

#include
#define pi pair
#define mk make_pair
using namespace std;
const int maxn = 1010;
struct node {
    int p, v, id;
    bool operator<(const node& t) const {
        return p > t.p;
    }
} a[maxn];
bool cmp(pi x, pi y) {
    return 1ll * x.first * y.second > 1ll * y.first * x.second;
}
int main()
{
    int T;
    scanf("%d", &T);
    while (T--) {
        int n, sum = 0, res = 0, p = 0;
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &a[i].p, &a[i].v);
            a[i].id = i;
            sum += a[i].v;
            res += a[i].p * a[i].v;
        }
        sort(a + 1, a + 1 + n);
        pi ans = mk(res, sum);
        for (int i = 1; i <= n; i++) {
            res += a[i].p;
            sum++;
            if (cmp(mk(res, sum), ans))
                ans = mk(res, sum), p = i;
        }
        int d = __gcd(ans.first, ans.second);
        printf("%d/%d\n", ans.first / d, ans.second / d);
        printf("%d\n", p);
        if (!p)
            puts("");
        for (int i = 1; i <= p; i++)
            printf("%d%c", a[i].id, i == p ? '\n' : ' ');
    }
}

你可能感兴趣的:(比赛----gym,数据结构----线段树,图论----lca,动态规划)