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(1n109) n ( 1 ≤ n ≤ 10 9 ) ,构造一个算式使其结果为n,且不使用超过100个1。

题解

注意到如果每次都除2,就是用 (1+1) ( 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) π ( x , y ) 表示 gcd(x,y) g c d ( x , y ) 的质因子个数,比如 π(30,105)=2,π(8,16)=1,π(2,3)=0 π ( 30 , 105 ) = 2 , π ( 8 , 16 ) = 1 , π ( 2 , 3 ) = 0 。定义

S(a,b)=(x,y)π(x,y)(ax<yb) S ( a , b ) = ∑ ( x , y ) π ( x , y ) ( a ≤ x < y ≤ b )

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

题解

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

x=bdad+1

为了统一,我们改写:

x=bda1d x = ⌊ b d ⌋ − ⌊ a − 1 d ⌋

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

如果 d<a d < a ,那么我们每次跳 min{aai,bbi} min { ⌊ a ⌊ a i ⌋ ⌋ , ⌊ b ⌊ b i ⌋ ⌋ } ,保持 bp1=bp2andap1=ap2 ⌊ 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(1n100) n ( 1 ≤ n ≤ 100 ) 任务, pi p i 时刻后可以开始执行,必须在 ki k i 时刻前结束,需要 ci c i 的时间执行,执行的时间不必连续。同一时段最多执行 m m 个任务,问是否可行。

题解

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

K

大水题不写了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值