NEUQ-ACM2022预备队 2023春 第二周

第二周训练

训练主要内容:动态规划、数据结构

第一题 加一

题目链接

题目1

对于一个数进行 m m m 次操作。每次操作中,将这个数的每一位加上1(9变成10),再组成新数。问操作后的结果。

思路1

如果通过模拟每一次操作,运算量较大会超时。
由于0-9每个数操作m次后得到的长度都可以计算,而且运算次数为 10 × 2 ⋅ 1 0 5 10 \times 2 \cdot 10 ^ 5 10×2105 所以可以先计算0-9所有数字操作 m m m 次后得到的长度再计算每个数字的个数后累加。
计算0-9每个数操作 m m m 次后的长度可以通过动态规划。
状态:0-9中的数字 j j j ,操作 i i i
状态转移方程:
f ( i , j − 1 ) = f ( i − 1 , j ) , j ∈ [ 1 , 9 ] f(i, j - 1) = f(i - 1, j), j \in [1,9] f(i,j1)=f(i1,j),j[1,9]
f ( i , 9 ) = f ( i − 1 , 0 ) + a ( i − 1 , 1 ) f(i, 9) = f(i - 1, 0) + a(i - 1, 1) f(i,9)=f(i1,0)+a(i1,1)
起始条件: f ( 0 , j ) = 1 , j ∈ [ 0 , 9 ] f(0, j) = 1, j \in [0, 9] f(0,j)=1,j[0,9]

代码1

#include<cstdio>
#include<cstring>

constexpr int maxm = 2e5 + 1, mod = 1e9 + 7;
int t;
//存放f,存放0-9每一位的个数
int a[maxm][10], num[10];

int main()
{
    //起始条件
    for (int i = 0; i < 10; i++)
    {
        a[0][i] = 1;
    }
    //状态转移方程
    for (int i = 1; i <= maxm; i++)
    {
        for (int j = 1 ; j < 10; j++)
        {
            a[i][j - 1] = a[i - 1][j];
        }
        a[i][9] = (a[i - 1][0] + a[i - 1][1]) % mod;
    }

    scanf("%d", &t);
    while (t--)
    {
        //清空数据
        memset(num, 0, sizeof(num));
        int n, m;
        scanf("%d %d", &n, &m);
        //计算每一位的个数
        while (n > 0)
        {
            num[n % 10]++;
            n /= 10;
        }
        
        int ans = 0;
        for (int i  = 0; i < 10; i++)
        {
            //不使用a[m][i] * num[i],可能导致溢出
            for (int j = 0; j < num[i]; j++)
            {
                ans += a[m][i];
                ans %= mod;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

第二题 跳跳

题目链接

题目2

给一些整点,成为魔法阵。传送时,开始位于一个魔法阵上,通过魔法 ( A , B ) (A, B) (A,B) 可以从 ( x , y ) (x, y) (x,y) 传送到 ( x + A , y + B ) (x + A, y + B) (x+A,y+B) ,一次传送可以使用同一个魔法多次。问至少需要多少魔法才能从任意魔法阵直接传送到任意魔法阵。

思路2

由于可以使用同一个魔法多次,所以将所有需要的魔法都拆成最小的魔法,可以使需要的魔法最少。
最小的魔法可以通过求最大公约数来获取。求最大公约数可以通过内部函数 __ g c d ( ) gcd() gcd() 。存储和获取魔法是否已经存在可以使用 m a p map map ,魔法作为键,是否存在作为值。

代码2

#include<iostream>
#include<algorithm>
#include<utility>
#include<map>
#include<cmath>
using namespace std;

constexpr int maxn = 501;
//ans为需要的魔法数
int n, ans = 0;
//每个魔法阵的位置
int cx[maxn], cy[maxn];
//存储需要的魔法
map<pair<int, int>, bool> magic;

//获取两点之间传送需要的最小魔法
pair<int, int> getMagic(int p1, int p2)
{
    int x = cx[p2] - cx[p1], y = cy[p2] - cy[p1];
    //由于魔法不能使用负次数,所以两点任意传送需要相反的两个魔法。
    //如果不获取绝对值,则两个魔法就会变成同一个魔法。
    int gcd = abs(__gcd(x, y));
    return make_pair(x / gcd, y / gcd);
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> cx[i] >> cy[i];
    }
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            //在一个魔法阵之间传送
            if (i == j)
            {
                continue;
            }
            //获取最小的魔法
            pair<int, int> m = getMagic(i, j);
            //如果还没有使用过这个魔法,就在map中标记需要这个魔法,并计数
            if (magic.find(m) == magic.end())
            {
                ans++;
                magic.insert(map<pair<int, int>, bool>::value_type(m, true));
            }
        }
    }
    cout << ans << endl;
    return 0;
}

第三题 异或和或

题目链接

题目3

有一个01序列,可以选择任意两个数,对其进行异或和或操作得到两个新数,再把两个新数返回原来的位置。问是否能将一个序列通过这个操作变成另一个序列。

思路3

异或
0 ^ 0 = 00 | 0 = 0
1 ^ 0 = 11 | 0 = 1
1 ^ 1 = 01 | 1 = 1

由表可以看出,0和0不能产生1,1和1可以产生0,但不能产生两个0。
所以全是0的序列不能产生有1的序列,有1的序列不能产生全是0的序列。而其他情况都能产生。
因此只需要判断是否需要将全是0的序列转换为有1的序列或者将有1的序列转换为全是0的序列即可。

代码3

#include<iostream>
using namespace std;

int main()
{
    int t;
    cin >> t;
    while (t--)
    {
        //原序列和新序列
        string s, s2;
        cin >> s >> s2;
        //长度不同肯定不能转换
        if (s.size() != s2.size())
        {
            cout << "NO" << endl;
            continue;
        }
        //长度都为1时,只需要看两个序列是否相同
        if (s.size() == 1)
        {
            cout << (s[0] == s2[0] ? "YES" : "NO") << endl;
        }
        else
        {
            //原序列1的个数和新序列1的个数
            int o1 = 0, o2 = 0;
            //计数
            for (int i = 0; i < s.size(); i++)
            {
                if (s[i] == '1')
                {
                    o1++;
                }
                if (s2[i] == '1')
                {
                    o2++;
                }
            }
            //原序列全是0而新序列有1
            if(o1 == 0 && o2 > 0)
            {
                cout << "NO" << endl;
            }
            //原序列有1而新序列全是0
            else if(o1 > 0 && o2 == 0)
            {
                cout << "NO" << endl;
            }
            //其他情况都可以
            else
            {
                cout << "YES" << endl;
            }
        }
    }
    return 0;
}

第四题 01序列

题目链接

题目4

求一个01序列的出现 k k k 次1的子序列的个数。

思路4

由于 0 ≤ k ≤ 1 0 6 0 \leq k \leq 10^6 0k106 ,如果枚举所有的子序列,为 O ( n 2 ) O(n^2) O(n2) ,会超时。
可以采用前缀和,只需要寻找前缀和相差 k k k 的起点和终点,即可得到一个符合要求的子序列。可以再将前缀和相同的个数存储下来,这样子序列的个数就是前缀和相差 k k k 的个数相乘再求和。只需要求解一次前缀和,时间复杂度为 O ( n ) O(n) O(n) ,符合题目要求。
但是如果 k = 0 k = 0 k=0 ,这个方法就不适用了。

前缀和为从第 1 1 1 项到第 n n n 的和。这里的和为1的个数。

代码4

#include<iostream>
#include<map>
using namespace std;

//前缀和
int sum[1000001];

int main()
{
    int k;
    cin >> k;
    string s;
    cin >> s;
    long long cnt = 0;
    //k = 0的情况
    //对于每一个只有0组成的部分,能生成的字串个数是 n * (n + 1) / 2(n为0的个数)
    //总个数即为所有个数之和
    if (k == 0)
    {
        //记录0的个数
        long long temp = 0;
        for (int i = 0; i < s.size(); i++)
        {
            if (s[i] == '0')
            {
                temp++;
            }
            else
            {
                cnt += temp * (temp + 1) / 2;
                temp = 0;
            }
        }
        cnt += temp * (temp + 1) / 2;
    }
    //用前缀和
    else
    {
        //前缀和
        int temp = 0;
        //记录前缀和相同的个数
        map<int, int> m;
        //循环会漏掉零个元素的前缀和,单独补上
        m[0] = 1;
        for (int i = 0; i < s.size(); i++)
        {
            if (s[i] == '1')
            {
                temp++;
            }
            //记录前缀和,其实前缀和完全不需要记录了,因为并没有用到
            sum[i + 1] = temp;
            //更新前缀和的个数
            m[sum[i + 1]]++;
        }
        //求前缀和相差k的个数的积,并求和
        for (int i = 0; i < s.size(); i++)
        {
            cnt += m[i] * m[i + k];
        }
    }
    cout << cnt << endl;
    return 0;
}

第五题 出栈序列判断

题目链接

题目5

入栈序列为 1 , 2 , ⋯   , n 1, 2, \cdots, n 1,2,,n ,给一个出栈序列。问如何进行入栈和出栈操作,可以产生给出的出栈序列。

思路5

由于入栈序列已经给出,递增。所以只需要一直入栈直到顶部的数与出栈序列当前的数相同,进行出栈即可。

代码5

#include<cstdio>

constexpr int maxn = 100005;
//栈中元素个数,模拟栈用的数组
int cnt = 0, s[maxn];

//入栈
void push(int number)
{
    s[cnt++] = number;
}

//出栈
void pop()
{
    cnt--;
}

//获取顶部元素
int top()
{
    return s[cnt - 1];
}

//判断栈是否为空
bool empty()
{
    return cnt == 0;
}

int main()
{
    //now为当前要入栈的元素
    int n, t, now = 1;
    scanf("%d", &n);
    //每次循环出栈一次
    for (int i = 1; i <= n; i++)
    {
        //获取出栈序列当前元素
        scanf("%d", &t);
        //顶部元素不为出栈序列当前元素时就入栈
        while (empty() || top() < t)
        {
            push(now);
            printf("push %d\n", now);
            now++;
        }
        //相同就出栈
        if (top() == t)
        {
            pop();
            printf("pop\n");
        }
    }
    return 0;
}

第六题 序列维护

题目链接

题目6

有一个序列,需要支持插入、删除和查询操作。

思路6

直接用vector模拟即可。

代码6

#include<iostream>
#include<vector>
using namespace std;

int main()
{
    int m;
    //序列
    vector<int> v;
    cin >> m;
    while (m--)
    {
        string op;
        cin >> op;
        //插入
        if (op == "insert")
        {
            int x, y;
            cin >> x >> y;
            v.insert(v.begin() + x, y);
        }
        //查找
        else if (op == "query")
        {
            int k;
            cin >> k;
            cout << v[k - 1] << endl;
        }
        //删除
        else
        {
            int x;
            cin >> x;
            v.erase(v.begin() + x - 1);
        }
    }
    return 0;
}

第七题 网格判断

题目链接

题目7

有一个 N × N N \times N N×N 的网格,每个正方形为黑色或白色。判断网格是否满足:

  1. 每行的黑色方块数与白色方块数相同。
  2. 每列的黑色正方形数与白色方块数相同。
  3. 没有行或列具有 3 3 3 个及以上相同颜色的连续正方形。

思路7

模拟,逐条判断每个条件即可。

代码7

#include<iostream>
using namespace std;

constexpr int maxn = 26;
int n;
//存放网格
char grid[maxn][maxn];

int main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        for (int j = 1; j <= n; j++)
        {
            cin >> grid[i][j];
        }
    }

    for (int i = 1; i <= n; i++)
    {
        //这一列白色的个数、这一列黑色的个数、这一行白色的个数、这一行黑色的个数
        int cwcnt = 0, cbcnt = 0, rwcnt = 0, rbcnt = 0;
        for (int j = 1; j <=n; j++)
        {
            //判断行
            if (grid[i][j] == 'B')
            {
                rbcnt++;
            }
            else
            {
                rwcnt++;
            }
            //判断列
            if (grid[j][i] == 'B')
            {
                cbcnt++;
            }
            else
            {
                cwcnt++;
            }
        }
        //行或列有黑白色不相等
        if (cwcnt != cbcnt || rwcnt != rbcnt)
        {
            cout << 0 << endl;
            return 0;
        }
    }

    for (int i = 1; i <= n; i++)
    {
        //判断有没有三个连续相同
        for (int j = 3; j <= n; j++)
        {
            //行
            if (grid[i][j - 2] == grid[i][j] && grid[i][j - 1] == grid[i][j])
            {
                cout << 0 << endl;
                return 0;
            }
            //列
            if (grid[j - 2][i] == grid[j][i] && grid[j - 1][i] == grid[j][i])
            {
                cout << 0 << endl;
                return 0;
            }
        }
    }
    cout << 1 << endl;

    return 0;
}

第八题 整齐的数组

题目链接

题目8

有一组数和一个整数 k k k 。对数组操作:选择数组的一个数减去 k k k 。进行若干次操作后,所有数都变成同一个数。问最大的 k k k

思路8

如果能变成同一个数,那肯定可以变成原来的最小的数。找到所有数和最小数的差的最大公约数,这个数即为 k k k

代码8

#include<iostream>
#include<algorithm>
using namespace std;

constexpr int maxn = 40 + 2;
int t, n;
//存储数
int a[maxn];

int main()
{
    cin >> t;
    while (t--)
    {
        cin >> n;
        //m存放最小数,ans是k
        int m = 0x3f3f3f3f, ans = -1;
        //找最小数
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i];
            m = min(a[i], m);
        }
        //求最大公约数
        for (int i = 1; i <= n; i++)
        {
            //求这个数与最小数的差
            int sub = a[i] - m;
            //这个数不是最小数
            if (sub > 0)
            {
                //这是第一个数时,最大公约数就是自己
                if (ans == -1)
                {
                    ans = sub;
                }
                else
                {
                    ans = __gcd(ans, sub);
                }
            }
        }
        cout << ans << endl;
    }
    return 0;
}

第九题 删删

题目链接

题目9

给定一个字符串,你可以删除多个(可以是 0 0 0 ) 相同 的字符,这样操作之后,你能否得到一个回文串?如果能,求最小化删除的个数。

思路9

循环判断每一个字符是否为需要删除的数组。然后分别从字符串开头和字符串结尾向中间比对字符。如果有不相同的字符,说明这个字符需要删除才可能成为回文串。如果删去的字符都是判断的字符,就说明删除这个字符可以使字符串变成回文串。再求最小值即可。

代码9

#include<iostream>
using namespace std;

//无穷大
constexpr int inf = 0x3f3f3f3f;
int t;

int main()
{
    cin >> t;
    while (t--)
    {
        int n, ans = inf;
        string s;
        cin >> n >> s;
        //循环每一个字符
        for (char c = 'a'; c <= 'z'; c++)
        {
            //开头和结尾的下标,需要删除的字符的个数
            int l = 0, r = n - 1, cnt = 0;
            //逐个判断
            while (l < r)
            {
                //出现不同字符
                if (s[l] != s[r])
                {
                    //删去是当前循环的字符
                    if (s[l] == c)
                    {
                        l++;
                        cnt++;
                        continue;
                    }
                    else if (s[r] == c)
                    {
                        r--;
                        cnt++;
                        continue;
                    }
                    //都不是那么这个字符不行
                    else
                    {
                        cnt = inf;
                        break;
                    }
                }
                //向中间移动
                l++;
                r--;
            }
            //更新答案
            ans = min(ans, cnt);
        }
        cout << (ans == inf ? -1 : ans) << endl; 
    }
    return 0;
}

第十题 快快变大

题目链接

题目10

给定一个长度为 n n n 的数组,进行 n − 1 n − 1 n1 次操作,每次选择一个下标 x x x ,将 a x a_x ax a x + 1 a_{x + 1} ax+1 合并成 a x × a x + 1   m o d   1000003 a_x \times a_{x + 1} \space mod \space 1000003 ax×ax+1 mod 1000003 ,并获得 ( a x − a x + 1 ) 2 (a_x - a_{x + 1}) ^ 2 (axax+1)2 分。当数组只剩一个数时停止。求最大分数。

思路10

这是一道动态规划。
状态:从 i i i 合并到 j j j 得到的最大分数
状态转移方程: f ( i , j ) = m a x ( f ( i , k ) + f ( k + 1 , j ) + ( p ( i , k ) − p ( k + 1 , j ) ) 2 ) f(i, j) = max(f(i, k) + f(k + 1, j) + (p(i, k) - p(k + 1, j)) ^ 2) f(i,j)=max(f(i,k)+f(k+1,j)+(p(i,k)p(k+1,j))2)    p ( i , j ) \space \space p(i, j)   p(i,j) i i i j j j 的乘积, k k k 是合并的下标
起始条件: f ( i , i ) = 0 f(i, i) = 0 f(i,i)=0

代码10

#include<iostream>
//会溢出,要用long long
#define int long long
using namespace std;

constexpr int maxn = 305, mod = 1000003;
int n;
//数组,p,dp的结果f
int a[maxn], p[maxn][maxn], ans[maxn][maxn];

signed main()
{
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        //求p的起始条件
        p[i][i] = 1;
        p[i][i - 1] = 1;
    }
    //求p的结果
    for (int i = 1; i <= n; i++)
    {
        for (int j = i; j <= n; j++)
        {
            p[i][j] = (p[i][j - 1] * a[j]) % mod;
        }
    }
    //枚举区间长度
    //要先求小区间的结果才能得到大区间的结果
    for (int l = 2; l <= n; l++)
    {
        for (int i = 1; i <= n - l + 1; i++)
        {
            //求j的值
            int j = i + l - 1;
            //枚举k,从i到j - 1
            for (int k = i; k < j; k++)
            {
                //状态转移方程
                ans[i][j] = max(ans[i][j], ans[i][k] + ans[k + 1][j] + (p[i][k] - p[k + 1][j]) * (p[i][k] - p[k + 1][j]));
            }
        }
    }
    cout << ans[1][n] << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值