hihoCoder[Offer收割]编程练习赛3题目解析

题目1 : hiho密码
时间限制:10000ms
单点时限:1000ms
内存限制:256MB
描述
小Ho根据最近在密码学课上学习到的知识,开发出了一款hiho密码,这款密码的秘钥是这样生成的:对于一种有N个字母的语言,选择一个长度为M的单词;将组成这个单词的所有字母按照顺序不重复的写出(即遇到相同字母时跳过);然后将字母表剩下的没有使用过的字母按照顺序在其后进行排列。
如对于有5个字母的hiho语,选择单词1, 2, 2, 4, 3(此处数字表示字母在字母表中的顺序),则秘钥为1,2,4,3,5。
但是有一天小Ho在计算出了秘钥之后,却发现他弄丢了一开始选择的单词,于是他找到了你,希望你能够帮他找到能够生成这个秘钥的最短的单词。
输入
每个输入文件包含单组测试数据。
每组测试数据的第一行为一个正整数N,意义如前文所述。
每组测试数据的第二行为N个正整数,用来描述一个秘钥,其中第i个正整数Ai表示秘钥的第i个字符在字母表中的顺序。
对于100%的数据,满足N<=1000,1<=Ai<=N。
对于100%的数据,满足对于任意1<=i, j<=N,若i≠j,则Ai≠Aj。
输出
对于每组测试数据,输出能够生成输入给出的秘钥的最短的单词(空串不认为是单词)。由于字母表没有给出,所以对于每个字母,输出其在字母表中的顺序即可(用空格隔开)。
样例输入
5
1 2 4 3 5
样例输出
1 2 4
从后向前遍历整个序列,如果前一个数比后一个数大,那么最短的密钥就是从第一个数到前一个数;如果整个序列是递增的,那么最短的密钥就是第一个数。
AC代码:
#include<cstdio>  
#include<iostream>  
using namespace std;  
int main()  
{  
    int N[1010];  
    int num,pos=0;  
    scanf("%d",&num);  
    for(int i=1;i<=num;i++)  
    {  
        scanf("%d",&N[i]);  
    }  
    for(int i=num;i>=2;i--)  
    {  
        if(N[i-1]>N[i])   
        {  
            pos=i-1;  
            break;   
        }  
    }  
    if(pos)  
    {  
        for(int i=1;i<=pos;i++)  
        {  
            printf("%d ",N[i]);  
        }  
        printf("\n");  
    }  
    else  
    {  
        printf("%d\n",N[1]);  
    }  
}  
题目2 : 机会渺茫
时间限制:5000ms
单点时限:1000ms
内存限制:256MB
描述
小Hi最近在追求一名学数学的女生小Z。小Z其实是想拒绝他的,但是找不到好的说辞,于是提出了这样的要求:对于给定的两个正整数N和M,小Hi随机选取一个N的约数N',小Z随机选取一个M的约数M',如果N'和M'相等,她就答应小Hi。
小Z让小Hi去编写这个随机程序,到时候她review过没有问题了就可以抽签了。但是小Hi写着写着,却越来越觉得机会渺茫。那么问题来了,小Hi能够追到小Z的几率是多少呢?
输入
每个输入文件仅包含单组测试数据。
每组测试数据的第一行为两个正整数N和M,意义如前文所述。
对于40%的数据,满足1<=N,M<=10^6。
对于100%的数据,满足1<=N,M<=10^12。
输出
对于每组测试数据,输出两个互质的正整数A和B(以A分之B表示小Hi能够追到小Z的几率)。
样例输入
3 2
样例输出
4 1
思路很直接,没有什么好说的。
AC代码:
#include<cmath>  
#include<cstdio>  
#include<vector>  
#include<algorithm>  
#include<iostream>  
using namespace std;  
int main()  
{  
    long long int N,M,i;  
    vector<long long int> N1,M1;  
    N1.clear();  
    M1.clear();  
    cin>>N>>M;  
    for(i=1;i<=sqrt(N);i++)  
    {  
        if(!(N%i))  
        {  
            N1.push_back(i);  
            if(i!=(N/i)) N1.push_back(N/i);  
        }  
    }  
    for(i=1;i<=sqrt(M);i++)  
    {  
        if(!(M%i))  
        {  
            M1.push_back(i);  
            if(i!=(M/i)) M1.push_back(M/i);  
        }  
    }  
    sort(N1.begin(),N1.end());  
    sort(M1.begin(),M1.end());  
    long long int m=N1.size()*M1.size();  
    long long int A=m;  
    long long int B=0;  
    long long int len=N1.size();  
    for(i=0;i<len;i++)  
    {  
        long long int d=N1[i];  
        if(binary_search(M1.begin(),M1.end(),d))  
        {  
            B++;  
        }  
    }  
    for(i=2;i<=B;i++)  
    {  
        while(!(B%i)&&!(A%i))   
        {  
            A=A/i;  
            B=B/i;  
        }  
    }  
    cout<<A<<" "<<B<<endl;  
}  
题目3 : 智力竞赛
时间限制:5000ms
单点时限:1000ms
内存限制:256MB
描述
小Hi、小Ho还有被小Hi强拉来的小Z,准备组队参加一个智力竞赛。竞赛采用过关制,共计N个关卡。在第i个关卡中,小Hi他们需要获得Ai点分数才能够进入下一关。每一关的分数都是独立计算的,即使在一关当中获得超过需要的分数,也不会对后面的关卡产生影响。
小Hi他们可以通过答题获得分数。答对一道题获得S点分数,答错一道题获得T点分数。在所有的N个关卡中,小Hi他们一共有M次答题机会。在每个关卡中,都可以在累计答题次数不超过M的情况下使用任意次的答题机会。
那么现在问题来了,对于给定的N、M、S、T和A,小Hi他们至少需要答对多少道题目才能够完成所有的关卡呢?
输入
每个输入文件包含多组测试数据,在每个输入文件的第一行为一个整数Q,表示测试数据的组数。
每组测试数据的第一行为四个正整数N、M、S和T,意义如前文所述。
第二行为N个正整数,分别表示A1~AN。
对于40%的数据,满足1<=N,M<=100。
对于100%的数据,满足1<=N,M<=1000,1<=T<S<=10,1<=Ai<=50。
对于100%的数据,满足1<=Q<=100。
输出
对于每组测试数据,如果小Hi他们能够顺利完成关卡,则输出一个整数Ans,表示小Hi他们至少需要答对的题目数量,否则输出No。
样例输入
1
2 10 9 1
12 35 
样例输出
5
动态规划,比较简单。从比赛的结果来看通过的人数明显较多。
AC代码:
#include<cstdio>  
#include<climits>    
#include<cstring>    
#include<iostream>    
#define MAXN 1010    
#define MAXM 1010   
using namespace std;    
int dp[MAXN][MAXM];   
//dp[i][j]表示过前i关答对j题时最少要答错多少道题  
int a[MAXN];    
int n,m,s,t;    
    
int main()  
{    
    int Q,k,p;    
    scanf("%d",&Q);    
    while(Q--)  
    {    
        memset(dp,0,MAXM*sizeof(int));    
        memset(a,0,sizeof(a));    
        scanf("%d%d%d%d",&n,&m,&s,&t);    
        for(int i=1;i<=n;i++)  
        {    
            scanf("%d",&a[i]);    
            fill(dp[i],dp[i]+MAXM,INT_MAX);    
        }    
        for(int i=1;i<=n;i++)  
        {    
            k=(a[i]+s-1)/s;  
            //第i个关卡最多需要答对几道题    
            for(int j=0;j<=m;j++)  
            {    
                for(int h=0;h<=k;h++)  
                {    
                    p=a[i]-h*s;    
                    p=(p<=0?0:((p+t-1)/t));  
                    //p表示在答对h道题的情况下需要答错几道题    
                    dp[i][j+h]=min(dp[i][j+h],dp[i-1][j]+p);    
                }    
            }    
        }    
        bool has=false;    
        for(int i=0;i<=m;i++)  
        {    
            if(i+dp[n][i]<=m)  
            {    
                has=true;    
                printf("%d\n",i);   
                break;    
            }    
        }    
        if(!has) printf("No\n");  
    }    
}    
题目4 : 子矩阵求和
时间限制:20000ms
单点时限:2000ms
内存限制:256MB
描述
给定一个无限矩阵A,如下图所示,其中Aij=min(i,j)。  
1 1 1 1 1 1 1 1 1
1 2 2 2 2 2 2 2 2
1 2 3 3 3 3 3 3 3
1 2 3 4 4 4 4 4 4
1 2 3 4 5 5 5 5 5  ... y
1 2 3 4 5 6 6 6 6
1 2 3 4 5 6 7 7 7
1 2 3 4 5 6 7 8 8
1 2 3 4 5 6 7 8 9
      ⋮           ⋱
      x
小Hi希望你从中找到一个N*M的子矩阵,使得子矩阵中元素的和是K的整数倍。  
如果不存在这样的子矩阵,输出-1;否则输出子矩阵左上角元素的下标x和y。如果存在多个满足条件的子矩阵,输出x+y最小的一个;如果仍有多个,输出其中x最小的一个。  
输入
输入的第一行是一个整数Q (1 <= Q <= 100)表示输入数据的组数。
对于每组数据,输入一行三个整数N, M, K。1 <= N, M <= 105, 1 <= K <= 106。
输出
对于每组数据输出一行,-1或者子矩阵左上角元素的下标x和y。
样例输入
2
2 2 10
3 3 31
样例输出
2 3
6 7
这种压轴的题,没有什么套路,考智商考思维。
我们设Sij表示左上角是Aij的子矩阵的和。 
那么有两个重要的性质: 
1. Sij + N × M = Si+1,j+1 
2. 当y>=n时,S1,y=S1,y+1;当x>=m时,Sx,1=Sx+1,1
根据性质1,我们只要知道S11的值,那么S11,S22,S33...这条对角线上的子矩阵和哪些是K的倍数可以O(1)求出。只要解一个同余方程S11+xnm=0 mod K。同理知道S12可以得到S23,S34,S45...
根据性质2,我们只要求S11,S12,S13...S1n和S21,S31,S41...Sm1这O(N+M)条对角线上哪些子矩阵是K的倍数就行。
我的代码TLE了,大家可以参考下,有什么改进的地方希望能不吝赐教。解同余方程的扩展欧几里得算法这里就不再讲解了。
#include<cmath>  
#include<cstdio>  
#include<cstring>    
#include<iostream>    
#define MAXN 100010    
#define MAXM 100010   
using namespace std;    
int N,M,K;  
int sum1[MAXN];  
int sum2[MAXM];  
  
//扩展欧几里得算法   
int exgcd(int m,int n,int &x,int &y)  
{  
    int x1,y1,x0,y0;  
    x0=1; y0=0;  
    x1=0; y1=1;  
    x=0; y=1;  
    int r=m%n;  
    int q=(m-r)/n;  
    while(r)  
    {  
        x=x0-q*x1; y=y0-q*y1;  
        x0=x1; y0=y1;  
        x1=x; y1=y;  
        m=n; n=r; r=m%n;  
        q=(m-r)/n;  
    }  
    return n;  
}  
  
//ax与b关于n同余,解x   
int modular_linear_equation(int a,int b,int n)  
{  
    int x,y,x0;  
    int d=exgcd(a,n,x,y);  
    if(b%d) return -1;  
    x0=x*(b/d)%n;    
    for(int i=0;i<d;i++)   
    {  
        int res=(x0+i*(n/d))%n;  
        if(res>=0) return res;  
    }  
}  
  
//求第i行前M个元素的和   
int row(int i)  
{  
    int res=0;  
    if(i>=M)  
    {  
        int j=M;  
        while(j)  
        {  
            res+=j;  
            j--;  
        }  
    }  
    else  
    {  
        int j=1;  
        while(j<=i)  
        {  
            res+=j;  
            j++;  
        }  
        res+=i*(M-i);  
    }  
    return res;   
}  
  
//求第i列前N个元素的和   
int col(int i)  
{  
    int res=0;  
    if(i>=N)  
    {  
        int j=N;  
        while(j)  
        {  
            res+=j;  
            j--;  
        }  
    }  
    else  
    {  
        int j=1;  
        while(j<=i)  
        {  
            res+=j;  
            j++;  
        }  
        res+=i*(N-i);  
    }  
    return res;   
}  
  
int main()  
{  
    int Q;  
    cin>>Q;  
    while(Q--)  
    {  
        memset(sum1,0,sizeof(sum1));  
        memset(sum2,0,sizeof(sum2));  
        cin>>N>>M>>K;  
        int tempn=N;  
        int tempm=M;   
        int minx=999999;  
        int miny=999999;  
        int i=1;  
        while(tempn&&tempm)  
        {  
            sum1[1]+=i*(tempm+tempn-1);  
            i++;  
            tempn--;  
            tempm--;   
        }  
        sum2[1]=sum1[1];  
        for(i=2;i<=N;i++)  
        {  
            sum1[i]=sum1[i-1]+col(i+M-1)-col(i-1);  
        }  
        for(i=2;i<=M;i++)  
        {  
            sum2[i]=sum2[i-1]+row(i+N-1)-row(i-1);  
        }  
        for(i=1;i<=N;i++)  
        {  
            int res=modular_linear_equation(N*M,abs(sum1[i]-K),K);  
            if(res>0)  
            {  
                if(1+res+i+res<minx+miny)  
                {  
                    minx=1+res;  
                    miny=i+res;  
                }  
                else if(1+res+i+res==minx+miny)  
                {  
                    if(1+res<minx)  
                    {  
                        minx=1+res;  
                        miny=i+res;  
                    }  
                }  
            }  
        }  
        for(i=1;i<=M;i++)  
        {  
            int res=modular_linear_equation(N*M,abs(sum2[i]-K),K);  
            if(res>0)  
            {  
                if(i+res+1+res<minx+miny)  
                {  
                    minx=i+res;  
                    miny=1+res;  
                }  
                else if(i+res+1+res==minx+miny)  
                {  
                    if(i+res<minx)  
                    {  
                        minx=i+res;  
                        miny=1+res;  
                    }  
                }  
            }  
        }  
        if(minx==999999) cout<<"-1"<<endl;  
        else cout<<minx<<" "<<miny<<endl;  
    }  
}  


你可能感兴趣的:(hihoCoder[Offer收割]编程练习赛3题目解析)