Codeforces Round #621 (Div. 1 + Div. 2)

题目链接:https://codeforces.com/contest/1307

A - Cow and Haybales

一开始读错了题,看见t神0分钟过了才知道确实是签到。

题意:给一个非负整数序列,每次操作可以把一个非零数字-1,相邻的数字+1。求至多能使得第1个数字变得多大?

题解:贪心,尽量从最近的数字往第1个数字搬。事实上因为数据量极小,每次搬1确实也可以。

B - Cow and Friend

题意:给一个非负整数序列,每次操作先选一个数字,然后在二维平面上走恰好这个数字长度,求最少要多少次操作才能从(0,0)到(x,0)。

题解:首先假如有个与x相等的数字,那么答案就是1。其次,假如最大的数字maxa>=x/2,那么可以走一个等腰三角形到达(在完全伸开到达2*max和完全折叠到达0之间必有一点恰好等于x),答案是2。那么得到一个理所当然的贪心,就是先往x走到尽可能近,然后假如某一步走到超过了x,就把最后的两步折起来变成一个等腰三角形。

C - Cow and Message

题意:给一个字符串,求其中出现次数最多的子序列的出现次数。

题解:当时猜了一个结论,就是一定是两个不同的字母的组合成的长度为2的序列,由于这样的组合本身就26*25种,所以可以直接莽,假如枚举的是"ab",那么记录"a"的数量,然后每次遇到"b"就把这个前面的"a"的数量加上去。然后相了一想,假如是"aaaaa",答案对应的序列就应该是"aa",然后在cnta*(cnta+1)/2和cnta*(cnta-1)/2之间纠结,最后从"aaa"中确定是后者。准备提交的时候想到,假如只有1个字符,那我的答案岂不是0?还好立刻补了一发。其实假如把初始值设为1就没有这个问题了。最后发现其实两个相同的字母的统计方法也可以包含在第1种情况中。

感觉应该会有人因为只有1个字母的数据挂掉。确实,main test的36就是这个东西,有几个紫名爷掉了。

当时hack了一个用int的人,没想到被

#define int long long

制裁了。

char s[100005];
int cnt[128];
void test_case() {
    scanf("%s", s + 1);
    int n = strlen(s + 1);
    for(int i = 1; i <= n; ++i)
        ++cnt[s[i]];
    ll ans = 0;
    for(int i = 'a'; i <= 'z'; ++i)
        ans = max(ans, 1ll * cnt[i]);
    for(int i = 'a'; i <= 'z'; ++i)
        ans = max(ans, 1ll * cnt[i] * (cnt[i] - 1ll) / 2ll);
    for(int i = 'a'; i <= 'z'; ++i) {
        for(int j = 'a'; j <= 'z'; ++j) {
            if(j == i)
                continue;
            if(1ll * cnt[i]*cnt[j] <= ans)
                continue;
            int cnti = 0;
            ll sum = 0;
            for(int k = 1; k <= n; ++k) {
                if(s[k] == i)
                    ++cnti;
                else if(s[k] == j)
                    sum += cnti;
            }
            ans = max(ans, sum);
        }
    }
    printf("%lld\n", ans);
}

*D - Cow and Fields

第一次在比赛过程中能过标签带graph的1900(猜)的题耶!

题意:给n个点,m条边的无向连通图,每天要从1号点走到n号点。给出k个特殊点,要求在这些特殊点之间加入恰好1条边,使得整个图的从1到n的最短路尽可能长,求出这个最短路的长度。

题解:首先有一个很显然但是没什么卵用的观察,就是假如特殊点之间有边,那么加一条重边就是答案。这种题好像做过几次有点套路了?还是说变强了?当时猜测是要按dis1[x]的大小分层考虑,每一层的x都向同层或者深层的y连边,这样的最短路就保证为dis1[x]+1+disn[y],但是不知道怎么证明。今天补上这个证明。

设这两个特殊点为x,y,考虑必须走新加的边的最短路,那么这个最短路的长度必定为Lxy=min(dis1[x]+1+disn[y],dis1[y]+1+disn[x]),然后从所有的x,y的组合中选出最大的Lxy,与原本的最短路取min,就是题目要找的答案。麻烦的地方在于一开始的这个min,毕竟这条边是有两种走法的,然后隐隐约约感觉到,貌似可以通过额外的信息确定这条边要按哪个方向走(才可能形成最短路)。

虽然要去掉这个min要分很多种情况,但是不失一般性,可以先规定dis1[x]<=dis1[y],然后分disn[x]<disn[y],disn[x]=disn[y],disn[x]>disn[y]三种情况讨论。

先考虑最简单的disn[x]=disn[y],此时直接提取到外面Lxy=disn[y]+1min(dis1[x],dis1[y])=dis1[x]+1+disn[y]。要取这个的最大值,只需要按dis1[x]排序满足dis1[x]<=dis1[y]后,使用一个后缀最大值即可。

可能存在某些点,满足dis1[x]<=dis1[y],同时disn[x]<disn[y],例如一个T形的,1号点和n号点在两端,x在中间的交叉处,y在下面。这个时候的最短路不可能比dis1[x]+disn[x]=dis1[n]更短,直接忽略,所以把不优的答案dis1[x]+1+disn[y]也合并进来并不会产生不良影响

可能存在某些点,满足dis1[x]<=dis1[y],同时disn[x]>disn[y],比如x和y都在原本的从1号点到n号点的最短路上,就是满足这样的式子,这种情况最小值确实就是dis1[x]+1+disn[y],因为它(有可能)缩短了最短路,而且从dis1[x]+1+disn[y]<dis1[y]+1+disn[x]也可以知道直接合并dis1[x]+1+disn[y]即可。

综上所述,按dis1[x]排序,然后取disn[y]的后缀最大值。

vector<int> G[200005];
int dis1[200005];
int disn[200005];
bool vis[200005];
int *dis;
 
priority_queue<pair<int, int> > pq;
void dijkstra(int s, int n) {
    while(!pq.empty())
        pq.pop();
    for(int i = 1; i <= n; ++i) {
        dis[i] = 0x3f3f3f3f;
        vis[i] = 0;
    }
    dis[s] = 0;
    pq.push({-dis[s], s});
    while(!pq.empty()) {
        int u = pq.top().second;
        pq.pop();
        if(vis[u])
            continue;
        vis[u] = 1;
        for(int i = 0; i < G[u].size(); ++i) {
            int v = G[u][i];
            if(!vis[v] && dis[v] > dis[u] + 1) {
                dis[v] = dis[u] + 1;
                pq.push({-dis[v], v});
            }
        }
    }
}
 
int a[200005];
 
pii PII[200005];
int sufmax[200005];
 
void test_case() {
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= k; ++i)
        scanf("%d", &a[i]);
    while(m--) {
        int u, v;
        scanf("%d%d", &u, &v);
        G[u].push_back(v);
        G[v].push_back(u);
    }
    dis = dis1;
    dijkstra(1, n);
    dis = disn;
    dijkstra(n, n);
    for(int i = 1; i <= k; ++i)
        PII[i] = {dis1[a[i]], a[i]};
    sort(PII + 1, PII + 1 + k);
    sufmax[k] = disn[PII[k].second];
    for(int i = k - 1; i >= 1; --i)
        sufmax[i] = max(sufmax[i + 1], disn[PII[i].second]);
    /*for(int i = 1; i <= k; ++i)
        printf("%d %d\n", PII[i].first, PII[i].second);
    for(int i = 1; i <= k; ++i)
        printf("%d\n", sufmax[i]);*/
    int ans = disn[1], maxans = 0;
    for(int i = 1; i < k; ++i) {
        int u = PII[i].second;
        int tmp = dis1[PII[i].second] + 1 + sufmax[i + 1];
        if(tmp >= ans) {
            printf("%d\n", ans);
            return;
        }
        maxans = max(maxans, tmp);
    }
    printf("%d\n", maxans);
}

反思:最短路其实可以用bfs的,结合桶排序或者其他什么DAG上dp可以把复杂度确实降低到线性。

收获:貌似最短路的变化一般都从最短路径生成树(以及dis[v]=dis[u]+w的点也是一条最短路),以及根据距离分层的算法来考虑?

posted @ 2020-02-18 09:37  KisekiPurin2019  阅读(250)  评论(0编辑  收藏  举报