Petrozavodsk Summer Training Camp 2017, Warsaw U Contest

C: Painting

题目大意

你有数字1~n,对于每个数字i,用且仅用一次刷子将某个区间修改为i。每次刷子的代价为区间长度。总代价最大是多少?
比如刷后的数列是2 1 2 3,那么可以:[1,4]=3,[1,3]=2,[2,2]=1。总共代价为8是最大的。

题解

首先我们可以搞出区间的包含关系,那么对于被一个区间包含的一些区间,如果不和其他子区间邻接,那么就只能修改这个子区间,否则若一些子区间邻接,比如1 1 3 3,我们就可以先改成1 1 1 1再改成1 1 3 3 使得代价最大,显然每次只留一端的一个区间最好,至于选择左端还是右端,dp即可。

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define FOR(i,j,k) for(i=j;i<=k;++i)
const int N = 100005, M = 5005;
vector<int> p[M], sub[M];
int c[N], l[M], r[M], sum[M], dp[M][M];

bool cmp(int a, int b) {
    return l[a] < l[b];
}

int work(const vector<int> &lens) {
    int i, j, n = lens.size();
    FOR(i,1,n) {
        dp[i][i] = lens[i - 1];
        sum[i] = sum[i - 1] + lens[i - 1];
    }

    FOR(i,1,n) for (j = i; j; --j)
        dp[j][i] = max(dp[j + 1][i], dp[j][i - 1]) + sum[i] - sum[j - 1];
    return dp[1][n];
}

int main() {
    int n, m, i, j, ans = 0;
    scanf("%d%d", &n, &m);
    FOR(i,1,n) {
        scanf("%d", &c[i]);
        p[c[i]].push_back(i);
    }

    FOR(i,1,m) {
        l[i] = p[i].front();
        r[i] = p[i].back();
    }

    FOR(i,1,m) {
        int f = 0;
        FOR(j,1,m)
            if (l[j] < l[i] && r[i] < r[j] && l[j] > l[f])
                f = j;
        sub[f].push_back(i);
    }
    FOR(i,0,m) {
        sort(sub[i].begin(), sub[i].end(), cmp);
        vector<int> lens;
        for (j = 0; j < sub[i].size(); ++j) {
            if (j != 0 && l[sub[i][j]] != r[sub[i][j - 1]] + 1) {
                ans += work(lens);
                lens.clear();
            }
            lens.push_back(r[sub[i][j]] - l[sub[i][j]] + 1);
        }
        ans += work(lens);
    }
    printf("%d", ans);

    return 0;
}

D: Ones

题目大意

一个算式中,只可以有括号、加法、乘法和1,给定 n ( 1 n 10 9 ) ,构造一个算式使其结果为n,且不使用超过100个1。

题解

注意到如果每次都除2,就是用 ( 1 + 1 ) 不断乘起来最后100个1就绰绰有余了。

#include <cstdio>

void work(int x) {
    if (x == 1) printf("1");
    else if (x == 2) printf("1+1");
    else if (x == 3) printf("1+1+1");
    else {
        if (x & 1) printf("1+");
        printf("(1+1)*(");
        work(x / 2);
        printf(")");
    }
}

int main() {
    int t, k;
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &k);
        work(k);
        putchar('\n');
    }
    return 0;
}

G: Permutation

题目大意

给定一个数列,提取出两个子序列使得一个严格递增一个严格递减,两个子序列覆盖数列所有元素。

题解

结果有3种情况:

  / |  \   | \     /
 /  |   \  |  \   /
/   |    \ |   \ /
\   |    / |   / \
 \  |   /  |  /   \
  \ |  /   | /     \

如果元素比上升的小,下降的大,那么一定无解。
否则如果比上升的大,就归到上升,比下降的小,就归到下降。
否则就是第二种情况,如果当前元素比下一个元素小,那么一定上升,否则下降。

#include <cstdio>
#include <vector>
using namespace std;

int p[100005];
int main() {
    int t, n, i;
    scanf("%d", &t);
    while (t--) {
        scanf("%d", &n);
        vector<int> R { 0 }, M { n + 1 };
        for (i = 0; i < n; ++i)
            scanf("%d", &p[i]);
        for (i = 0; i < n; ++i) {
            if (p[i] < R.back() && p[i] > M.back()) {
                puts("NO");
                break;
            } else if (p[i] < R.back())
                M.push_back(p[i]);
            else if (p[i] > M.back() || i == n - 1)
                R.push_back(p[i]);
            else if (p[i] < p[i + 1])
                R.push_back(p[i]);
            else
                M.push_back(p[i]);
        }
        if (i == n) {
            puts("YES");
            R.erase(R.begin());
            M.erase(M.begin());
            printf("%u", R.size());
            for (auto &r : R) printf(" %d", r);
            printf("\n%u", M.size());
            for (auto &m : M) printf(" %d", m);
            printf("\n");
        }
    }
    return 0;
}

H: Primes

题目大意

定义 π ( x , y ) 表示 g c d ( x , y ) 的质因子个数,比如 π ( 30 , 105 ) = 2 , π ( 8 , 16 ) = 1 , π ( 2 , 3 ) = 0 。定义

S ( a , b ) = ( x , y ) π ( x , y ) ( a x < y b )

在线询问 S ( a , b ) ,询问组数 5 10 4

题解

考虑一个质因子 d 对答案的贡献,若d在 [ a , b ] 中的倍数的个数为 x ,那么对答案的贡献就是 x ( x 1 ) 2 。也就是说,我们枚举 [ 1 , b ] 内的所有质数,求一次即可。
当然不行了质数个数有 8 10 4
对于 x ,我们有

x = b d a d + 1

为了统一,我们改写:
x = b d a 1 d

我们发现,连续的几个质数他们的 x 可以是一样的,考虑对于每段段 x 相同的质数求一次。
如果 d a ,那么 a 1 d = 0 ,我们只考虑前一项,对于两个素数,如果 b p 1 = b p 2 ,那么 b b p 2 p i b b p 2 。因此对于这个区间内的所有质数的x都是相等的,我们可以算一整段,每次跳 b b i

如果 d < a ,那么我们每次跳 min { a a i , b b i } ,保持 b p 1 = b p 2 a n d a p 1 = a p 2 就能使整个等式成立。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

const int N = 1000005;
bool is_prime[N];
int sum[N];
void sieve() {
    memset(is_prime, 1, sizeof is_prime);
    for (int i = 2; i < N; ++i) {
        if (is_prime[i]) {
            for (int j = i + i; j < N; j += i)
                is_prime[j] = false;
        }
        sum[i] = sum[i - 1] + is_prime[i];
    }
}

int main() {
    int q;
    scanf("%d", &q);
    sieve();
    while (q--) {
        int a, b;
        long long ans = 0;
        scanf("%d%d", &a, &b);
        --a;
        for (int i = 1, j; i <= b; i = j + 1) {
            j = a >= i ? min(a / (a / i), b / (b / i)) : b / (b / i);
            long long k = b / i - a / i;
            ans += k * (k - 1) / 2 * (sum[j] - sum[i - 1]);
        }
        printf("%lld\n", ans);
        fflush(stdout);
    }


    return 0;
}

J: Scheduling

题目大意

n ( 1 n 100 ) 任务, p i 时刻后可以开始执行,必须在 k i 时刻前结束,需要 c i 的时间执行,执行的时间不必连续。同一时段最多执行 m 个任务,问是否可行。

题解

由于n很小,我们建立n个节点从原点指向,边权为 c i ,对于每个时间点,任务节点连向可行的时间点,容量为1,每个时间点向汇点连容量为m的边跑最大流判断流量是否为 c i 即可。时刻点可以合并。

K

大水题不写了

猜你喜欢

转载自blog.csdn.net/huanghongxun/article/details/79606403