WHUT第二周训练整理

WHUT第二周训练整理

写在前面的话:我的能力也有限,错误是在所难免的!因此如发现错误还请指出一同学习!

索引

一、简单套路题:01、03、05、09、12、16、22

二、思维题:02、13、18、20

三、模拟题:04、07、08、11、15、17、19、21、23、24、25、26

四、数论题:06、10、14

本题解报告大部分使用的是C++语言,方便调用STL库以及相关容器。

一、简单套路题

1001:计算两个正整数的最小公倍数,即lcm。

分析:先求出两个数的最大公约数gcd,然后代入公式 a ⋅ b g c d ( a , b ) \frac{a\cdot b}{gcd(a, b)} gcd(a,b)ab 即可。

Code

// 这里求gcd直接调用algorithm库里的__gcd()了,手写也很简单
//int gcd(int a, int b){
//    return b == 0 ? a : gcd(b, a%b);
//}
int main()
{
    int a, b;
    while (cin >> a >> b)
    {
        int d = __gcd(a, b); 
        cout << a * b / d << endl;
    }
    return 0;
}

1003:二分背景下猜数字n次可以猜到的最大数。

分析:在最差的情况下,每次猜一个数字x都可以将当前的答案区间[a, b]缩小成[a, x]或者[x, b],最后当区间的两个端点相同的时候就得到了答案。这就是二分的思想,我们知道在有序的数列中查找一个数最差需要log2n次,因此这道题我们只要求log2(x) < n的最大x,即x < 2n

Code

// 为了保险起见我把pow的结果强转成longlong
int main()
{
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        cout << (long long)pow(2, n)-1 << endl;
    }
    return 0;
}

1005:求n个整数中倒数第二小的数。

分析:排序一下输出第二个数就好了,sort默认是按从小到大排序的。

Code

const int MAXN = 100 + 10;

int arr[MAXN];

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        int n;
        cin >> n;
        for (int i = 0; i < n; i++)
        {
            cin >> arr[i];
        }
        sort(arr, arr + n);
        cout << arr[1] << endl;
    }
    return 0;
}

1009:求多个数的最小公倍数lcm。

分析:1001的强化版,两个两个合并就可以了。

lcm(a, b, c) = lcm(lcm(a, b), c)。

Code

int main()
{
	int T;
    cin >> T;
	while (T--)
	{
        int n;
		cin >> n;
		int ans; 
		for (int i = 0; i < n; i++)
		{
			int v;
            cin >> v;
			if (i == 0)  // 先把第一个数字当做前面的lcm
				ans = v;
			else
			{
				int d = __gcd(ans, v);
				ans = ans*v/d;  // 前面合并完的lcm继续合并
			}
		}
		cout << ans << endl;
	}
	return 0;
}

1012:排序。

分析:sort。

Code

const int MAXN = 1000 + 10;

int arr[MAXN];

signed main()
{
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        for(int i = 0; i < n; i++){
            cin >> arr[i];
        }
        sort(arr, arr+n);
        for(int i = 0; i < n; i++){
            if(i) cout << " ";  // 除了第一个元素前面都加
            cout << arr[i];
        }
        cout << endl;
    }
    return 0;
}

1016:求lowbit,即求某一个数的二进制表示中最低的一位1。

分析:很套路,结论很简单,巧妙利用位运算,lowbit是树状数组的核心功能。

Code

int main()
{
    while(cin >> x, x){
        cout << (x&(-x)) << endl;  // x-(x&(x-1))也可以
    }
    return 0;
}

证明:

  1. x - (x & (x - 1)):

    首先我们知道 x & (x - 1) 的作用是消除x的二进制串中最低位的1,如图。

    在这里插入图片描述

    而消除掉的部分正好就是我们要求的部分,因此用 x - (x & (x - 1)) 即可。

  2. x & (-x): 经典写法

    利用了计算机补码的特点,-x的补码就是x的二进制串按位取反,最后+1。比如上面的 x = 10100B,那么-x = 01100B,进行一次与运算&之后100前面的数字就消除掉了,只剩下我们需要的数字。重点理解一下!

1022:完全背包问题。

分析:这道题目一看到就想到完全背包问题,如果没学过的同学可以去学习一下,背包问题是经典的动态规划dp问题。不过数据范围很小,正解可能是贪心。

Code

const int MAXN = 10000 + 10;

int n, v;
int dp[MAXN];
int volume[10], value[10];

void park(int val, int vol)
{
    for (int j = vol; j <= v; j++)
    {
        dp[j] = max(dp[j], dp[j - vol] + val);
    }
}

signed main()
{
    int T;
    cin >> T;
    while(T--){
       	cin >> v;
        n = 3;
        volume[1] = value[1] = 150;
        volume[2] = value[2] = 200;
        volume[3] = value[3] = 350;
        memset(dp, 0, sizeof(dp));
        for(int i = 1; i <= n; i++){
            park(value[i], volume[i]);
        }
        cout << v-dp[v] << endl;
    }
    return 0;
}

二、思维题

1002:计算从a点b分到s点t分时针和分针重合的次数。

分析:说实话这道题不是很好写,我也是看网上的代码才会的,这里引用一下。(我也不是很理解)

时针旋转1周,即12小时内,时针和分针重合了11次,且连续两次重合相隔的时间相同;
所以可计算:60*12/11 = 720/11(分钟)
即:下次重合的时间是 1 点 60/11 分 ;
最少经过 720/11 分钟,时针和分针就能重合一次。

Code

int main()
{
    int a, b, c, d, x, y, t;
    while (cin >> a >> b >> c >> d, a+b+c+d)
    {
        a %= 12;
        c %= 12;
        x = (a * 60 + b) * 11;
        y = (c * 60 + d) * 11;
        if (x > y)
            y += 720 * 11;
        t = y / 720 - x / 720;
        if (x == 0)
            t++;
        cout << t << endl;
    }
    return 0;
}

1013:求NN的最后一位数字

分析:暴力方法不可取,数字太大存不下,我们只需要考虑个位数字,看看是否有规律。

对于输入的N,是正整数,因此个位数只能是0~9。

0 -> 0 -> 0 -> 0 -> 0

1 -> 1 -> 1 -> 1 -> 1

2 -> 4 -> 8 -> 6 -> 2

3 -> 9 -> 7 -> 1 -> 3

4 -> 6 -> 4 -> 6 -> 4

5 -> 5 -> 5 -> 5 -> 5

6 -> 6 -> 6 -> 6 -> 6

7 -> 9 -> 3 -> 1 -> 7

8 -> 4 -> 2 -> 6 -> 8

9 -> 1 -> 9 -> 1 -> 9

通过观察我们发现0~9所有数字序列都可以看成以4为周期的循环序列,因此我们只需要求出前四项即可。

Code

int arr[5];

int main()
{
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        arr[1] = n%10;
        arr[2] = arr[1]*n%10;
        arr[3] = arr[2]*n%10;
        arr[0] = arr[3]*n%10;
        cout << arr[n%4] << endl;
    }
    return 0;
}

1018:求不同的人从出生到18岁生日经过的总天数。

分析:首先考虑没有18岁生日的情况,只可能那个人是02.29生的,那么他就不会有18岁生日。接下来考虑闰年的问题,闰年年份要么能被4整除且不能被100整除,要么能被400整除。一个人的出生月份会影响总天数,如果一个人出生在2月之后,那么不管今年是不是闰年都不影响,闰年将会从第二年开始影响;如果一个人出生在2月及之前,那么闰年从这一年就开始影响。

Code

int checkYear(int year)  // 判断year是否为闰年
{
    if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
        return 1;
    else
        return 0;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T--)
    {
        int year, month, day;
        scanf("%d-%d-%d", &year, &month, &day);  // scanf好控制输入
        if (month == 2 && day == 29)
        {
            printf("-1\n");
        }
        else
        {
            int sum = 0;
            if (month > 2)
            {
                for (int y = year + 1; y <= year + 18; y++)
                {
                    if (checkYear(y))
                        sum += 366;
                    else
                        sum += 365;
                }
            }
            else
            {
                for (int y = year; y <= year + 17; y++)
                {
                    if (checkYear(y))
                        sum += 366;
                    else
                        sum += 365;
                }
            }
            printf("%d\n", sum);
        }
    }
    return 0;
}

1020:不连续吃相同的糖果,问是否能全部吃完。

分析:因为不能连续吃相同的糖果,那么我们应该最先考虑处理掉数量最多的同类糖果,联想高中的插空法,假设有n个相同的糖果,先放好n个糖果,再将剩下的所有排好序的糖果依次插空,如果全部插满n-1个空,那么最大的问题已经解决了,剩下如果有多余的糖果再依次插入新糖果序列的空中,返回Yes,若插不满则返回No。

Code

int main()
{
    int T;
    cin >> T;
    while(T--){
        int n;
        cin >> n;
        int maxV = 0;
        int sum = 0;
        for(int i = 0; i < n; i++){
            int v;
            cin >> v;
            maxV = max(maxV, v);
            sum += v;
        }
        if(sum-maxV+1 < maxV){  // 插不满
            cout << "No" << endl;
        }
        else{
            cout << "Yes" << endl;
        }
    }
    return 0;
}

三、模拟题

1004:计算圆周率到小数点后5n位。

分析:使用圆周率收敛级数进行模拟计算,大家一起学习一下 = =。

Code

int a[5000];

int main()
{
    float s;
    int b, x, n, c, i, j, d, l;
    while (cin >> x, x)
    {
        x *= 5;
        for (s = 0, n = 1; n <= 5000; n++) //累加确定项数.
        {
            s = s + log10((2 * n + 1) / n);
            if (s > x + 1)
                break;
        }
        for (i = 0; i <= x + 5; i++)
            a[i] = 0;
        for (c = 1, j = n; j >= 1; j--) //按公式分布计算。
        {
            d = 2 * j + 1;
            for (i = 0; i <= x + 4; i++) //各位实施除2j+1.

            {
                a[i] = c / d;
                c = (c % d) * 10 + a[i + 1];
            }
            a[x + 5] = c / d;
            for (b = 0, i = x + 5; i >= 0; i--) //各位实施乘j

            {
                a[i] = a[i] * j + b;
                b = a[i] / 10;
                a[i] = a[i] % 10;
            }
            a[0] = a[0] + 1;
            c = a[0]; //整数加1.
        }
        for (b = 0, i = x + 5; i >= 0; i--) //按公式各位乘2
        {
            a[i] = a[i] * 2 + b;
            b = a[i] / 10;
            a[i] = a[i] % 10;
        }
        cout << a[0] << "." << endl; //诸位输出计算结果。
        int now = 1;
        int index = 1;
        while(now <= x/5){
            for(int i = 0; i < 10; i++){
                if(now <= x/5){
                    cout << " ";
                    for(int j = 0; j < 5; j++){
                        cout << a[index++];
                    }
                    now++;
                }
                else{
                    break;
                }
            }
            cout << endl;
        }
    }
    return 0;
}

1007:计算e。

分析:只要10项,根据题目给的公式模拟即可。

注意:保留小数问题,后面有一项最后是0,也要输出。

Code

double ans[MAXN];

int main()
{
    int fac;  // 保存阶乘
    for(int i = 0; i < 10; i++){
        if(i == 0) fac = 1;
        else fac = fac*i;
        if(i == 0) ans[i] = 1;
        else ans[i] = ans[i-1]+1.0/fac;
    }
    cout << "n e" << endl;
    cout << "- -----------" << endl;
    for(int i = 0; i < 10; i++){
        if(i < 3) cout << i << " " << ans[i] << endl;
        else cout << fixed << setprecision(9) << i << " " << ans[i] << endl;
    }
    return 0;
}

1008:统计满足 (a2+b2+m)%(ab) = 0 的 (a, b)对数。

分析:双重循环遍历n,枚举a和b检查是否满足条件,统计结果即可。

注意:(a, b) 和 (b, a) 算一个,每个输出块之间要输出空行。

Code

int main()
{
	int T;
    cin >> T;
    while(T--){
        int kase = 1;
        while(cin >> n >> m, n+m){
            int ans = 0;
            for(int i = 1; i < n; i++){
                for(int j = i+1; j < n; j++){  // i+1开始,避免重复
                    if((i*i+j*j+m)%(i*j) == 0){
                        ans++;
                    }
                }
            }
            cout << "Case " << kase++ << ": " << ans << endl;
        }
        if(T) cout << endl;
    }
	return 0;
}

1011:计算[i, j]之间所有数能达到的最大的3n+1循环次数。

分析:遍历 [i, j] 之间的所有数,进行模拟计算循环次数,维护最大值。

Code

int calc(int x){  // 计算x的3n+1循环次数
    int cnt = 0;
    while(x != 1){
        if(x%2) x = x*3+1;
        else x = x/2;
        cnt++;
    }
    return cnt+1;
}

int main()
{
    int a, b;
    while (cin >> a >> b)
    {
        int aa = min(a, b), bb = max(a, b);  // 确保a小于等于b
        int ans = 0;
        for (int i = aa; i <= bb; i++)
        {
            ans = max(ans, calc(i));
        }
        cout << a << " " << b << " " << ans << endl;
    }
    return 0;
}

1015:模拟加减乘除。

分析:根据输入的运算符进行分类处理即可。

注意:只有当结果是浮点数的时候才保留小数,进行除法的时候结果可能是浮点数也可能是整数!

Code

int main()
{
	int t;
	scanf("%d", &t);
	while(t--)
	{
		getchar();
		double a,b,sum;
        char op;
		scanf("%c %lf%lf",&op,&a,&b);
		switch(op)
		{
			case '+': sum=a+b;break;
			case '-':sum=a-b;break;
			case '*':sum=a*b;break;
			case '/':sum=a/b;break;
		}
		if(sum!=(int)sum)  // 结果是浮点数的时候才保留
		{
			printf("%.2f\n",sum);
		}
		else
		{
			printf("%d\n",(int)sum);
		}
	}
	return 0;
}

1017:求出所有满足10、12、16进制下按位相加结果相同的四位数。

分析:短除法模拟进制转化并且求和。

Code

int calc(int x, int base){  // 模拟短除法
    int sum = 0;
    while(x){
        sum += x%base;
        x /= base;
    }
    return sum;
}

int main()
{
    for(int i = 2992; i <= 9999; i++){
        int a = calc(i, 10), b = calc(i, 12), c = calc(i, 16);
        if(a == b && b == c){
            cout << i << endl;
        }
    }
    return 0;
}

1019:计算GPA。

分析:按要求统计相除即可。

注意:学分和成绩可能是实数,用double,并且可能存在学分为0的情况,这也不是有效成绩记录。

Code

int main()
{
    while(cin >> n){
        double sum1 = 0, sum2 = 0;  // 对精度有要求,用double,不要用float
        for(int i = 0; i < n; i++){
            double a, b;
            cin >> a >> b;
            if(b == -1) continue;  // 缺考,不统计
            sum1 += a;
            if(b >= 90) sum2 += 4*a;
            else if(b >= 80) sum2 += 3*a;
            else if(b >= 70) sum2 += 2*a;
            else if(b >= 60) sum2 += 1*a;
            else sum2 += 0;
        }
        if(sum1 == sum2 && sum2 == 0){  // 包含了学分为0以及缺考的情况
            cout << -1 << endl;
        }
        else{
            cout << fixed << setprecision(2) << sum2/sum1 << endl;
        }
    }
    return 0;
}

1021:统计得到给定分数x的学生人数。

分析:把学生的成绩先保存下来,再遍历一遍统计结果。

Code

const int MAXN = 10000 + 10;

int arr[MAXN];

int main()
{
    while(scanf("%d", &n) == 1 && n){
        for(int i = 0; i < n; i++){
            scanf("%d", &arr[i]);
        }
        int target;
        scanf("%d", &target);
        int cnt = 0;
        for(int i = 0; i < n; i++){
            if(arr[i] == target) cnt++;
        }
        printf("%d\n", cnt);
    }
    return 0;
}

1023:按照规则翻转数字。

分析:有负号先输出负号,再统计后导0的个数,将其之前的字符串翻转。

Code

int main()
{
    int T;
    cin >> T;
    while(T--){
        string str;
        cin >> str;
        if(str[0] == '-'){  // 先输出负号
            cout << "-";
            str = str.substr(1);  // 去掉负号
        }
        int cnt = 0;
        for(int i = str.length()-1; i >= 0; i--){  // 统计后导0的个数
            if(str[i] != '0') break;
            cnt++;
        }
        reverse(str.begin(), str.end()-cnt);  // 调库,翻转后导0之前的字符串
        cout << str << endl;
    }
    return 0;
}

1024:验证角谷猜想。

分析:按照规则模拟即可。

Code

vector<int> ans;  // 使用了C++的vector容器,相当于可以动态伸长的数组

int main()
{
    int T;
    cin >> T;
    while(T--){
        ans.clear();  // 清空容器
        int n;
        cin >> n;
        while(n != 1){
            if(n%2) ans.push_back(n), n = n*3+1;  // 把过程元素记录到容器中
            else n = n/2;
        }
        if(ans.size()){  // 若容器非空
            for(int i = 0; i < ans.size(); i++){
                if(i) cout << " ";
                cout << ans[i];
            }
            cout << endl;
        }
        else{
            cout << "No number can be output !" << endl;
        }
    }
    return 0;
}

1025:字符串翻转相加,直到变成回文串。

分析:涉及到字符串相加,于是我就直接上了高精,模拟过程记录下来即可。

Code

const int L = 1100;

string add(string a, string b)  // 高精加法,只限两个非负整数相加,只要会用就可以
{
    string ans;
    int na[L] = {0}, nb[L] = {0};
    int la = a.size(), lb = b.size();
    for (int i = 0; i < la; i++)
        na[la - 1 - i] = a[i] - '0';
    for (int i = 0; i < lb; i++)
        nb[lb - 1 - i] = b[i] - '0';
    int lmax = la > lb ? la : lb;
    for (int i = 0; i < lmax; i++)
        na[i] += nb[i], na[i + 1] += na[i] / 10, na[i] %= 10;
    if (na[lmax])
        lmax++;
    for (int i = lmax - 1; i >= 0; i--)
        ans += na[i] + '0';
    return ans;
}

int check(string str){  // 检测是否是回文串
    int len = str.length();
    for(int i = 0; i < (len+1)/2; i++){
        if(str[i] != str[len-i-1]) return 0;
    }
    return 1;
}

vector<string> ans;  // C++的容器,动态伸长的数组

int main()
{
    string str;
    while(cin >> str){
        ans.clear();  // 清空容器
        while(!check(str)){  // 如果不是回文串继续变换
            ans.push_back(str);  // 记录过程字符串
            string tmp = str;
            reverse(tmp.begin(), tmp.end());  // 调库翻转字符串
            str = add(str, tmp);  // 高精加法
        }
        ans.push_back(str);  // 记录回文串
        cout << ans.size()-1 << endl;
        for(int i = 0; i < ans.size(); i++){
            if(i) cout << "--->";
            cout << ans[i];
        }
        cout << endl;
    }
    return 0;
}

1026:模拟计算机。

分析:按照规则模拟即可。

Code

int main()
{
    int m1, m2, r1, r2, r3;
    while (cin >> m1 >> m2)
    {
        r1 = r2 = r3 = 0;
        string op;
        cin >> op;
        for (int i = 0; i < op.length(); i++)
        {
            switch (op[i])
            {
            case 'A':
                r1 = m1;
                break;
            case 'B':
                r2 = m2;
                break;
            case 'C':
                m1 = r3;
                break;
            case 'D':
                m2 = r3;
                break;
            case 'E':
                r3 = r1 + r2;
                break;
            case 'F':
                r3 = r1 - r2;
                break;
            }
        }
        cout << m1 << "," << m2 << endl;
    }
    return 0;
}
四、数论题

1006:给定a和b,求是否存在x、y满足 x + y = b x+y = b x+y=b x ⋅ y = b x\cdot y = b xy=b

分析: x ⋅ y = b x\cdot y = b xy=b,则 x = b y x = \frac{b}{y} x=yb,代入 x + y = b x+y = b x+y=b,得 b y + y = a \frac{b}{y}+y = a yb+y=a,即 y = a ± a 2 − 4 ∗ b 2 y = \frac{a\pm\sqrt{a^{2}-4\ast b}}{2} y=2a±a24b

故我们只需要把a和b代入公式,再把求出来的x和y代入验证就可以了。

Code

int main()
{
    while(cin >> n >> m, n+m){
        int a = n*n-4*m;
        if(a < 0){  // 若a^2-4b < 0则无解
            cout << "No" << endl;
        }
        else{
            int x1 = (n+sqrt(a))/2;
            int y1 = n-x1;
            int x2 = (n-sqrt(a))/2;
            int y2 = n-x2;
            if(x1*y1 == m || x2*y2 == m){  // 将可能的(x, y)代入验证
                cout << "Yes" << endl;
            }
            else{
                cout << "No" << endl;
            }
        }
    }
    return 0;
}

1010:统计一个整数的组合方式。

分析:不多说了,母函数裸题,要学的同学自行百度了~~毕竟我不是数论手,不是很会(太菜了…)。

Code

const int MAXN = 1e4 + 10;

int n;
int c1[MAXN];
int c2[MAXN];

int main()
{
    while (cin >> n)
    {
        for (int i = 0; i <= n; ++i)
        {
            c1[i] = 1;
            c2[i] = 0;
        }
        for (int i = 2; i <= n; ++i)
        {
            for (int j = 0; j <= n; ++j)
            {
                for (int k = 0; k + j <= n; k += i)
                {
                    c2[j + k] += c1[j];
                }
            }
            for (int j = 0; j <= n; ++j)
            {
                c1[j] = c2[j];
                c2[j] = 0;
            }
        }
        cout << c1[n] << endl;
    }
    return 0;
}

1014:已知f(x) = 5x13+13x5+kax,给定k,求最小的自然数a满足对任意x有f(x)%65=0。

分析:既然要求对任意x都满足,不妨设x = 1,则f(x) = 18+ka,问题转化为 (18+ka)%65 = 0,即 ka%65 = 47。

因此只需要枚举a的值,从1到65,检查是否满足要求,是则输出答案,否则无解,因此%65的缘故,a再大就出现循环。

Code

int main()
{
	while(cin >> n){
        int ans = -1;
        for(int i = 0; i < 65; i++){
            if(n*i%65 == 47){
                ans = i;
                break;
            }
        }
        if(ans == -1){
            cout << "no" << endl;
        }
        else{
            cout << ans << endl;
        }
    }
	return 0;
}

【END】感谢观看!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值