2022牛客寒假算法基础集训营2

题目链接
更新ing

A 小沙的炉石

题目大意:
n张法术进攻牌,每张消耗一点法力,造成一点基础伤害。
m张法术恢复牌,不消耗法力,每次可以恢复一点法力。一开始有一点法力,法力无上限。
法术伤害初始为0,实施法术后,法术伤害+1.
法术进攻牌的伤害 = 法术伤害+基础伤害。
问能否将敌人的血量恰好变成0?

二分/结论

分析

  1. 固定攻击次数,那么攻击范围是一个区间。
    假设攻击a次,恢复b次,则a <= b + 1。
    最小攻击量为:攻击、恢复、攻击、恢复…直到用完所有攻击。则攻击总量 = (1 + 3 + 5 + …+ (2a-1) = a^2;
    最大攻击量为:恢复、恢复、恢复…攻击、攻击… 。则攻击总量 = (b + 1) + (b+2)+(b+3)…+(b + a) = ab + (a + 1) * a / 2. 此时b = m

二分写法

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void)
{
    LL n, m, k;
    scanf("%lld%lld%lld", &n, &m, &k);
    n = std::min(n, m + 1);
    
    while(k --)
    {
        LL x;
        scanf("%lld", &x);
        LL l = 1, r = n;
        bool flag = false;
        //区间左端点 <= x && 区间右端点 >= x
        while(l <= r)
        {
            LL mid = l + r >> 1;
            LL minn = mid * mid;
            LL maxn = (m * mid) + (mid + 1) * mid / 2;
            if(x >= minn && x <= maxn)
            {
                flag = true;
                break;
            }
            else if(x < minn) r = mid - 1;
            else l = mid + 1;
        }
        if(flag) puts("YES");
        else puts("NO");
    }
    
}

结论写法
解释minn: 在满足区间左端点 <= x的情况下,让攻击次数尽可能大,这是检查右区间即可

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
int main(void)
{
    LL n, m, k;
    scanf("%lld%lld%lld", &n, &m, &k);
    n = std::min(n, m + 1);
    
    while(k --)
    {
        LL x;
        scanf("%lld", &x);
        LL minn = std::sqrt(x);
        //找到当即使所有进攻牌数都用时的最小值且这个值的最小大于等于x
        if(minn > n) minn = n;
        if(minn * m + (minn + 1) * minn / 2 >= x) puts("YES");
        else puts("NO");
    }
    
}

C 小沙的杀球

签到题,能杀就杀,不能杀不杀就完事了。

D 小沙的涂色

题目大意:
在这里插入图片描述
在这里插入图片描述

模拟/构造

分析

  1. 如果变长是3的倍数我们无法构造出来。
    总格子数n ^2 % 3 == 0, 不能做到仅剩一个格子。
  2. 染色方法很多种,下图是下面代码的栗子
    在这里插入图片描述
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
int n, idx;
int f[N][N];

void draw3(int x, int y) //1 1 2                           
{                        //1 2 2
    for(int j = y; j <= n; j += 3)
    {
        f[x][j] = f[x][j + 1] = f[x + 1][j] = ++ idx;
        f[x][j + 2] = f[x+1][j+1] = f[x+1][j+2] = ++ idx;
    }
}
void draw2(int x)                //1
{                                //1 1 2
    for(int i = x; i <= n; i ++)//   2 2
    {
        if(i & 1) f[i - 1][1] = f[i][1] = f[i][2] = ++ idx;
        else f[i-1][3] = f[i][2] = f[i][3] = ++ idx;
    }
}

void draw1(int x, int y)            //1 1 
{                                   //1 2
    for(int i = x; i <= n; i += 3)  //2 2
    {
        for(int j = y; j <= n; j += 2)
        {
            f[i][j] = f[i][j+1] = f[i+1][j] = ++ idx;
            f[i+1][j+1] = f[i + 2][j] = f[i+2][j+1] = ++ idx;
        }
    }
}

int main(void)
{
    scanf("%d", &n);
    if(n % 3 == 0)
    {
        puts("NO");
        return 0;
    }
    if(n == 1)
    {
        puts("YES");
        puts("0");
        return 0;
    }
    if(n % 3 == 1)
    { 
        //满足此条件n最小为4, 先把前四行填满,左下角空着
        f[1][1] = f[1][2] = f[2][1] = ++idx;
        f[4][3] = f[4][4] = f[3][4] = ++idx;
        f[3][1] = f[3][2] = f[4][2] = ++ idx;
        f[1][3] = f[1][4] = f[2][4] = ++idx;
        f[2][2] = f[2][3] = f[3][3] = ++ idx;
        draw3(1, 5);
        draw3(3, 5);
        if(n & 1)
        {//如果n是奇数, draw2可以把前三列剩下的填满,并且有一个空格
            draw2(5);
            //剩下列数一定是偶数个,且行数是3的倍数,进行draw1操作
            draw1(5, 4);
        }
        else draw1(5, 1); //如果n是偶数, 此时列数是偶数,且行数是3的倍数
    }
    else //n % 3 == 2
    {
        f[1][1] = f[1][2] = f[2][2] = ++ idx;
        //把前两行填满
        draw3(1, 3);
        if(n & 1) //奇数
        {
            //把前三列填满且有一个空,
            draw2(3);
            // 此时未填的行数是3的倍数,未填的列数是偶数
            draw1(3, 4);
        }
        else draw1(3, 1);//列数是偶数,且未填的行数为3的倍数
    }
    puts("YES");
    for(int i = 1; i <= n; i ++)
    {
        for(int j = 1; j <= n; j ++)
        {
            printf("%d ", f[i][j]);
        }
        puts("");
    }
    return 0;
}

E 小沙的长路

题目大意:
小沙有一个n个点的完全图(完全图是一个简单的无向图,其中每对不同的顶点之间都恰连有一条边相连),你可以给每条边选择方向,规定每条边只能走一次,请问n个点的完全图的最长路径,现在现在小沙想要知道它的最小值和最大值分别是多少?
图论

分析

  1. 对于最长路的最小值,我们考虑,如果图上有环,那么我们肯定能尽可能多的走环,这样的话我一定会比我不走环更长,所以我们构造的图要尽可能的没环。在没环的情况下,我们只会经过每个点各一次,所以总长度是n-1
  2. 最长路的最大值:
    我们考虑尽可能的将每一条路都走遍,我们可以理解为对一个完全图进行删边,我们需要删尽可能少的边,从而使他能够从头走到尾,也就是构造出一个欧拉回路。
    欧拉回路:一个图中最多应该只有度为2的奇数点(一笔画问题)。`完全图最多有n * (n - 1)/2条边。对于偶数个点的图,每个点的入度均为奇数,最多有两个入度为奇数的点,所以删去的边数最少为(n - 2)/2条。
#include <bits/stdc++.h>
using namespace std;
int main(void)
{
    long long n;
    cin >> n;
    cout << n - 1 << " ";
    long long t = (n % 2 == 0);
    cout << n * (n-1) / 2 - t*(n - 2) / 2 << endl;
    return 0;
}

F 小沙的算数

题目大意:

算数题中,他们中间的符号都是+或者× ,并且每个题+和×的位置都是一样的。 由于答案数字过大 所以我们对1000000007取模

输入描述:
第一行 给定二个个数n代表有n个数进行计算 q代表有q次询问
第二行 给定一个一个长度为n-1的*+字符串 表示我们要进行的计算符号是什么
第三行 给定n个整数,代表这每个位置上的数字
随后q行每行两个数字x,y
代表着将第x个数字改成y,且x≤n

思维、并查集、逆元

分析

  1. 乘法优先级大于加法,每个+会分开一段区间,每个区间的值互不干扰,所以可以提前将每个区间的信息整合道一个值中,同时id数组记录下标对应的哪个区间(可以用并查集,代码中用id记录了)。在进行修改时,先将未修改的答案ans - 该区间的值, 该区间的值/=修改前的值(需要用到逆元), 该区间的值*=修改后的值, ans + 该区间的值
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7, N = 1e6 + 10;
int a[N], id[N];
LL f[N];

LL qmi(LL a, LL b)
{
    LL res = 1;
    while(b)
    {
        if(b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int main(void)
{
    int n, q;
    scanf("%d%d", &n, &q);
    string s;
    cin >> s;
    s = " " + s;
    for(int i = 1; i <= n; i ++) scanf("%d", &a[i]), f[i] = 1;
    int temp = 1;
    for(int i = 1; i <= n; i ++)
    {
        f[temp] = f[temp] * a[i] % mod;
        id[i] = temp;
        if(s[i] == '+') temp ++;
    }
    
    LL res = 0;
    for(int i = 1; i <= temp; i ++)
    {
        res = (res + f[i]) % mod;
    }
    
    while(q --)
    {
        int i, j;
        scanf("%d%d", &i, &j);
        res = (res - f[id[i]] + mod) % mod;
        f[id[i]] = f[id[i]] * qmi(a[i], mod - 2) % mod;
        f[id[i]] = f[id[i]] * j % mod;
        a[i] = j;
        res = (res + f[id[i]]) % mod;
        printf("%lld\n", res);
    }
}

H 小沙的数数

题目大意:
在这里插入图片描述

二进制运算、思维

分析

  1. a + b = 2 * (a & b) + (a ^ b) 在a + b一定的情况下,要想a ^ b最大,则a & b应该为0。
    所以,a + b的二进制中的为1的位在数组中也应只有一个数该二进制位为1,1个1对应n中选择。
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 1e9 + 7;
LL n, m;

int main(void)
{
    cin >> n >> m;
    //m为1的a数组只能有一位为1,
    n %= mod;
    LL ans = 1;
    LL temp = m;
    for(int i = 0; i < 63; i ++){
        if((temp >> i) & 1){
            ans *= n;
            ans %= mod;
        }
    }
    cout << ans % mod << endl;
    return 0;
}

I 小沙的构造

题目大意:

这题小沙想让你构造出一串字符串,这个字符串有以下几个特点。
1,他是对称串,他关于这个字符串的垂直对称。例如"()",我们将他翻转过来他也是“()”,例如“p“翻转过来就是“q“。
2, 这个串串的所有字符都是除小写字母以外的可见字符(不包括空格)。
现在小沙想让你构造一个长度为nn的不同字符数量为mm的一个字符串。
可见字符如下:
!"#$%&’()+,-./0123456789:;<=>?@[]^_`QWERTYUIOPASDFGHJKLZXCVBNM{}|~
其中具有对称性质的有
"!'
±.08:=^_WTYUIOAHXVM|<>/[]{}()

构造、思维

分析

  1. 具有对称性质的字符是可以放到奇数长度串的中间位置的
    需要配对使用的字符用时需要配对比如[], {}(代码中的ab数组的前五个)。
  2. 当n为奇数时,需要在中间放一个自对称的字符,因为" 在ab数组的末尾(我们在for循环遍历时,并未检查是否用过下标为k的字符)所以我们填位于最后一个", 使得尽可能满足m的条件
  3. m也有可能出现奇数的情况, 对应的便是mp.size() + 1 == m, 此时需要填一个自对称的字符,下标>=5
#include <bits/stdc++.h>
using namespace std;
const int N = 1e4 + 10;
char res[N];
set<char> mp;
char a[] = "({[\\<!'*+-.08:=^_WTYUIOAHXVM|\"";
char b[] = ")}]/>!'*+-.08:=^_WTYUIOAHXVM|\"";
int n, m;

int main(void)
{
    cin >> n >> m;
    if(m == 1) while(n --) putchar('*');
    else
    {
        if(n & 1)
        {
            res[1 + n >> 1] = ('"');
            mp.insert('"'); 
        }
        for(int i = 1, k = 0; i <= n / 2; i ++)
        {
            if(mp.size() + 1 == m) //m为奇数的情况
                k = max(k, 5);
            res[i] = a[k], res[n - i + 1] = b[k];
            mp.insert(a[k]);
            mp.insert(b[k]);
            if(mp.size() < m) k ++;
            k %= 30;
        }
        puts(mp.size() != m ? "-1" : res + 1);
    }
}

K 小沙的步伐

签到题,略。

L 小沙的remake(普通版)

题目大意:
题目链接
有一个顺序数组,需要按顺序选择,且当前选择的不能比已经选择的小,选择的条件:(1)位于第一个或者(2)前bi个以内至少存在一个已被选择。选择的序列,只要有一个不同,就是不同的选法。问能选择的序列有多少种

两点:

找到连续的,上升子序列且下标之差满足某个范围
求方案数

树状数组、DP

思路

  1. 我们需要存储下标并且排序(先按照权值后下标)
  2. 排完序后,寻找不大于当前数,并且下标也在它之前的方案数量,存储到当前数字的下标数组里。
  3. 树状数组:快速求前缀和, 可以修改某个数。
  4. 树状数组中(原数组的下标)下标对应该段之前的方案数;且可以快速求前缀和
#include<bits/stdc++.h>
namespace GenHelper
{
    int z1,z2,z3,z4,z5,u,res;
    int get()
    {
        z5=((z1<<6)^z1)>>13;
        z1=((int)(z1&4294967)<<18)^z5;
        z5=((z2<<2)^z2)>>27;
        z2=((z2&4294968)<<2)^z5;
        z5=((z3<<13)^z3)>>21;
        z3=((z3&4294967)<<7)^z5;
        z5=((z4<<3)^z4)>>12;
        z4=((z4&4294967)<<13)^z5;
        return (z1^z2^z3^z4);
    }
    int read(int m) {
        u=get();
        u>>=1;
        if(m==0)res=u;
        else res=(u/2345+1000054321)%m;
        return res;
    }
     void srand(int x)
    {
        z1=x;
        z2=(~x)^(0x23333333);
        z3=x^(0x12345798);
        z4=(~x)+51;
      	u = 0;
    }
}
using namespace GenHelper;
using namespace std;
const int N=2e6+7,mod=1e9+7;
typedef long long LL;
int a[N],b[N];
pair<int, int>x[N];
LL tre[N]; //树状数组
int n;
int lowbit(int x)
{
    return x & (-x);
}
void add(int x, int k)
{
    while(k <= n)
    {
        tre[k] = (tre[k] + x) % mod;
        k += lowbit(k);
    }
}

LL getsum(int k)
{
    LL sum = 0;
    while(k)
    {
        sum = (tre[k] + sum) % mod;
        k -= lowbit(k);
    }
    return sum;
}

int main(){
    int seed;
    scanf("%d %d",&n,&seed);
	srand(seed);
	for(int i=1;i<=n;i++){
		a[i]=read(0),b[i]=read(i);
        x[i].first = a[i];
        x[i].second = i;
	}
    sort(x + 1, x + n + 1);
    LL sum = 0;
    for(int i = 1; i <= n; i ++)
    {
        LL temp = (getsum(x[i].second) - getsum(x[i].second - b[x[i].second] - 1) + 1 + mod) % mod;
        add(temp, x[i].second);
    }
    cout << getsum(n);
    return 0;
}

竞速版的该题也可以用该方法及代码过,此处略
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xuhx&

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值