算法练习-02

今天给大家带来的是第二天的几道练习题,包括几道思路特别巧妙的算法题,以及提升的背包问题,相信这类问题对大家算法能力的提升还是十分有帮助的,希望大家学完可以给博主点一个关注。

第一题:

问题描述

给定一个长度为 n 的数组 a,小蓝希望从数组中选择若干个元素(可以不连续),并将它们重新排列,使得这些元素能够形成一个先严格递增然后严格递减的子序列(可以没有递增部分或递减部分)。你需要求出在满足这个条件下,最多可以选择多少个元素。

输入格式

第一行包含一个正整数 n,表示数组的长度。(1≤n≤105)

第二行包含 nn 个整数 a1,a2,...,an−1,an(1≤ai​≤105)

输出格式

一行包含一个整数,表示最多能选多少个元素。

#include 
using namespace std;
const int N = 1e5 + 10;
int a[N];
int ans,mx;
int main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
  int x;cin>>x;
   a[x]++;
}
for(int i=1;i<=1e5;i++){
 ans+=min(2,a[i]);
 if(a[i]>=1)mx=max(mx,i);
}
cout<=2?1:0);

    return 0;
}    

这道题在思路上还是特别的创新的,对于先增大后减小,我一开始想到的是用最长上升子序列的模版,但是这道题的范围很大,用最长上升子序列的模版很容易导致超时,所以必须转换思路,记录每种数字出现的次数来解决,特别注意最大的次数。然后统计时巧用1e5的范围而不是直接用map这可以防止重复统计,这个细节可以学习。

第二题

问题描述

给定两个正整数 n,m,请你求出 Cnm​,由于这个数可能很大,你只需要输出这个数末尾有多少个零。

输入格式

第一行输入一个正整数 T,表示测试数据的组数。

接下来 T行,每行两个整数 n,m,含义如问题描述所示。

输出格式

输出共 T 行,每组测试数据仅一行,输出一个整数表示要求组合数的末尾零的个数。

#include
using namespace std;
using ll=long long;

int solute(ll n,ll m)
{
    ll c2=0,c5=0;
    for(int i=n;i>=n-m+1;i--)
    {
        ll x=i;
        while(x%2==0) { c2++;x/=2; }
        while(x%5==0) { c5++;x/=5; }
    }

    for(int i=m;i>0;i--)
    {
        ll x=i;
        while(x%2==0) { c2--;x/=2; }
        while(x%5==0) { c5--;x/=5; }
    }
    return min(c2,c5);
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        ll n, m;
        cin >> n >> m;
        cout << solute(n, m) << endl;
    }
    return 0;
}

这道题也十分巧妙,首先是要理解组合排序的概念,学会用循环来表示分子和分母的排序数,其次就是统计0的个数,就是看分子约去分母后最后剩余多少个2✖️5,当然最后取的是min(C2,C5)。

第三题:

问题描述

大衣有一个长度为 ​2⋅N​ 的数组 ​A​。

大衣还有一个空数组 B,他需要进行以下操作恰好 N 次:

  • 从数组 A中选择两个不同的索引 x,y(1≤x,y≤∣A∣)。
  • 将元素 (Ax​−Ay​) 加入到数组 B的末尾。
  • 从数组 A 中将第 x 个元素和第 y 个元素删除,但不改变剩余元素的顺序。

请问是否存在一种操作,使得最终的数组 B​满足 Bi≠Bi+1(1≤i

输入格式

第一行输入一个正整数 T 表示测试数据的组数。

接下来 T组测试数据每组输入两行:

  • 第一行输入一个正整数 ​N​ 如题所述。

  • 第二行输入2⋅N 个整数 A1,A2,⋯,A2⋅N​ 表示数组 A 的元素。

输出格式

对于每组测试数据,如果存在一种操作,使得最终的数组 B 满足 Bi≠Bi+1(1≤i

#include 
using namespace std;
const int N = 1e3+3;
int a[2*N];

void solve()
{
  int n;cin >> n;
  memset(a,0,sizeof(a));
  int cnt=0;
  for(int i=1;i<=n*2;i++)
  {
    int j;cin >> j;
    a[j]++;
    cnt=max(cnt,a[j]);
  } 
  //b数组里面最多有(n+1)/2个数相等
  //这意味着只要a数组里面只要出现次数最多的数别超过n+1个就行
  if(cnt<=n+1) cout << "YES" << "\n";
  else cout << "NO" << "\n";
}

int main()
{
  // 请在此输入您的代码
  int t;cin >> t;
  while(t--)
  {
    solve();
  }
  return 0;
}

这道题关键是要理解,对于2n的a数组来说,最多只允许存在n+1个相同的数,如果存在n+2个相同的数,则最理想的状态下还会存在n-2个其他相同的数,一一匹配过后就会剩下4个相同的存量,这样就不可避免的会产生2个相邻的数,即不满足题意。

第五题

问题描述

给定一个长度为 n 的字符串 S,你必须从中选择一个连续子串,将其删除。删除后剩余的子串长度不能小于 2,且剩余子串的字符不能全部相同。 问合法删除方式的方案数。

输入格式

第一行输入一个正整数 T,表示测试样例组数。

对于每组数据:

输入一行,包含一个字符串 S。

数据保证 Si为小写英文字母。

输出格式

输出一个正整数,为合法删除的总方案数。

#include 
using namespace std;
long long cnt;
void solve(){
  string s;
  cin>>s;
  unordered_maphash;
   cnt=0;
  int n=s.length();
  int l=0;
  for(int i=0;i=2){
    cnt+=r-l+1;
  }
  }
cout<>t;
  while(t--){
    solve();
    
  }
  return 0;
}

这道题是经典的移动窗口的问题,可以当作模版题来记忆,这道题的难度还是比较大的。

第六题

问题描述

初始化有一个数 x=x0,依次执行 N 次操作,每次操作形如:

  1. 给予 c,x:=x&c
  2. 给予 c,x:=x∣c
  3. 给予 c,x:=x⊕c。

现在有 Q次询问,每次询问给定x=xi​,询问初始的x=xi​,经过 N 次操作后 x 的具体值。

输入格式

第一行包含 2 个正整数 N,Q。

之后 N 行,第 i 行给定一个操作类型 typei​,表示运算符,以及一个正整数 fi​。

之后 Q行,第 i行给定一个正整数 xi​,表示依次询问。

输出格式

输出共 Q行,每行包含 1 个整数,表示最终答案

#include 
using namespace std;
const int N=1e5+10;
int dp[N][2];
int n,q;
int main()
{
  
  cin>>n>>q;
  dp[0][1]=1;
  for(int i=1;i<=n;i++){
   char c;cin>>c;int v;cin>>v;
   if(c=='&'){
     dp[i][1]=dp[i-1][1]&v;
     dp[i][0]=dp[i-1][0]&v;
   }
   if(c=='|'){
     dp[i][1]=dp[i-1][1]|v;
     dp[i][0]=dp[i-1][0]|v;
   }
   if(c=='^'){
     dp[i][1]=dp[i-1][1]^v;
     dp[i][0]=dp[i-1][0]^v;
   }
  }
  while(q--){
    int x;cin>>x;
    if(x==0)cout<

这道题我一开始尝试只用一维的dp数组来实现,但是后面我发现这样处理是不好实现的,因为如果用一维dp来实现,dp[0]初值不好设置,对于0来说初值是0,但对于其他数来说初值则是1,所以遇到这种特殊情况,使用的思路是通过设置二维dp的方式来实现遍历。

第七题:

问题描述

爱丽有一个字符串 S,只包含 0 和 1 ,爱丽定义了完美 d 串,指的是长度为 d 的连续的串,并且全由 11 组成。

比如:完美 4 串,指的就是 1111 。完美 6 串,指的就是111111 。

爱丽有 k个魔法棒,每个魔法棒可以把 S 串中的一个字符变成 1。

爱丽会告诉你 d 的值,然后使用魔法,再将 S 分成若干段,问最多能取出多少个完美 d 串。

输入格式

第一行一个整数 T 。表示测试数据组数。

接下来 T行,每行包含 2 个整数 k,d ,以及一个字符串 S 。

k代表魔法棒的数量, d代表需要取出完美 d 串, S 代表爱丽的字符串。

输出格式

输出 T 行,每行一个整数,代表该组测试数据可以取出最多的完美 d 串的数量。

#include 
using namespace std;
const int N=4e4+10;
int dp[N][1005];
void slove(){
int k,d;string s;
cin>>k>>d>>s;
int n=s.size();
memset(dp,0,sizeof dp);
int pre[N];
for(int i=1;i<=n;i++){
  if(s[i-1]=='0')pre[i]=pre[i-1]+1;
  else pre[i]=pre[i-1];
  }
  for(int i=pre[d];i<=k;i++)dp[d][i]=1;
  for(int i=d+1;i<=n;i++){
    for(int j=0;j<=k;j++){
      if(j>=pre[i]-pre[i-d])dp[i][j]=dp[i-d][j-(pre[i]-pre[i-d])]+1;
      dp[i][j]=max(dp[i][j],dp[i-1][j]);
    }
  }
  
  cout<>t;
  while(t--){
    slove();
  }
  return 0;
}

这道题的难度在dp中是比较大的,有几个关键点可以帮助大家更好地理解。

对于这道题来说状态转移的过程必须要理解:

1.选择区间转移时,即选择在[i-d,i]这个区间进行转移操作时,进行的是if(j>=pre[i]-pre[i-d])dp[i][j]=dp[i-d][j-(pre[i]-pre[i-d])]+1;

2.为选择区间时,继承上一状态即可,即:dp[i][j]=max(dp[i][j],dp[i-1][j]);

关于这个转移部分是如何定义的需要大家更好地理解。

好了,这篇博文耗时耗力,创作不易,希望大家多多关注点赞。

你可能感兴趣的:(算法,数据结构,c++)