MX模拟赛四总结

又是被 freopen 背刺的一天……

T1

题面:

上来一道诈骗题……

其实我们把题意稍微转化一下就是把 n+1n+1n+12n2n2n 之间所有的数一直除以 222,直到这个数变成奇数为止。

我们可以先列举一些例子:

2~2:1
3~4:1,3
4~6:1,3,5

我们会发现,这个区间内的奇数只可能会出现一次!我们尝试来证明一下这个结论。

假设 n+1≤f(i)i≤2nn+1\le\frac{f(i)}{i}\le2nn+1if(i)2n,那只有当 f(i)i\frac{f(i)}{i}if(i) 乘了 222 才有可能出现重复的情况,我们来算一算:

2n<2n+2≤2f(i)i2n\lt2n+2\le\cfrac{2f(i)}{i}2n<2n+2i2f(i)

因此这个“可能出现的数”一定不可能出现。

所以我们也就证明了在 n+1→2nn+1\to2nn+12n 之间的奇数只会出现一次。

那么最终的总和就是 1+3+5+…1+3+5+\dots1+3+5+,这个的结果是多少呢?其实就是 n2n^2n2。所以直接输入 nnn,然后输出 n2n^2n2 就行了。总之纯诈骗题。

T2

题面:

这个题有很多做法,比如说有暴力 dfs、DP、枚举加贪心,这里我主要讲一下枚举这种做法。

因为 n≤20n\le20n20,所以我们可以直接二进制枚举,其中 111 表示要用,000 表示不用,且要用就必须全用完,这里的用不是说把它拿去取一下模就行,而是必须要让 A 减少。

那我们思考一个问题:这些数选出来了,该按照什么样的顺序去取模呢?假设我们取出来 mmm 个数,且满足 a1≤a2≤⋯≤ama_1\le a_2\le\dots\le a_ma1a2am,因为我们定义了取出来就必须“用”,所以如果我们上来就对 ai(i<m)a_i(i\lt m)ai(i<m) 取模,那 i+1→mi+1\to mi+1m 的这些数就都没有用了,因为 A mod ai<ai≤amA\bmod a_i\lt a_i\le a_mAmodai<aiam。所以最优策略一定是从大到小取模。

所以代码就很简单了:首先对 aaa 数组从大到小排序,然后暴力枚举,总时间复杂度 O(T(nlog⁡n+2nn))O(T(n\log n+2^nn))O(T(nlogn+2nn))

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int t,n,A,ans=LONG_LONG_MAX,a[26];
bool cmp(int x,int y)
{
    return x>y;
}
bool check(int x)
{
    int k=A;
    for(int i=1;i<=n;i++)
    {
        if((1<<i-1)&x)
        {
            k%=a[i];
        }
    }
    return k==0;
}
signed main()
{
    cin>>t;
    while(t--)
    {
        ans=LONG_LONG_MAX;
        cin>>n>>A;
        bool fl=false;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            if(a[i]==1)
            {
                fl=true;
            }
        }
        sort(a+1,a+n+1,cmp);
        if(fl)
        {
            cout<<1<<'\n';
            continue;
        }
        int m=(1<<n)-1;
        for(int i=0;i<=m;i++)
        {
            if(check(i))
            {
                ans=min(ans,(int)(__builtin_popcount(i)));
            }
        }
        cout<<(ans>1e9?-1:ans)<<'\n';
    }
    return 0;
}

T3

题面:

又是一道水题……

题解中给的做法是 DP,但是实际上并不用,这题贪心就可以解决。

因为要求区间和,所以我们上来肯定首先要求前缀和(求区间和的好方法),又因为要被 ppp 整除,所以我们可以考虑把前缀和数组全模一个 ppp,这样当余数相同的时候两数相减就必定是 000,也就是说中间的数的和一定是 ppp 的倍数。

然后这道题就做出来了。

对于所有区间,我们肯定是能取的必定取,所以从前往后贪心的扫一遍,记录在当前节点之前有没有余数相同的节点,如果有,直接计入答案,并把前面所有的数全标记成 -1(防止再次被用),然后就 AC 了。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int n,p,ans,a[1000006],s[1000006],b[1000006];
signed main()
{
    cin>>n>>p;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        s[i]=(s[i-1]+a[i])%p;
    }
    b[0]=1;
    for(int i=1;i<=n;i++)
    {
        if(b[s[i]])
        {
            ans++;
            for(int j=i-1;j>=0;j--)
            {
                if(s[j]==-1)
                {
                    break;
                }
                b[s[j]]=0;
                s[j]=-1;
            }
        }
        b[s[i]]=1;
    }
    cout<<ans;
    return 0;
}

T4

题面:

这个题首先一眼 DP。

因为我们要算的是从一个节点划开,前后满足题目条件子串有多少。其实我们也可以反过来看,即从 iiijjj 出发的两个子串对哪些节点的值有多少贡献。

因此我们可以设 fi,jf_{i,j}fi,j 表示以 i,j(i<j)i,j(i\lt j)i,j(i<j) 两点作为起点往后延伸,能在满足题目条件的情况下能延伸的最大长度是多少。

为了便于转移,我们枚举 i,ji,ji,j 之间的距离 ddd,于是 j=i+dj=i+dj=i+d,并且保证在转移过程中距离不变。

因此我们只有一种转移方向:fi,j→fi+1,j+1f_{i,j}\to f_{i+1,j+1}fi,jfi+1,j+1

假设我们已知 fi,j=Lf_{i,j}=Lfi,j=L,要算 fi+1,j+1f_{i+1,j+1}fi+1,j+1,我们先画个图来看一看:

我们已知那一段长度为 LLL 的子串是符合题目要求的,因此另一段长度为 L−1L-1L1 的子串也肯定是合法的,现在的问题就是中间空出来的那一段该怎么办。

因为我们并不知道以 iii 开头的那一段子串里与后面 jjj 开头的那一段子串里不同的字符有多少,所以这里我们还需要设一个 gi,jg_{i,j}gi,j,定义就是记录不同的字符有多少。

所以,长度为 L−1L-1L1 的那段子串的 ggg 我们就可以轻松求出来:gi+1,j+1=gi,j−[si≠sj]g_{i+1,j+1}=g_{i,j}-[s_i\not=s_j]gi+1,j+1=gi,j[si=sj]。然后我们就可以往后延伸了。注意在延伸过程中也要记录不同的字符对数。

然后我们也可以在 L−1L-1L1 的基础上计算 fi+1,j+1f_{i+1,j+1}fi+1,j+1 的值了。

最后就是统计答案,我们设 ansians_iansi 表示从 iii 后面划开的答案数,于是我们就可以计算出贡献:

具体原因请读者自证。

我们做一次差分,就得到了:

0 0 ... 0 1 1 ... 1 0 0 ... 0 -f[i][j] 0 0 ... 0

再做一次差分,就得到了:

0 0 ... 0 1 0 ... 0 -1 0 ... 0 -f[i][j] f[i][j] 0 0 ... 0

我们会发现,实际上就是在 iii 处加了 111,在 i+Li+Li+L 处减了 111,在 jjj 处减了 fi,jf_{i,j}fi,j,在 j+1j+1j+1 处加了 fi,jf_{i,j}fi,j,因此对于任意的一对 (i,j)(i,j)(i,j),都有这个公式可以计算。最后做两遍前缀和就行了。

代码:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int n,k,f[3006][3006],g[3006][3006],ans[3006];
string s;
signed main()
{
    cin>>n>>k;
    cin>>s;
    s=" "+s;
    for(int d=1;d<n;d++)
    {
        int j=1+d,sum=0,l;
        for(l=0;j+l<=n&&1+l<j;l++)
        {
            sum+=(s[1+l]!=s[j+l]);
            if(sum>k)
            {
                sum--;
                break;
            }
        }
        f[1][j]=l;
        g[1][j]=sum;
    }
    for(int d=1;d<n;d++)
    {
        for(int i=1;i+d<n;i++)
        {
            int j=i+d;
            int L=f[i][j]-1;
            if(L<=0)
            {
                int sum=0;
                for(L=0;j+1+L<=n&&i+1+L<j+1;L++)
                {
                    sum+=(s[i+1+L]!=s[j+1+L]);
                    if(sum>k)
                    {
                        sum--;
                        break;
                    }
                }
                f[i+1][j+1]=L;
                g[i+1][j+1]=sum;
                continue;
            }
            int sum=g[i][j]-(s[i]!=s[j]);
            if(sum<k)
            {
                for(;j+1+L<=n&&i+1+L<j+1;L++)
                {
                    sum+=(s[i+1+L]!=s[j+1+L]);
                    if(sum>k)
                    {
                        sum--;
                        break;
                    }
                }
            }
            f[i+1][j+1]=L;
            g[i+1][j+1]=sum;
        }
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=i+1;j<=n;j++)
        {
            ans[i]+=1,ans[i+f[i][j]]-=1,ans[j]-=f[i][j],ans[j+1]+=f[i][j];
        }
    }
    for(int i=1;i<=n;i++)
    {
        ans[i]+=ans[i-1];
    }
    for(int i=1;i<=n;i++)
    {
        ans[i]+=ans[i-1];
    }
    for(int i=1;i<n;i++)
    {
        cout<<ans[i]<<'\n';
    }
    return 0;
}

T5

题面:

看着是一道数学题,但它其实还是一道 DP!

我们设 fi,jf_{i,j}fi,j 表示前 iii 项中总和模 kkk 等于 jjj 的子序列数。那么我们可以轻易地转移这个 DP:

fi+1,j=fi+1,j+fi,j,fi+1,(j+ai+1) mod k=fi+1,(j+ai) mod k+fi,jf_{i+1,j}=f_{i+1,j}+f_{i,j},f_{i+1,(j+a_{i+1})\bmod k}=f_{i+1,(j+a_i)\bmod k}+f_{i,j}fi+1,j=fi+1,j+fi,j,fi+1,(j+ai+1)modk=fi+1,(j+ai)modk+fi,j

即这个子序列可以选择加入 ai+1a_{i+1}ai+1,也可以选择不加入。

现在就是这个算答案的问题了。

如果前面有一个序列让 ai+1a_{i+1}ai+1 加入,那它能额外带来的贡献是 ⌊j+ai+1k⌋\lfloor\frac{j+a_{i+1}}{k}\rfloorkj+ai+1,那对多少个序列产生了这种额外的贡献呢?首先在后面的肯定有 fi,jf_{i,j}fi,j 个,其次在它前面有 n−i−1n-i-1ni1 个数,这些数对这次贡献不会有什么影响,所以又会有 2n−i−12^{n-i-1}2ni1 个,总的来讲,就有 dpi,j×2n−i−1dp_{i,j}\times2^{n-i-1}dpi,j×2ni1 个。

然后代码就很好写了:

#include<bits/stdc++.h>
#define int long long
#define code using
#define by namespace
#define plh std
code by plh;
int n,k,p,ans,a[5006],pw[5006],dp[5006][5006];
signed main()
{
    cin>>n>>k>>p;
    const int m=static_cast<const int>(p);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    pw[0]=1;
    for(int i=1;i<=n;i++)
    {
        pw[i]=pw[i-1]*2%m;
    }
    dp[0][0]=1;
    for(int i=0;i<n;i++)
    {
        for(int j=0;j<k;j++)
        {
            if(j+a[i+1]>=k)
            {
                ans=(ans+(j+a[i+1])/k*dp[i][j]%m*pw[n-i-1]%m)%m;
            }
            dp[i+1][j]=(dp[i+1][j]+dp[i][j])%m;
            dp[i+1][(j+a[i+1]%k)%k]=(dp[i+1][(j+a[i+1]%k)%k]+dp[i][j])%m;
        }
    }
    cout<<ans;
    return 0;
}

总结

  • T1:0/100,freopen 写错了,最不应该出现的错误还是一不小心出现了。
  • T2:50/100,排序那个地方没太处理好,当时是在函数里排的序,所以时间复杂度稍微有点问题,另外就是在函数里额外多开了一个 vector,常数有点大,被卡 T 了。
  • T3:20/30,没仔细看,多了一个点 WA 了。
  • T4:50/60,同样的,多了一个点 WA。
  • T5;20/20。

总之,本人对这次考试成绩十分不满意,因为中间时间安排也有点问题,导致最终最多只有 290pts,望下次改正。

### 关于第十六届蓝桥杯模拟赛单片机题目解题思路 目前尚未有具体公开的关于第十六届蓝桥杯模拟赛中单片机相关的官方题目或详细资料[^1]。然而,基于以往的比赛经验以及类似的竞赛内容分析,可以推测比赛可能涉及的内容和解题方法。 #### 可能涉及的知识点 以下是常见的单片机开发相关内容及其对应的解题思路: 1. **硬件初始化** - 使用工具链(如STM32CubeMX)配置外设参数并生成初始代码框架。 - 初始化GPIO、UART、I2C等常用接口以便后续功能实现。 2. **定时器应用** - 定时器常用于延时操作或者周期性触发事件处理程序。 - 设置合适的预分频系数及时基重装载值来满足特定时间间隔的需求。 3. **中断机制** - 中断服务函数的设计需简洁高效,避免长时间占用CPU资源。 - 正确设置优先级以确保重要任务能够得到及时响应。 4. **通信协议编程** - 实现串口数据收发控制逻辑,注意波特率匹配及校验方式的选择。 - 对接收到的数据包进行解析,并按照既定规则执行相应动作。 5. **传感器数据采集与处理** - 利用ADC模块读取模拟信号转换成数字量表示形式。 - 应用滤波算法提高测量精度减少噪声干扰影响。 6. **PWM输出调节电机速度/亮度等功能** - 调整占空比改变实际电压水平从而达到调速目的。 - 结合反馈控制系统优化性能表现。 下面给出一段简单的PWM生成代码作为例子: ```c #include "stm32g4xx_hal.h" TIM_HandleTypeDef htim; void MX_TIM_Init(void){ __HAL_RCC_TIM2_CLK_ENABLE(); htim.Instance = TIM2; htim.Init.Prescaler = 83; // SystemCoreClock / (Prescaler + 1) * ARR -> Frequency ~ 1kHz htim.Init.CounterMode = TIM_COUNTERMODE_UP; htim.Init.Period = 999; HAL_TIM_PWM_Init(&htim); } uint32_t duty_cycle = 500; // Duty cycle between 0 and Period int main(){ MX_TIM_Init(); __HAL_TIM_SET_COMPARE(&htim, TIM_CHANNEL_1, duty_cycle); while(1){} } ``` 上述代码展示了如何通过修改比较寄存器中的数值调整PWM波形的不同占空比情况。 #### 总结 对于即将参加此类赛事的学生来说,扎实掌握基础理论知识的同时也要注重实践动手能力培养;多做历年真题熟悉命题风格特点有助于提升应试技巧水平。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值