2017-2018 ACM-ICPC Southeastern European(SEERC 2017)【solved :5 / 12】

题目链接:http://codeforces.com/gym/101669

A - Concerts(dp)

给出k( 1 300 ),n( 1 1 e 5 ),代表一个人要参加k场演唱会,一共有n场演唱会会举办。接下来给出每场演唱会要歇息的天数(一共26个乐队用ABC…Z代表),接下来一行给出n天演唱会举办的顺序。问这个人按顺序参加这k场演唱会有几种方案。

思路:
sum[i][j]:第i天的时候已经参加到了第j场演唱会的方案数。
pre[i][j]: k = 1 n s u m [ k ] [ j ]
可以推得方程:sum[i][j] = (sum[i][j] + pre[idx][j-1]) % mod;(idx代表回滚到参加第j-1场演唱会的日期)
然后考虑到内存的限制,无法开两个三千万的数组,所以可以滚一下sum数组。

#include <bits/stdc++.h>
using namespace std;
//k 300 n 1e5
const int maxn = 1e5 + 5;
int sum[2][300 + 5], pre[maxn][300+5];
const int mod = 1e9 + 7;

char s1[300 + 5], s2[maxn];
int cost[30];
int main()
{
    int k, n;
    scanf("%d%d", &k, &n);
    for(int i = 0; i < 26; i++) scanf("%d", &cost[i]);
    scanf("%s%s", s1 + 1, s2);
    for(int i = 0; i < n; i++)
    {
        if(i)   pre[i][1] = pre[i-1][1];
        if(s1[1] == s2[i])  pre[i][1]++;
    }

    long long ans = 0;
    for(int i = 0; i < n; i++)
    {
        for(int j = 2; j <= k; j++)
        {
            sum[i&1][j] = 0;
            if(i)   pre[i][j] = pre[i - 1][j];
            if(s1[j] != s2[i])  continue;
            char ch = s1[j-1];
            int c = cost[ch-'A'];
            int idx = i - (c + 1);
            if(idx < 0) continue;
            sum[i&1][j] = (sum[i&1][j] + pre[idx][j-1]) % mod;
            pre[i][j] = (sum[i&1][j] + pre[i][j]) % mod;
            if(j == k)ans = (ans + sum[i&1][k]) % mod;
        }
    }
    printf("%lld\n", ans);
    return 0;
}

D - Harry Potter and The Vector Spell (并查集)

给出一个m*n的01矩阵(每列恰有两个非零),问它在模2意义(异或xor)下的秩是几。
( n m 1 e 5 )

思路:转换一下输入以后,可以考虑到每次询问u和v两列的时候,只要两者在一个并查集内,就代表这一行是无效的,否则计数,最后输出即可

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int pa[maxn];
int uf_find(int x){return x == pa[x] ? x : pa[x] = uf_find(pa[x]);}
void join(int x, int y)
{
    int fx = uf_find(x);
    int fy = uf_find(y);
    if(fx != fy)
    {
        pa[fx] = fy;
    }
}
vector<int>row[maxn];
int main()
{
    int m, n;
    scanf("%d%d", &m, &n);
    for(int i = 1; i <= m; i++)
    {
        int k;
        scanf("%d", &k);
        for(int j = 0; j < k; j++)
        {
            int x;
            scanf("%d", &x);
            row[x].push_back(i);
        }
    }
    for(int i = 1; i <= m; i++) pa[i] = i;
    int ans = 0;
    for(int i = 1; i <= n; i++)
    {
        if(row[i].size() == 0)  continue;
        int u = row[i][0], v = row[i][1];
        int fx = uf_find(u), fy = uf_find(v);
        if(fx != fy)
        {
            ans++;
            join(u, v);
        }
    }
    printf("%d\n", ans);
    return 0;
}

F - Binary Transformations (思维题)

给出n( 1 e 5 ),接下来给出每一位的cost[i],接下来给出s1串和s2串,让你把s1串转变为s2串,转变s1串中第i位的时候,所需的花费是,第i位转变后,s1串中所有s1[k] = 1的k的cost加和,问最小花费是多少。

思路:一开始考虑的策略是贪心的去做,先考虑1到0的转变,转变肯定从cost大到小转变,再考虑0到1的转变,转变肯定从cost小到大。wa了以后考虑到,1到1的时候,如果cost很大,可能做了1到0的转变,再做0到1的转变,反而会使得答案更小。所以将1到1的按cost排序,从大到小排序,枚举一遍从大到小有几个拿去做1to0,0to1。然后再套上之前的贪心即可。

#include <stdio.h>
#include <iostream>
#include <algorithm>
#include <set>
#include <vector>
#include <string.h>
using namespace std;
typedef long long LL;
const int maxn = 5005;
int c[maxn];
char a[maxn], b[maxn];
multiset<int> decr, incr;
vector<int> ori;
int s1[maxn], s2[maxn];
int main()
{
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &c[i]);
    }
    scanf("%s%s", a + 1, b + 1);
    int k = 0, p = 0, q = 0;
    for (int i = 1; i <= n; i++)
        s1[i] = a[i] - '0', s2[i] = b[i] - '0';
    for (int i = 1; i <= n; i++)
    {
        if (s1[i] == 1 && s2[i] == 0)
            decr.insert(c[i]), k++;
        else if (s1[i] == 0 && s2[i] == 1)
            incr.insert(c[i]), p++;
        else if (s1[i] + s2[i] == 2)
            ori.push_back(c[i]), q++;
    }
    sort(ori.begin(), ori.end());
    LL res = 0;
    LL ans = 0;
    ans += LL(k + p);
    LL cost = 0;
    for (auto o : ori)
        cost += o;
    ans *= cost;
    int idx = k - 1;
    for (auto o : decr)
    {
        ans += LL(idx) * o;
        idx--;
    }
    idx = p;
    for (auto o : incr)
    {
        ans += LL(idx) * o;
        idx--;
    }
    //cout << ans << endl;
    int sz = ori.size();
    for (int i = sz - 1; i >= 0; i--)
    {
        LL cur = 0;
        cost -= ori[i];
        k++;p++;
        decr.insert(ori[i]);
        incr.insert(ori[i]);
        cur += cost * (k + p);
        idx = k - 1;
        for (auto o : decr)
        {
            //cout << idx << ' ' << o << endl;
            cur += LL(idx) * o;
            idx--;
        }
        //cout << cur << ' ';
        idx = p;
        for (auto o : incr)
        {
            cur += LL(idx) * o;
            idx--;
        }
        //cout << cur << endl;
        ans = min(ans, cur);
    }
    printf("%I64d\n", ans);
    return 0;
}

G - Robots (water)

水题不表。

K - Escape Room (思维)

让你给出一个满足条件的最小字典序的一个1到n的排列。条件:给出一个数组代表着这个排列从第i位开始的最长递增子序列长度为a[i]

思路:可想而知,从左往右扫,a[i]=1的可以考虑到是从这一位至末尾最大的数,所以n一定是在第一个a[i]=1的位置,所以从左往右扫a[i]=1,a[i]=2依次铺放n,n-1,…,1。考虑到复杂度的原因,可以用一个set来维护一下即可。

#include <bits/stdc++.h>
using namespace std;

int main()
{
    int n;
    scanf("%d", &n);
    set<pair<int, int>>s;
    for(int i = 0; i < n; i++)
    {
        int x;
        scanf("%d", &x);
        s.insert({x, i});
    }
    vector<int>ans;
    ans.resize(n);
    int cnt = n;
    for(auto o : s)
    {
        int idx = o.second;
        int val = o.first;
        ans[idx] = cnt--;
    }
    for(int i =0 ;i < n; i++)
        printf("%d%c", ans[i], i + 1 == n ?'\n':' ');

    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_29556211/article/details/80428339