NEUQ-ACM实验班训练001题解

NEUQ-ACM实验班训练001,1-10部分题解,待补充

7-4 字符串的冒泡排序 (10分)
我们已经知道了将N个整数按从小到大排序的冒泡排序法。本题要求将此方法用于字符串序列,并对任意给定的K(

输入格式:
输入在第1行中给出N和K(1≤K

输出格式:
输出冒泡排序法扫描完第K遍后的中间结果序列,每行包含一个字符串。

输入样例:
6 2
best
cat
east
a
free
day
输出样例:
best
a
cat
day
east
free

核心思路:确定第K次的排序,理解冒泡排序原理是本质;

冒泡排序
先敲一遍冒泡:

#include
using namespace std;
int main(){
     
     int a[100];
     int  n=0;
     cin>>n;
     for(int i=0;i<n;i++) cin>>a[i];
     
     for(int i=0;i<n;i++){
     
        for(int j=0;j<n;j++){
     //这里存在了多次重复判断 复杂度O(n^2);
           if(a[j]>a[j+1]) 
              swap(a[j],a[j+1]);
    
    for(int i=0;i<n;i++) cout<<a[i]<<" ";
//优化:
     for(int i=0;i<n;i++)
        for(int j=0;j<n-i-1;j++)//已经归位的就没必要排了
        //复杂度为O((n^2-n)/2)

而最外层实际就是控制扫描次数(最多n-1次)因此K

通过调整最外层K的参数,就可以实现输出任意K的遍历中间排序状况;

在字符串的冒泡排序中,利用string字符串自带的字典序排序即可实现判断

string a[100]
     for(int i=0;i<n;i++){
     
        for(int j=0;i<n-j-1;j++
        if(a[j]>a[j+1]) swap balabala.....

7-6 房间 (10分)
终于到了假期了,老师决定带领ACM队员们出去游山玩水,计划出行两天,这样的话中间就需要找个地方住宿一晚。

恰巧,老师带领队员们来到了这么一所酒店,这所酒店只有双人间(最多住两人)和三人间(最多住三人),但是价格不同,现在我们算上老师,一共有 n 个人,酒店的双人间价格是 a 元,三人间价格是 b 元,现在老师想知道怎样安排房间才能使得花销最小,你能帮助老师计算出最小的花销吗?

输入格式:
第一行给出一个正整数 T(1≤T≤1000),代表测试数据的组数。

接下来 T 行每行给出三个正整数 n,a,b,1≤n,a,b≤10
​9
​​ ,含义如题。

输出格式:
输出包含 T 行,每行对应一组样例的输出。

输入样例:
2
2 20 200
3 20 20
输出样例:
20
20

分析:需要明确的是,双人间价格未必比三人间价格便宜,再此基础上。如果遍历所有组合是肯定超时的,因此需要推出最优子结构———利用每个人的性价比,也就是每个人住房的价格进行分类讨论。

if(a/2<b/3){
     
     if(a%2==0) cout<<n/2*a<<endl;
     else cout<<min((n/2+1)*a,(n/2-1)*a+b)<<endl;
     //一个数不能整除2,只能余1,因此这个人要么住二人间,要么和其中两个人一起住三人间,在最小值的公式左边中,考虑到二人间此时性价比最高,因此是归进二人间而不是一起住三人间。
//在三人间性价比最高的前提下
   if(n%3==0)
      cout<<n/3*b<<endl;
      
    else if(n%3==1)//产生三种讨论,第一种是和三个人一起住两个二人间,第二种是1个人住二人间,第三种是1个人住三人间;
         cout<<min(min((n/3-1)*b+2*a,n/3*b+a),(n/3+1)*b)<<endl;
     else if(n%3==2)
         cout<<min(n/3*b+a,(n/3+1)*b)<<endl;   

附完整代码

#include
using namespace std;

int main(){
     
     
     int t=0;
     cin>>t;
     
     while(t--){
     
     	 long long n,a,b;
     	 cin>>n>>a>>b;
     	 
     	 if(n==1||n==2) cout<<min(a,b)<<endl;
     	 
     	 else{
     
     	 	  if(a*3<=b*2){
     
     	 	  	
     	 	  	if(n%2==0)
     	 	  	   cout<<n*a/2<<endl;
				
				else cout<<min((n/2+1)*a,(n/2-1)*a+b)<<endl;
     	 	  
		  }
		  
		  else{
     
		  	 
		  	   if(n%3==0) cout<<n/3*b<<endl;
		  	   
		  	   else if(n%3==1) cout<<min(min((n/3-1)*b+2*a,(n/3)*b+a),(n/3+1)*b)<<endl;
		  	   
		  	   else if(n%3==2)
		  	       cout<<min((n/3+1)*b,(n/3)*b+a)<<endl;
		  	   
		  }
     	
	 }
     
}
	 return 0;
}

7-9 求区间和 (10分)
本题会给你一段长度为N的整数序列,并进行K次询问。每次询问要求你给出区间a到b的和(序列下标由1到N)。由于区间和可能较大,请你输出它对10000019取模的结果。

(注意:如果你想不到高效的做法,可以用朴素算法得到一部分分,但本题满分需要你用一个比较高效的做法。)

输入格式:
首先一行整数N,代表序列长度。

接下来一行N个整数,为序列内容。

接下来一行整数K,代表对区间和的询问次数。

接下来K行,每行两个整数a和b,请你输入序列的第a到b号元素(含)的和取模后的结果。

输出格式:
一共K行,每行一个整数,代表询问的区间和取模后的结果。

输入样例:
在这里给出一组输入。例如:

5
1 2 3 4 5
3
1 5
2 3
3 3
输出样例:
在这里给出相应的输出。例如:

15
5
3
数据限制:
N<=1e6;
​​K<=1e5;

分析:朴素思路:每询问一次就处理一次,时间复杂度为O(n*k) 题里的数据限制使得朴素算法是超时的,Omax(nk)=1e11>1e8;
正常思路:利用前缀和处理询问时直接做差即可,复杂度O(N+K)

朴素思路:

#include
using namespace std;
#define maxn 1000005
int main(){
     
     int  n=0;
     long a[maxn];
     cin>>n;
     
     for(int i=1;i<=n;i++) cin>>a[i];
     int k=0;
     cin>>k;
     
     while(k--){
     
       long long sum=0;
       int l,r=0;
       cin>>l>>r;
       for(int i=l;i<=r;i++)
            sum+=a[i];
            
       cout<<sum%10000019<<endl;
       }
    return 0;
          

优化思路:

#include
#define maxn 1000005
using namespace std;
int main(){
     
     int n=0;
     int sum[maxn];
     cin>>n;
     
     for(int i=1;i<=n;i++)
        long x=0;
        cin>>x;
        sum[i]=sum[i-1]+x;//先做前缀和,准备进行k次询问;

    int k=0;
    cin>>k;
    while(k--){
     
       int l,r=0;
       cin>>l>>r;
       cout<<(sum[r]-sum[l-1])%10000019<<endl;//类似数列已知和求其中几项,例如s4-s2=a3+a4;
       }
     return 0;    

7-10 抽卡游戏 (10分)
本题的灵感来源于一个古典的概率模型。

Alice 在一个卡池里抽卡,里面有 x 张 s 卡和 y 张 a 卡。

Alice 每次会不放回的随机从卡池中抽出一张卡。

Bob 在一旁看 Alice 抽卡并对每次的结果进行预测:

若卡池里 s 卡的数量多于 a 卡,Bob 会猜 Alice 抽出 s 卡。

反之则会猜测 Alice 抽出 a 卡。

但是如果当卡池里的两种卡的数量相等的时候,Bob 就不对抽卡的结果做任何的猜测了。

Alice 会一直抽卡,直到卡池空为止。

现在告诉你初始的时候卡池里 s 卡和 a 卡的数量,你能算算 Bob 期望下猜对多少次?

输入格式:
在一行中给出两个整数 a,b(1≤a,b≤1e5)

输出格式:
一个实数表示期望,四舍五入保存两位小数。

输入样例:
1 1
输出样例:
1.00
样例解释:
初始局面的时候 Bob 不做任何猜测,第一次抽完之后,第二次抽的时候不管剩下的是哪一种卡,Bob 都能猜对,所以期望是1.00

分析: 难点在于,1.Bob不做猜测则概率是0;
2.抽和猜是两部分,抽是具象的,猜有猜对和猜错,因此两部分的期望都要算;
3:利用递推公式计算E(a,b);
​​证明如下,我们令Ex(a,b)表示相应的期望。
Ex(1,1) = 1; Ex(1,2) = Ex(2,1) = 2/3+(2/3)(0+1)+(1/3)(1+1) = 2;
EX(a,b) =(a/a+b)+ (a/a+b)*Ex(a-1,b)+(b/a+b)Ex(a,b-1) //前提是a>b; 第二部分是条件概率,在第一次抽的基础上进行下一次的概率计算
数学归纳法:Ex(a-1,b) = a-1;Ex(a,b-1) = b-1;
EX(a,b) =(a/a+b)+ (a/a+b)
(a-1)+(b/a+b)*a = a(a+b)/(a+b) = a;
同理当a = b 或 a < b证法相同

因此,结论是,只要判断a,b中的最大值即可

#include
using namespace std;
int main(){
     
    int a,b=0;
    cin>>a>>b;
    cout<<(a>b? a:b)<<endl;
    return 0;

7-8 最长上升子序列 (10分)
给定一个序列,求它的一个递增子序列,使它的元素个数尽量多,求该序列的最长上升子序列中元素个数。例如序列1,6,2,5,4,7的最长上升子序列是1,2,5,7或1,2,4,7,则其最长上升子序列中的元素个数为4。

输入格式:
第一行中输入序列的个数(不超过100),第二行中输入每个元素,以空格隔开。

输出格式:
输出该序列中最长上升子序列中的元素个数。

输入样例:
在这里给出一组输入。例如:
6
1 6 2 5 4 7
输出样例:
在这里给出相应的输出。例如:
4
分析:初始的每一个子序列长度均为1,当到达第i位的时候,从第1位进行判断,如果满足小于,那么子序列的长度++,反之,保留当前最大值,保留之后要进行下次判断!直到第i-1位结束,也就是说,是回溯而不是向前递推!!!

每次比较可以得出状态转移方程 dp[i]=max(dp[i],dp[j]+1)
经典的dp动态规划入门 反正我也不会
1.O(n^2)复杂度代码

#include
using namespace std;
int main(){
     
      int n=0;
      int a[1005],dp[1005];
      cin>>n;
      for(int i=1;i<=n;i++) cin>>a[i];
      
      for(int i=1;i<=n;i++)
            dp[i]=1;
        for(int j=1;j<i;j++)
           if(a[i]>a[j])
               dp[i]=max(dp[i],dp[j]+1);

//通过O(n^2)遍历,得到了所有子序列长度,进行一次o(n)遍历求最大值即可
       int res=0;
       for(int i=0;i<n;i++)
          res=max(res,dp[i]);

      cout<<res<<endl;

2。二分求子序列,咕了咕了

你可能感兴趣的:(字符串,算法,动态规划,数据结构,acm竞赛)