题目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;
}
}