Codeforces Round #500 (Div. 2) [based on EJOI]

回忆场,以前打过,但貌似现在还是不会D。

A - Piles With Stones

题意:有n堆石头,每堆石头有ai个。数列a,b记录了每堆石头的数量。已知两次记录至今可以有任意个人选择移动一颗石头或者拿走一颗石头,问是否合法。

题解:求和。

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

void test_case() {
    int n;
    scanf("%d", &n);
    int s1 = 0, s2 = 0;
    for(int i = 1, x; i <= n; ++i) {
        scanf("%d", &x);
        s1 += x;
    }
    for(int i = 1, x; i <= n; ++i) {
        scanf("%d", &x);
        s2 += x;
    }
    puts(s2 <= s1 ? "Yes" : "No");
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

B - And

题意:有n个数一个x,每次操作可以把一个ai变成ai&x,求最少的步数使得有至少一对ai,aj相等。

题解:分别存下a和a&x的值,注意不能够让a匹配a&x,方法是当两个相同时只存a。

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

int cnt1[100005];
int cnt2[100005];
void test_case() {
    int n, x;
    scanf("%d%d", &n, &x);
    while(n--) {
        int a;
        scanf("%d", &a);
        cnt1[a]++;
        if((a & x) != a)
            cnt2[a & x]++;
    }
    int ans = 1e9;
    for(int i = 0; i <= 100000; ++i) {
        if(cnt1[i] >= 2)
            ans = min(ans, 0);
        if(cnt2[i] >= 2)
            ans = min(ans, 2);
        if(cnt1[i] && cnt2[i])
            ans = min(ans, 1);
    }
    if(ans == 1e9)
        ans = -1;
    printf("%d
", ans);
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

C - Photo of The Sky

题意:有个星空的记录,上面有n颗星星,可惜他们的xy坐标都被打乱了。复原一个序列,使得包围这些星星的矩形面积最小。

题解:大胆猜想,既然是个C题!把所有数字sort之后,我们假设其中一边一定是一段连续的区间。然后滑动窗口。证明:假设a1和a2n都不是x,那么y的极差已经确定,需要最小化x的差,滑动一次一定可以。否则,即a1或a2n的其中之一是x,那么肯定要把小的一段都给x,大的一段都给y。

注:需要注意边界啊,要(可重)不漏。

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

int a[200005];
void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= 2 * n; ++i)
        scanf("%d", &a[i]);
    sort(a + 1, a + 1 + 2 * n);
    ll ans = 1ll * (a[n] - a[1]) * (a[2 * n] - a[n + 1]);
    for(int i = 2; i <= n; ++i)
        ans = min(ans, 1ll * (a[i + n - 1] - a[i]) * (a[2 * n] - a[1]));
    printf("%lld
", ans);
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

D - Chemical table

题意:有一张n*m的元素周期表,其中可以用(r1,c1),(r2,c1),(r1,c2)三个元素生成(r2,c2),求填满这个元素周期表还需要买的最小的元素。

废话:当时大一升大二的暑假的时候做的……当时我CF的分貌似和fzr是同届里少有的蓝色,后面就不行了。归根到底是我没有去挑战这些难的题目,混在宿舍里面打游戏浪费时间,没有好好补题,到现在一年半过去了他们都橙色了我还是蓝色。虽然我觉得按照我的积淀应该年底可以到紫色吧……不过当时补了的题还是没有印象,说明还是菜到没有直觉(不过C题的直觉是有了)。

题解:和现在补题时的直觉一样,已经有了这个图是二分图的模型的直觉,但是不知道怎么转化。以行列分别为点,元素为其中的边,事实上添加一个新的元素并不会改变原本的二分图的连通性。而为什么一定是二分图的连通块的个数-1呢?貌似很显然,假如我们能够证明/观察出一个连通的二分图一定是可以补完整张图的,那么我们只需要添加最少的边使得它连通就可以。

是个挺好的图论题,记住了。

注:我当时干嘛还要分两个dfs写呢?有病吗?其实连通块计数还是并查集最快。加油,每天一场div2,一个月就可以上div1,还有2个星期。

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

bool vis[400005];
vector<int> G[400005];

void dfs(int u) {
    vis[u] = 1;
    for(auto v : G[u]) {
        if(!vis[v])
            dfs(v);
    }
}

void test_case() {
    int n, m, q;
    scanf("%d%d%d", &n, &m, &q);
    while(q--) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(n + v);
        G[n + v].push_back(u);
    }
    int cnt = 0;
    for(int i = 1; i <= n + m; ++i) {
        if(!vis[i]) {
            dfs(i);
            ++cnt;
        }
    }
    printf("%d
", cnt - 1);
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

E - Hills

题意:有n(<=5000)个地点,每个地点当它比它的邻居(假如有)都高的话,则是小山丘,必须把房子建在小山丘上。求建1,2,...,(n+1)/2个房子的最小代价。注意你只能花费每代价1降低一个地点的高度。

题解:看起来数据量很小,我口胡一个dp!dp[i][j][k]表示前i个地点,其中i地点以高度j结尾,i-1及其之前有k个山顶的,所花费的最小代价,所以有dp[i][j][k]=dp[i-1][j+1~h[i-1]][k-1]+h[i]-j。可惜这样转移是立方的。

高手解法:可以由奇数的情况得出一个很显然的结论“建房子的小山丘是不会被挖的”,非常明显挖了就很不好。设dp[i][j][0/1]为前i个地点,建了j个房子,其中第i个点不建或者建的最小代价。可以发现这样设状态少了一维高度,为什么呢?是因为i-1高度一定是挖到比两个房子都矮(假如i-2也是房子,被两个房子夹),或者只需比i号房子矮(i-2不是房子),这样就少了一维,需要注意的是若i点不建房子,需要补回i-1点建房子的费用。看起来还可以滚动数组,但是没必要,这个算法时间就是n方的。

废话:这一年的经验让我学习速度up了,或许要轮到我量变到质变了。

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

const int INF = 0x3f3f3f3f;
int h[5005];
int dp[5005][2505][2];

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &h[i]);
    memset(dp, INF, sizeof(dp));
    dp[0][0][0] = 0;
    dp[1][0][0] = 0;
    dp[1][1][1] = 0;
    for(int i = 2; i <= n; ++i) {
        for(int j = 0, c = (i + 1) / 2; j <= c; ++j)
            dp[i][j][0] = min(dp[i - 1][j][0], dp[i - 1][j][1] + max(0, h[i] - (h[i - 1] - 1)));
        for(int j = 1, c = (i + 1) / 2; j <= c; ++j)
            dp[i][j][1] = min(dp[i - 2][j - 1][0] + max(0, h[i - 1] - (h[i] - 1)), dp[i - 2][j - 1][1] + max(0, max(h[i - 1] - (h[i] - 1), h[i - 1] - (h[i - 2] - 1))));
    }
    for(int k = 1, c = (n + 1) / 2; k <= c; ++k)
        printf("%d%c", min(dp[n][k][0], dp[n][k][1]), " 
"[k == c]);
}

int main() {
#ifdef KisekiPurin
    freopen("KisekiPurin.in", "r", stdin);
#endif // KisekiPurin
    int t = 1;
    for(int ti = 1; ti <= t; ++ti) {
        //printf("Case #%d: ", ti);
        test_case();
    }
}

启示:不需要把高度也设为状态之一,把高度的决定让给其他状态来决定。仔细思考之后发现高度是很容易被建不建房子所制约的,而且高度所造成的花费也非常容易计算。

参考资料:

codeforces 1013e E. Hills - yjt9299的博客 - CSDN博客

原文地址:https://www.cnblogs.com/KisekiPurin2019/p/11899245.html