Codeforces Round #574 (Div. 2)

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

好像做过这一场,这场的CDE都比较有印象,特别是E,随便掏个数据结构就可以做了。

A - Drinks Choosing

题意:每份饮料恰好有2支,有 \(n\) 个人 \(n\) 种饮料,每个人有自己最喜欢的一种饮料,要恰好买 \(\lceil\frac{n}{2}\rceil\) 份饮料,求最多有多少人喝到自己最喜欢的饮料?

题解:某种饮料每满2就肯定买一份,最后肯定剩下一群1或者0,随便买一些1。

int cnt[1005];

void test_case() {
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 1; i <= n; ++i) {
        int x;
        scanf("%d", &x);
        ++cnt[x];
    }
    int cnt2 = 0;
    for(int i = 1; i <= k; ++i)
        cnt2 += cnt[i] / 2;

    printf("%d\n", (n + 1) / 2 + cnt2);
}

B - Sport Mafia

题意:有 \(n\) 次操作,其中若是eat操作则会使得糖果数量 -1 ,若是第i次put操作则会使得糖果数量 +i ,已知最后恰好剩下 \(k\) 颗糖果,保证有唯一解,求eat了多少次。

题解:显然eat越多就会越少,满足单调性可以二分。

其实设put了x次,则有:

\(\frac{x(x+1)}{2}-(n-x)=k\)

\(x^2+x-2n+2x=2k\)

\(x^2+3x+(-2n-2k)=0\)

由韦达定理可知两根一正一负,可以直接解这个方程的正根。

int n, k;
bool check(int e) {
    ll p = n - e;
    ll sump = (1 + p) * p / 2;
    return (sump - e) >= k;
}
 
void test_case() {
    scanf("%d%d", &n, &k);
    int L = 0, R = n;
    while(1) {
        int M = (L + R) >> 1;
        if(L == M) {
            if(check(R)) {
                printf("%d\n", R);
                return;
            }
            assert(check(L));
            printf("%d\n", L);
            return;
        }
        if(check(M))
            L = M;
        else
            R = M - 1;
    }
}

C - Basketball Exercise

题意:有两行正数,要从两行之间交替选,求选出的和的最大值。

题解:直接dp就可以。设 \(dp[i][0]\) 表示恰好以第 \(i\) 个位置的第 \(0\) 行结尾的最大值,那么有个显然的转移方程在程序中。

int a[100005][2];
ll dp[100005][2];

void test_case() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i][0]);
    for(int i = 1; i <= n; ++i)
        scanf("%d", &a[i][1]);
    dp[1][0] = a[1][0];
    dp[1][1] = a[1][1];
    for(int i = 2; i <= n; ++i) {
        dp[i][0] = max(dp[i - 1][1], dp[i - 2][1]) + a[i][0];
        dp[i][1] = max(dp[i - 1][0], dp[i - 2][0]) + a[i][1];
    }
    printf("%lld\n", max(dp[n][0], dp[n][1]));
}

D1 - Submarine in the Rybinsk Sea (easy edition)

见下

*D2 - Submarine in the Rybinsk Sea (hard edition)

题意:定义一个合成函数 \(f(a,b)\) 它的合成规则是先把 \(b\) 的个位放在个位,再把 \(a\) 的个位放在十位,依次交替直到其中一个放完,剩下的就全放剩下那个数的剩余的高位。给 \(n\) 个数,求 \(\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n}f(a_i,a_j)\)

题解:对于某个 \(x\) 而言,所有相同长度的 \(y\)\(x\) 进行 \(f(x,y)\) 合成之后, \(x\) 对结果的贡献是固定的(可以通过处理 \(x\) 的各位在结果的哪位中确定)。所以可以统计各种长度的数量,再算贡献。好像这题在前几天也写过一个很像的。

int n;
ll a[100005];
ll sumL[100005][11];
ll sumR[100005][11];

int cntlen[11];

ll tmp[11];
ll pow10[21];

void calcL(int i) {
    ll x = a[i];
    for(int j = 1; j <= 10; ++j) {
        tmp[j] = x % 10;
        x /= 10;
        //printf("%lld", tmp[j]);
    }
    //printf("\n");
    for(int Rlen = 1; Rlen <= 10; ++Rlen) {
        sumL[i][Rlen] = 0;
        for(int j = 1; j <= Rlen; ++j)
            sumL[i][Rlen] += pow10[2 * j - 1] * tmp[j];
        for(int j = Rlen + 1; j <= 10; ++j)
            sumL[i][Rlen] += pow10[j + Rlen - 1] * tmp[j];
        sumL[i][Rlen] %= MOD;
        //printf("a[%d]=%lld sumLRlen[%d]=%lld\n", i, a[i], Rlen, sumL[i][Rlen]);
    }
    //puts("");
}

void calcR(int i) {
    ll x = a[i];
    for(int j = 1; j <= 10; ++j) {
        tmp[j] = x % 10;
        x /= 10;
        //printf("%lld", tmp[j]);
    }
    //printf("\n");
    for(int Llen = 1; Llen <= 10; ++Llen) {
        sumR[i][Llen] = 0;
        for(int j = 1; j <= Llen; ++j)
            sumR[i][Llen] += pow10[2 * j - 1 - 1] * tmp[j];
        for(int j = Llen + 1; j <= 10; ++j)
            sumR[i][Llen] += pow10[j + Llen - 1] * tmp[j];
        sumR[i][Llen] %= MOD;
        //printf("a[%d]=%lld sumRLlen[%d]=%lld\n", i, a[i], Llen, sumR[i][Llen]);
    }
    //puts("");
}

void calcLen(int i) {
    int len = 0;
    ll x = a[i];
    while(x) {
        ++len;
        x /= 10;
    }
    //printf("i=%d len=%d\n", i, len);
    ++cntlen[len];
}

void test_case() {
    pow10[0] = 1;
    for(int i = 1; i <= 20; ++i)
        pow10[i] = pow10[i - 1] * 10 % MOD;
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i)
        scanf("%lld", &a[i]);
    for(int i = 1; i <= n; ++i) {
        calcL(i);
        calcR(i);
        calcLen(i);
    }
    ll sum = 0;
    for(int i = 1; i <= n; ++i) {
        for(int Rlen = 1; Rlen <= 10; ++Rlen)
            sum += sumL[i][Rlen] * cntlen[Rlen] % MOD;
        for(int Llen = 1; Llen <= 10; ++Llen)
            sum += sumR[i][Llen] * cntlen[Llen] % MOD;
        sum %= MOD;
    }
    printf("%lld\n", sum);
}

E - OpenStreetMap

题意:给一个 \(n*m(1\leq n,m \leq 3000)\) 的矩阵,然后有一个 \(a*b(1\leq a \leq n, 1\leq b \leq m)\) 框,这个框正好遍历整个矩阵,求框中的最小值的和。

题解:用单调队列或者双栈队列,每列开一个这样的队列。逐行扫描,每次每列的队列Q中都存当前的 \(a\) 行的元素,那么转移的时候就可以直接把这些元素的最小值取出来了。扫描完一行之后命令所有的每列的队列Q都向下移动一个位置。

struct Queue {
    static const int MAXN = 3005;
    static const int INF = 1061109567;
    int s1[MAXN + 5], s2[MAXN + 5];
    int s1top, s2top, s1min;

    void Clear() {
        s1top = 0;
        s2top = 0;
        s2[0] = INF;
        s1min = INF;
    }

    void Push(int x) {
        s1[++s1top] = x;
        s1min = min(s1min, x);
    }

    void Pop() {
        if(s2top)
            --s2top;
        else {
            while(s1top)
                s2[++s2top] = min(s2[s2top - 1], s1[s1top--]);
            --s2top;
            s1min = INF;
        }
    }

    int Size() {
        return s1top + s2top;
    }

    int Min() {
        return min(s2[s2top], s1min);
    }
} Q[3005], RQ;

int g[3005 * 3005];
int h[3005][3005];

void test_case() {
    int n, m, a, b;
    scanf("%d%d%d%d", &n, &m, &a, &b);
    int g0, x, y, z;
    scanf("%d%d%d%d", &g0, &x, &y, &z);
    g[0] = g0;
    for(int i = 1; i <= n * m; ++i)
        g[i] = (1ll * g[i - 1] * x + y) % z;
    int top = 0;
    for(int i = 1; i <= n; ++i) {
        for(int j = 1; j <= m; ++j) {
            h[i][j] = g[top++];
            //printf("%d%c", h[i][j], " \n"[j == m]);
        }
    }
    for(int j = 1; j <= m; ++j) {
        Q[j].Clear();
        for(int i = 1; i <= a; ++i)
            Q[j].Push(h[i][j]);
    }
    ll sum = 0;
    for(int i = a; i <= n; ++i) {
        RQ.Clear();
        for(int j = 1; j <= b; ++j)
            RQ.Push(Q[j].Min());
        for(int j = b; j <= m; ++j) {
            sum += RQ.Min();
            RQ.Pop();
            if(j + 1 <= m)
                RQ.Push(Q[j + 1].Min());
        }
        if(i + 1 <= n) {
            for(int j = 1; j <= m; ++j) {
                Q[j].Pop();
                Q[j].Push(h[i + 1][j]);
            }
        }
    }
    printf("%lld\n", sum);
}
posted @ 2020-02-03 16:45  KisekiPurin2019  阅读(115)  评论(0编辑  收藏  举报