1.蘑菇炸弹
纯模拟,O(n)O(n)O(n)遍历统计即可
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=1e5+10;
int arr[maxn];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int ans=0,n;cin>>n;
for(int i=1;i<=n;i++) cin>>arr[i];
for(int i=2;i<=n-1;i++){
if(arr[i]>=arr[i-1]+arr[i+1]) ans++;
}
cout<<ans<<endl;
//system("pause");
return 0;
}
2.构造数字
题意
给定NNN,MMM,求出十进制下各个位的和为MMM的最大NNN位数
分析
看到数据范围N,M≤106N,M\leq 10^6N,M≤106,明显要用字符串,总的是NNN位数,所以可以先初始化为NNN个000。然后要让各个位数的和为MMM且最大,显然贪心,令这个数的高位尽可能大,也就是前几位应该尽可能多地填999,所以O(N)O(N)O(N)地遍历这个答案字符串,最高位填999的同时让MMM减去9,一直到M≤9M\leq 9M≤9时填MMM。
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,m;cin>>n>>m;
string str;
for(int i=1;i<=n;i++) str+='0';
for(int i=0;i<n;i++){
int tem=min(m,9);
str[i]+=tem;
m-=tem;
if(m==0) break;
}
cout<<str<<endl;
//system("pause");
return 0;
}
3.小蓝的金牌梦
题意
给定一个数组a[N]a[N]a[N],求该数组的长度为质数的最大子数组
分析
数据范围2≤n≤1052\leq n\leq 10^52≤n≤105,看到这题,先去搜了下10510^5105范围内的质数数量,发现只有10310^3103级别个,所以直接考虑枚举。
先枚举所有质数,再去枚举子数组的右端点,O(1)O(1)O(1)计算出左端点,考虑这个子数组和,所以可以再去用一个前缀和优化一下,知道左右端点后再O(1)O(1)O(1)的计算出子数组和更新答案。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e5+100;
int cnt=0,vis[maxn],prime[maxn];//cnt为素数的个数,maxn是要找的素数的上界
int arr[maxn],sum[maxn];
void find_prime(){
for(int i=2;i<maxn;i++){
if(vis[i]==0)
vis[i]=1,prime[++cnt]=i;
for(int j=1;j<=cnt&&i*prime[j]<maxn;j++){
vis[i*prime[j]]=1;
if(i%prime[j]==0)break;
}
}
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
find_prime();
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>arr[i],sum[i]=sum[i-1]+arr[i];
int ans=-inf;
for(int i=1;prime[i]<=n;i++){
int x=prime[i];
for(int j=x;j<=n;j++)
ans=max(ans,sum[j]-sum[j-x]);
}
cout<<ans<<endl;
//system("pause");
return 0;
}
4.合并石子加强版
题意
有nnn堆石子围成一个环,第iii堆石子的数量为a[i]a[i]a[i],每次合并两个相邻石子,代价为两堆石子的数量乘积,问合并的最小代价。
分析
看数据范围n≤3∗104n\leq 3*10^4n≤3∗104,一开始感觉可能是个O(n2)O(n^2)O(n2)的算法,但是这种合并又像是区间dpdpdp,需要O(n3)O(n^3)O(n3),所以先想着去贪心一下。
贪心的话无非就两种选择,要么每次合并最小的两堆,要么每次就合并最大的两堆,然后手写一个例子算一下,发现两种合并方法最终的答案一样,于是直接猜结论总花费跟合并次序无关(要证明应该也可以证明,就是直接设一堆字母,拿去合并,发现最后总答案不变,但是算法竞赛,能过就行,不用那么严谨)
于是直接O(N)O(N)O(N)遍历,模拟从111到nnn按顺序合并,并统计总花费。不过值得注意的是,这里乘积太大,会爆long long,所以我用的int128int128int128(不知道的小白可以去了解一下,int128int128int128就像是long long中的long long,可以在大多数情况下替代高精度,缺点就是基本只能加减乘除取余,不能cin,cout,得去抄一份输入输出int128int128int128的板子)
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=3e4+100;
int arr[maxn];
inline void printint(__int128 x){
if (x < 0) putchar('-'),x = -x;
if(x>=10) printint(x/10);
putchar('0'^(x%10));
}
signed main(){
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>arr[i];
__int128_t ans=0,sum=0;
for(int i=1;i<=n;i++){
ans+=sum*arr[i];
sum+=arr[i];
}
printint(ans);
//system("pause");
return 0;
}
5.简单的LIS问题
题意
给一个数组,在可以把一个任意位置的数arr[i]arr[i]arr[i]改为从0到101000到10^{100}0到10100的任何数的前提下,求数组的最长上升子序列,数据范围n≤5∗103n\leq 5*10^3n≤5∗103,可以O(n2)O(n^2)O(n2)
分析
首先容易想到的是,答案要么是原数组的最长上升子序列长度lenlenlen,要么是len+1len+1len+1。因为可以改变一个数的大小,那么肯定会去在原来的最长上升子序列的基础上,争取改变一个数,使得其插入原来的LISLISLIS,那么答案就是len+1len+1len+1。
那么考虑一下什么时候不行,有以下几种情况,一是原来的LISLISLIS的开头已经是000时,因为修改数字的范围是非负数,此时不能在LISLISLIS的开头前面插数。二是当LISLISLIS的最后一个数也是数组的最后一个数时,无法在LISLISLIS后面插一个数。三是LISLISLIS内部,如果是一个贴一个,或者相邻的数字之间只差1,都无法在内部再插入一个数。
于是有了第一种做法,分类讨论
做法一、插入位置分类讨论
这个做法我是从原题的题解区学到的,赛时也有想过,但是感觉不太对劲,没想到能过,但是我也证明不来,这里就介绍一下这种做法吧。
原理是经典的LISLISLIS的单调栈优化做法O(NlogN)O(NlogN)O(NlogN),然后可以在单调栈中sta[len]sta[len]sta[len]储存有潜力的上升子序列,长度跟LISLISLIS的长度是对的,但是具体的值不一定对应。
这种做法就是跑一遍这个单调栈优化,并且记录此时栈内每个值对应数组的下标pos[len]pos[len]pos[len],然后按照分析中的3种插入可能性判断,即
- 能否在LISLISLIS前面插入:判断sta[1]=0sta[1]=0sta[1]=0
- 能否在LISLISLIS后面插入:判断pos[len]=npos[len]=npos[len]=n即最大元素是否为数组的最后一位
- 能否在LISLISLIS中间插入:遍历单调栈,如果有相邻的两个值满足,差大于2(sta[i+1]−sta[i]>2sta[i+1]-sta[i]>2sta[i+1]−sta[i]>2),并且在原数组中的位置不是紧挨着的(pos[i+1]−pos[i]>1pos[i+1]-pos[i]>1pos[i+1]−pos[i]>1),就可以在这两个数之间再插入一个数
代码:
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e3+10;
int arr[maxn],sta[maxn],pos[maxn];
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n;cin>>n;
for(int i=1;i<=n;i++) cin>>arr[i];
int len=1;sta[len]=arr[1];pos[len]=1;
for(int i=2;i<=n;i++){
if(arr[i]>sta[len]){
sta[++len]=arr[i];
pos[len]=i;
}
else{
int tem=lower_bound(sta+1,sta+1+len,arr[i])-sta;
sta[tem]=arr[i];
pos[tem]=i;
}
}
if(len==1){
cout<<min(2,n)<<endl;
return 0;
}
int flag=0;
for(int i=1;i<=len-1;i++){
if(sta[i+1]-sta[i]>1&&pos[i+1]-pos[i]>1){
flag=1;
break;
}
}
if(sta[1]!=0&&pos[1]!=1) flag=1;
if(pos[len]!=n) flag=1;
cout<<len+flag<<endl;
//system("pause");
return 0;
}
PS:这种做法我也不知道为什么替换不会导致原本可能插入中间的位置被替换后反而插入不了,望大佬评论区教一下。
做法二、前后缀DP
这个做法是排行榜里面看别人代码看懂的,也是我感觉最容易理解的一种做法,充分利用了数据范围来简化难度。
开两个数组,pre[i]pre[i]pre[i]表示从1到i的LIS的长度1到i的LIS的长度1到i的LIS的长度,suf[i]suf[i]suf[i]表示从iii到nnn的LISLISLIS的长度。
这两个数组很容易用双重for循环for循环for循环,一个正着,一个逆着,O(n2)O(n^2)O(n2)预处理出来。即pre[i]=max(pre[i],pre[j]+1),∨1≤j<ipre[i]=max(pre[i],pre[j]+1),\vee_{1\leq j<i}pre[i]=max(pre[i],pre[j]+1),∨1≤j<i,同理有suf[i]=max(suf[i],suf[j]+1),∨i<j≤nsuf[i]=max(suf[i],suf[j]+1),\vee_{i<j\leq n}suf[i]=max(suf[i],suf[j]+1),∨i<j≤n
因为对于一个有插入数的LISLISLIS,可以把插入的位置作为分界线,分为左右两侧,左侧的长度就是从111到分界线左侧对应下标的LISLISLIS,右侧长度是从分界线右侧到nnn的LISLISLIS长度,所以会去这样处理前后缀DPDPDP,然后两层forforfor循环枚举分界线的左右两侧。
具体来说,一层forforfor循环iii,即分界线的左侧元素下标,然后第二层forforfor循环jjj从i+2i+2i+2开始,中间至少要有1个空位才能插,然后要是满足能插入,还需要原数组中arr[i]+1<arr[j]arr[i]+1<arr[j]arr[i]+1<arr[j],因为如果分界线左右两侧的数值原本就差1,中间也没法插入中间值。
这样两层forforfor循环跑一遍取最大值即为答案。
代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=5e3+10;
int pre[maxn],suf[maxn],arr[maxn];
signed main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>arr[i];
pre[i]=suf[i]=1;
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++)
if(arr[j]>arr[i])
pre[j]=max(pre[j],pre[i]+1);
}
for(int i=n;i;i--){
for(int j=i-1;j;j--)
if(arr[j]<arr[i])
suf[j]=max(suf[j],suf[i]+1);
}
int ans=1;
for(int i=1;i<=n;i++){
if(i<n) ans=max(ans,pre[i]+1);
else ans=max(ans,pre[i]);
if(i>1&&arr[i]) ans=max(ans,suf[i]+1);
else ans=max(ans,suf[i]);
for(int j=i+2;j<=n;j++)
if(arr[j]-arr[i]>1)
ans=max(ans,pre[i]+suf[j]+1);
}
cout<<ans<<endl;
return 0;
}
6.期望次数
还没看懂题解,不过大方面是期望dp,结合逆元等知识点来做,等我看懂了再更。
本文介绍了五个编程题目,涉及模拟算法、贪心策略、动态规划以及期望DP,展示了在不同问题场景下的解决方案,包括计数、字符串操作、最长上升子序列等。
511

被折叠的 条评论
为什么被折叠?



