Codeforces Round #662 (Div. 2) A-D --白话题解

#include <iostream>
#include <stdio.h>
#include<cstring>
#include<queue>
#include<map>
#include<algorithm>
#include<cmath>
#include<unordered_map>
#include<time.h>
#define F(i,a,b) for(int i=a;i<=b;i++)
#define FD(i,b,a) for (int i=b;i>=a;i--)
#define ll long long
#define db double
using namespace std;
const ll pm = 1e9 + 7;
const ll inf = 2147482647;

A

给一个n 要求对于一个nn的矩阵,每步操作可以放置不限个数个11的小方格
但要求每个小方格要与上一步操作放置的小方格至少有一个边共用。
(第一次操作只能在边界上放置(第1/n行及1/n列))
问最少几步可以放满整个矩阵
在这里插入图片描述
观察可以发现,每一步操作都可以使得蓝色区域往中间逼近一格,所以答案就是蓝色部分的长度。
当然n为偶数时检验一下 也是一样的。

int main()
{
    int T;
    cin >> T;
    while (T--) {
        ll a;
        cin >> a;
        cout << a / 2 + 1 << endl;
    }
    return 0;
}

B

先给n个指定长度的木棍,有q次操作,每次添加或去掉一根指定长度的木棍
求每次操作后,当前所有的木棍能否取出8个来,组成一个正方形和一个长方形(含正方形
木棍长度 、n、q小于10^5

虽然在每次判断的时候都有10^5数量级的木棍可取,但实际上我们只要知道是否存在8根一样的、6根一样的、4根一样的、2根一样的木棍,各有几组,这样就有办法O(1)地判断答案。这4个数据用桶排动态维护一下,总复杂度O(n)
具体看代码:

int a[100010] = {};
int main()
{
    int n; 
    int numof8 = 0, numof6 = 0, numof4 = 0, numof2 = 0;
    cin >> n;
    F(i, 1, n) {
        int x;
        scanf("%d", &x);
        a[x]++;
        if (a[x] == 8) { numof8++, numof6--; }
        if (a[x] == 6) { numof6++; numof4--; }
        if (a[x] == 4) { numof4++; numof2--; }
        if (a[x] == 2) numof2++;
    }
    int q;
    cin >> q;
    while (q--) {
        char mode; int x;
        cin >> mode >> x;
        if (mode == '+') {
            a[x]++;
            if (a[x] == 8) { numof8++, numof6--; }
            if (a[x] == 6) { numof6++; numof4--; }
            if (a[x] == 4) { numof4++; numof2--; }
            if (a[x] == 2) numof2++;
        }
        else {
            a[x]--;
            if (a[x] == 7) { numof8--; numof6++; }
            if (a[x] == 5) { numof6--; numof4++; }
            if (a[x] == 3) { numof4--; numof2++; }
            if (a[x] == 1) numof2--;
        }
        if (numof8)cout << "yes" << endl;//有若干组8根一样的
        else if (numof6 >= 2) cout << "yes" << endl;//有两组以上6根一样的
        else if (numof6 && (numof4 || numof2))cout << "yes" << endl;//有一组6根一样的同时有若干组4或2根一样的
        else if (numof4 >= 2)cout << "yes" << endl;//两组以上4根一样
        else if (numof4 && numof2 >= 2)cout << "yes" << endl;//一组4根一样同时两组以上2根一样
        else {
            cout << "no" << endl;
        }
    }
    return 0;
}

C

给n个数字,问有没有某种排列方式,使得任何相同两数字相距最大,问这个最大距离是多少

非常显然答案和相同数最多的那个数字是直接相关
那么对于长度为n的空位置,先放好这个数字,
使之互相间距离最大,然后间隙中插入剩余数字,一定能有某种放法使得这个最大距离为答案。
注意当相同数一样多的数字要先放时,这些数字以相同顺序为一组放置,答案加上相同数-1
这样修正一下最后的答案式子就可以。

int a[100010] = {};
int num[100010] = {};
int main()
{
    int T;
    cin >> T;
    while (T--) {
        memset(a, 0, sizeof(a));
        memset(num, 0, sizeof(num));
        int n;
        cin >> n;
        int maxnum = 0;

        F(i, 1, n) {
            scanf("%d", a + i);
            num[a[i]]++;
            if (a[i] > maxnum)maxnum = a[i];
        }
        int maxOne = 0;
        int numofmaxOne = 0;
        F(i, 1, maxnum) {
            if (num[i] == maxOne)numofmaxOne++;
            if (num[i] > maxOne)maxOne = num[i], numofmaxOne = 1;
        }
        cout << (numofmaxOne - 1) + (n - numofmaxOne * maxOne) / (maxOne - 1) << endl;
    }
    return 0;
}

D

(没写出来,但是学习一番题解蛮有所得,记录一下)
题意是给n*m的字符矩阵,问有多少个如图的菱形,要满足构成菱形的字符为同一个字符

在这里插入图片描述
主要算法是DP,这里记录两种DP思路

法1:

定义dp[i][j]表示以(i,j)为最下方的顶点构成的菱形有多少个,最后答案为sum(dp)
转移方程为

      if (checkCanAdd(i, j)) {
      dp[i][j] = 1 + min(min(dp[i - 1][j - 1], dp[i - 1][j + 1]), dp[i - 2][j]);
      }
       else dp[i][j] = 1;

其中checkCanAdd(i,j)定义为:表示(i,j)位置与它头顶上4个位置字符是否一致

int checkCanAdd(int x, int y) {
    if (mp[x][y] != mp[x - 1][y - 1])return 0;
    if (mp[x][y] != mp[x - 1][y])return 0;
    if (mp[x][y] != mp[x - 1][y + 1])return 0;
    if (mp[x][y] != mp[x - 2][y])return 0;
    return 1;
}

假定菱形的半径为中心到任一顶点的距离(单点构成的菱形半径为1)
这样处理的原理是:
dp[i][j]除了上述意义表示对答案的贡献,另一个重要意义是
以(i,j)为最下顶点的最大菱形的半径。
那这个dp[i][j]数字本身,就保障了有这么大一个字符为mp[i][j]的菱形可以以它为下顶点存在

dp[i - 1][j - 1]、dp[i - 1][j + 1]、dp[i - 2][j]
三个数字中的最小值min,就保障了以他们三为下顶点半径为min的菱形能够进行”合成“
看图一下就懂了,这样就能O(1)地算出dp[i][j]
在这里插入图片描述

char mp[2002][2002] = { };
ll dp[2002][2002] = {};
int checkCanAdd(int x, int y) {
    if (mp[x][y] != mp[x - 1][y - 1])return 0;
    if (mp[x][y] != mp[x - 1][y])return 0;
    if (mp[x][y] != mp[x - 1][y + 1])return 0;
    if (mp[x][y] != mp[x - 2][y])return 0;
    return 1;
}
int main()
{   
    int n, m;
    cin >> n >> m;
    F(i, 1, n) {
        scanf("%s", mp[i]+1);
    }

    F(i, 1, m)dp[1][i] = 1;
    F(i, 2, n) {
        F(j, 1, m) {
            if (checkCanAdd(i, j)) {
                dp[i][j] = 1 + min(min(dp[i - 1][j - 1], dp[i - 1][j + 1]), dp[i - 2][j]);
            }
            else dp[i][j] = 1;
        }
    }
    ll ans = 0;
    F(i, 1, n) {
        F(j, 1, m) {
            ans += dp[i][j];
        }
    }
    cout << ans << endl;
    return 0;
}

法2:

相对来说,法2的方法就没那么有技巧性,当然本质还是动规
(当然对我来说都是刀尖跳舞的杂技型技巧level了呜呜呜)
出发点在于,以菱形的中心点为直角顶点,能够分成左上、右上、左下、右下
四个直角三角形,如果能知道这样的直角三角形最大能有多大,
那么这个菱形的最大状态的半径就是这4个直角三角形中最小的那个的直角边长

所有问题转化为以(i,j)为直角顶点的4种情况的直角三角形直角边最长是多少

这里就是用到DP的地方,或者另一种的理解叫做二维前缀和的变形
在这里插入图片描述
以左上三角形为例
转移方程为

			//如果(i,j)位置的字符和上方、左方的字符一样
            if (mp[i - 1][j] == mp[i][j] && mp[i][j - 1] == mp[i][j]) {
                m1[i][j] = 1 + min(m1[i - 1][j], m1[i][j - 1]);
            }
            else m1[i][j] = 1;

然后依葫芦画瓢,4种直角三角形情况都能写出来


char mp[2002][2002] = {};
int m1[2002][2002] = {};
int m2[2002][2002] = {};
int m3[2002][2002] = {};
int m4[2002][2002] = {};
int main()
{   
    int n, m;
    cin >> n >> m;
    F(i, 1, n) {
        scanf("%s", mp[i] + 1);
    }
    F(i, 1, n) m1[i][1] = 1;
    F(i, 1, m) m1[1][i] = 1;
    F(i, 2, n) {
        F(j, 2, m) {
            if (mp[i - 1][j] == mp[i][j] && mp[i][j - 1] == mp[i][j]) {
                m1[i][j] = 1 + min(m1[i - 1][j], m1[i][j - 1]);
            }
            else m1[i][j] = 1;
        }
    }

    F(i, 1, n) m2[i][m] = 1;
    F(i, 1, m) m2[1][i] = 1;
    F(i, 2, n) {
        FD(j, m - 1, 1) {
            if (mp[i][j] == mp[i - 1][j] && mp[i][j] == mp[i][j + 1]) {
                m2[i][j] = 1 + min(m2[i - 1][j], m2[i][j + 1]);
            }
            else m2[i][j] = 1;
        }
    }

    F(i, 1, n) m3[i][1] = 1;
    F(i, 1, m) m3[n][i] = 1;
    FD(i, n - 1, 1) {
        F(j, 2, m) {
            if (mp[i][j] == mp[i+1][j] && mp[i][j] == mp[i][j-1]) {
                m3[i][j] = 1 + min(m3[i + 1][j], m3[i][j - 1]);
            }
            else m3[i][j] = 1;
        }
    }

    F(i, 1, n) m4[i][m] = 1;
    F(i, 1, m) m4[n][i] = 1;
    FD(i, n - 1, 1) {
        FD(j, m - 1, 1) {
            if (mp[i + 1][j] == mp[i][j] && mp[i][j + 1] == mp[i][j]) {
                m4[i][j] = 1 + min(m4[i + 1][j], m4[i][j + 1]);
            }
            else m4[i][j] = 1;
        }
    }
    ll ans = 0;
    F(i, 1, n) {
        F(j, 1, m) {
            ans += min(min(min(m1[i][j], m2[i][j]), m3[i][j]), m4[i][j]);
        }
    }
    cout << ans << endl;

    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值