[总结向] PAT甲级 简单数学 1049 1059 1081 1088 1096 1103 1104 1112 1116 1152

【总结向】PAT甲级 简单数学 20200525

引:本周值得关注的题目
  • 1049 1的个数:求1-N中数字1在每一位上出现的个数,可以通过枚举N的每一位通过排列组合计算1出现的次数,累加得到答案,暴力枚举会超时。
  • 1059 质因子:列举质因子时,为了减少复杂度,只列举前根号n的因子,循环结束后,可能n剩余1,可能剩余最大的>根号n的因子,这时输出>根号n的因子即可
  • 1081 有理数的和:在做分数通分时候,先除后乘可有效减少溢出问题
  • 1103 整数N的K-P分解:给出了两种思路DFS和DP,这题可以转化为二维完全背包问题来做
一、重点题目
1.1049 Counting Ones (30分)

在这里插入图片描述思🦌:
N=1e9,暴力的话,要从1到1e9每个数判断每一位(最多10次),则复杂度为1e10。因此将问题转化为从1到N,1在[每一位]出现的次数。然后遍历N的位数得到答案,以降低复杂度
不妨设,N=abcdefg,现讨论[1,abcdefg]区间,1在第四位(d)出现的次数.我们使用排列组合的思想

  • d=0时,[1,abc0efg]中,要想出现第四位为1的数Ni.那么Ni的aibici一定是在0到abc-1中的,eifigi可以取0-999.故次数为abc*1000
  • d=1时,[1,abc1efg]中,要想出现第四位为1的数Ni.需要再分两种情况
    • d=1时,当Ni在[abc0000,abc1def]中,要想出现第四位为1的数Ni.那么Ni的aibici一定是abc,eifigi可以取0-eifigi.故次数为efg+1
    • d=1时,当Ni在[1,abc0000)中,要想出现第四位为1的数Ni.那么Ni的aibici一定是在0到abc-1中的,eifigi可以取0-999.故次数为abc*1000
  • d>1时,当Ni在[1,abcdefg]中,要想出现第四位为1的数Ni,那么Ni的aibici一定是在0到abc中的,eifigi可以取0-999,故次数为(abc+1)*1000

维护高位数hi,存储abc, 低位数lo,存储efg. 遍历N的每一位得到结果

代🐎:

// 1049 Counting Ones
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
    string n;
    cin >> n;
    
    int re = 0;
    for(int i = n.size() - 1; i >= 0; i --)
    {
        int lo = 0, hi = 0, base = 1;
        //计算lo,hi
        for(int j = 0; j < n.size(); j ++){
            if(j > i) lo = lo * 10 + (n[j] - '0'), base *= 10;
            if(j < i) hi = hi * 10 + (n[j] - '0');
        }
        //cout << lo << " " <<  hi << endl;
        
        if(n[i] == '0') re += hi * base;
        else if(n[i] == '1') re += hi * base + 1 + lo;
        else re += (hi + 1) * base; 
    }
    
    cout << re << endl;
    return 0;
}
2.1059 Prime Factors (25分)

在这里插入图片描述
思🦌:
素数打表,然后采用试除法:如果N可以整除该质数,那么再试一次,直到N除尽为1.
循环为不断增加的质数,最多40000个,注意,因子可以大于根号n,但只有1个唯一的一个因子>n
因此我们列举根号n前的质数,只剩>根号n的因子或1退出循环。最后如果有,输出最大的因子

代🐎:

//1059 Prime Factors
#include<iostream>
#include<cstring>
using namespace std;

const int N = 40000;
bool p[N]; // 素数表

void init()
{
    memset(p, true, sizeof p);
    int psize = 40000;
    for(int i = 2; i <= psize; i++)
    {
        if(p[i]){
            for(int j = i + i; j <= psize; j += i)
                p[j] = false;
        }
    }
}

int main()
{
    init();

    int n;
    cin >> n;
    bool isFirst = true;
    
    if(n == 1){ // 特判
        cout << n << "=" << n << endl;
        return 0;
    }
    else cout << n << "=";
    
    for(int i = 2; i <= n / i; i ++)
    {
        if(!p[i]) continue;
        int cnt = 0;
        while(n % i == 0) cnt ++, n /= i;
        if(cnt == 0) continue;
        
        if(isFirst) isFirst = false; //第一个符号不输出*
        else cout << "*";
        
        cout << i ;
        if(cnt > 1) cout << "^" << cnt; 
        
    }
    
    
    if(n > 1){
        if(!isFirst) cout << "*";
        cout << n << endl; 
    }
    
    
    return 0;
}
3.1081 Rational Sum (20point(s))

在这里插入图片描述

思🦌:
这个题要注意数据溢出的问题,先除再乘,分数加法通分时要通分时,如8/14+2/6,公因子是42而非84。这样可以有效减少溢出

代🐎:

// 1081 Rational Sum (20point(s))
#include <iostream>
#include <algorithm>
using namespace std;

typedef long long LL;
const int N = 110;

LL gcd(LL a, LL b)
{
    return b ? gcd(b, a % b) : a;
}

int main()
{
    int n;
    cin >> n;
    
    LL a = 0, b = 1;
    while(n --)
    {   
        LL c, d;
        scanf("%lld/%lld", &c, &d);
        LL e = gcd(c, d);
        c /= e, d /= e;
        
        LL p = gcd(b, d); //再通分时候,要先约分再通分,否则会溢出
        a = d / p * a + b / p * c;
        b = b / p * d; // 先除再乘,防止溢出
        
        LL f = gcd(a, b);
        a = a / f;
        b = b / f;
    }
    
    LL g = a / b;
    LL h = a % b;
    if(b < 0) b = -b, a = -a;
    if(!g && !h) cout << "0";  // 0
    if(g && !h) cout << g;   //只有整数
    if(g && h) cout << g << " ";  // 存在整数和小数
    if(h) cout << a % b << "/" << b; //有小数
    
    return 0;
}
6.1103 Integer Factorization (30point(s))

在这里插入图片描述

思🦌:
这个题要注意数据溢出的问题,先除再乘,分数加法通分时要通分时,如8/14+2/6,公因子是42而非84。这样可以有效减少溢出

这题用dfs可能会超时,dp可以将时间缩短40倍(680ms VS 17ms),本题可以转化为二维完全背包问题来做
f ( m , i , j ) f(m,i,j) f(m,i,j)表示选取前m个物品,背包容量N=i,格子个数k=j的集合中,背包最大的价值(因子和)
因此二维完全背包问题有 f ( m , i , j ) = m a x ( f ( m − 1 , i , j ) , f ( m , i − m p , j − 1 ) + m ) ( ∗ ) f(m,i,j) = max(f(m-1,i,j), f(m,i-m^p,j-1)+m) (*) f(m,i,j)=max(f(m1,i,j),f(m,imp,j1)+m)()[不选m号和选多个m号]
公式推导如下: f ( m , i , j ) = m a x ( f ( m − 1 , i , j ) , f ( m − 1 , i − m p ∗ 1 , j − 1 ) + m , f ( m − 1 , i − m p ∗ 2 , j − 1 ) + 2 ∗ m , . . . ) ( 1 ) f(m,i,j) = max(f(m-1,i,j), f(m-1,i-m^p*1,j-1)+m, f(m-1,i-m^p*2,j-1)+2*m, ...) (1) f(m,i,j)=max(f(m1,i,j),f(m1,imp1,j1)+m,f(m1,imp2,j1)+2m,...)(1)
因为是完全背包问题,公式含义如下:[不选m号和选1个m号,选2个m号,选3个m号…]
作变量的等价替换有, f ( m , i − m p , j − 1 ) = m a x ( f ( m − 1 , i − m p , j − 1 ) , f ( m − 1 , i − m p ∗ 2 , j − 1 ) + m , . . . ) ( 2 ) f(m,i-m^p,j-1) = max(f(m-1,i-m^p,j-1), f(m-1,i-m^p*2,j-1)+m, ...) (2) f(m,imp,j1)=max(f(m1,imp,j1),f(m1,imp2,j1)+m,...)(2)
因此将(2)式+m,代入(1)式,可以得到(*)式。因此得到了状态转移方程

本题还要求二维背包问题的具体方案,可以倒着推数组,将每一步看成一个决策,总是回溯价值最大的方案
这里求字典序最大的方案,
因此若价值相同应该优先选m号(m号从后往前)而非不选的策略.在回溯过程中输出最大即可

代🐎:
(1) DP

// DP:1107 Integer Factorization (30point(s))
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 410;
const int K = 21; // 20^2就超过400了

int f[K][N][N];

int powr(int a, int b)
{
    int re = 1;
    for(int i = 1; i <= b; i ++) re *= a;
    return re;
}

int main()
{
    int n, k, p;
    cin >> n >> k >> p;
    
    //初始化数组为负无穷,因为有考虑到没有方案的情况
    memset(f, -0x3f, sizeof f);
    f[0][0][0] = 0;
    
    // 状态转移方程
    int m;
    for(m = 1; ; m ++)
    {
        int v = powr(m, p);
        if(v > n) break; // 超过了背包容量
        
        for(int i = 0; i <= n; i ++)
            for(int j = 0; j <= k; j ++)
            {
                f[m][i][j] = f[m-1][i][j]; // 因为i-m^p,j-1可能越界,要单独判断
                if(i >= v && j) f[m][i][j] = max(f[m][i][j], f[m][i-v][j-1] + m);
            }
    }
    
    m --; //注意m++后退出的循环,要减回去

    if(f[m][n][k] < 0) puts("Impossible");
    else
    {
        cout << n << " = ";
        bool is_first = true;
        while(m)
        {
            int v = powr(m, p);
            while(f[m][n-v][k-1] + m >= f[m-1][n][k]) //当价值相同时,总是选m号的决策
            {
                if(is_first) is_first = false;
                else cout << " + ";
                
                cout << m << "^" << p;
                
                n -= v, k -= 1;
            }
            
            m --;
        }
    }
    
    return 0;
    
}

(2) DFS

// DFS:1107 Integer Factorization (30point(s))
#include <cstdio>
#include <vector>
#include <cmath>
#include <algorithm> 
using namespace std;
vector<int> temp, ans, fac; //存放数字
int num, k, p, maxsumN = -1;

//将数字的幂存到fac vector中,下标从1到fac.size()-1  fac[0]=0用不上 
void init(){
	int i = 1;
	fac.push_back(0); 
	while(true){
		int t = pow(i*1.0, p*1.0); //随i增长的幂集
		if(t > num) break; 
		fac.push_back(t);		
		i++; 
	} 
}

void DFS(int index, int sumK, int sumN, int sumS){//index为fac数组的下标 
	if(sumK == k && sumS == num){
		if(sumN > maxsumN){
			maxsumN = sumN;
			ans = temp;
		}
		return;
	}	 
	if(sumS > num || sumK > k) return;
	//不去招惹下标0.fac[0]
	if(index - 1 >= 0){
		temp.push_back(index);
		DFS(index, sumK + 1, sumN + index + 1, sumS + fac[index]);
		temp.pop_back();
		DFS(index - 1, sumK, sumN, sumS); //倒叙排列 
	}	
}
 
int main(){	
	scanf("%d%d%d", &num, &k, &p);
	init(); 
	DFS(fac.size()-1,0,0,0); //DFS入口
	int size = ans.size();
	//printf("%d",fac.size()); 
	if(maxsumN == -1){
		printf("Impossible");	
	}
	else{
		printf("%d = %d^%d", num, ans[0], p); //ans[0]存的是下标,下标 + 1 = 实际数字		
		for(int i = 1; i < size; i++){
			printf(" + %d^%d", ans[i], p);
		}
	}
	return 0;
} 
二、次重点题目
5.1088 Rational Arithmetic (20分)

在这里插入图片描述

思🦌:
这题还是要开long long,做加减通分时做分母的公因子

代🐎:

// 1088 Rational Arithmetic (20分)
#include <iostream>
using namespace std;
typedef long long ll;

ll gcd(ll a, ll b)
{
    return b ? gcd(b, a % b) : a;
}

void out(ll a, ll b)
{
    bool is_plus = false;
    int e = gcd(a, b);
    a /= e, b /= e;
    if(b < 0) a = -a, b = -b;
    int aa = a / b;
    int bb = a % b;
    
    if(a < 0) is_plus = true;
    if(is_plus) cout << "(";
    
    if(aa < 0 && bb < 0) bb = -bb; // 负号若在带分数的分子上,不表示
    if(!a) cout << "0";
    if(aa && !bb) cout << aa;
    if(aa && bb) cout << aa << " ";
    if(bb) cout << bb << "/" << b;
    
    if(is_plus) cout << ")";
    return;
}


int main()
{
    ll a, b, c, d, e, f;
    scanf("%lld/%lld %lld/%lld", &a, &b, &c, &d);

    int p = gcd(b, d);
    
    out(a, b), cout << " + ", out(c, d), cout << " = ";
    
    e = d / p * a + b / p * c;
    f = b / p * d;
    out(e, f), cout << endl;
    
    out(a, b), cout << " - ", out(c, d), cout << " = ";
    e = d / p * a - b / p * c;
    f = b / p * d;
    out(e, f), cout << endl;
    
    out(a, b), cout << " * ", out(c, d), cout << " = ";
    e = a * c;
    f = b * d;
    out(e, f), cout << endl;
    
    out(a, b), cout << " / ", out(c, d), cout << " = ";
    if(c == 0) {cout << "Inf" << endl; return 0;}
    e = a * d;
    f = b * c;
    out(e, f), cout << endl;
    
    return 0;
}
5.1096 Consecutive Factors (20point(s))

在这里插入图片描述

思🦌:
求连续的最长因子段,思路就是用双指针枚举一下最长的因子段
注意,虽然29=1*29,但它不存在因子段(1不算因子).因此遇到这种情况,ans向量一定是空的,我们最后把n加进去

代🐎:

//1096 Consecutive Factors (20point(s))
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    int n;
    cin >> n;
    
    vector<int> ans;
    for(int i = 2; i <= n / i; i++ )
    {
        
        vector<int> tmp;
        for(int nn = n, j = i; nn % j == 0; j ++)
        {
            // 双指针枚举段
            tmp.push_back(j);
            nn /= j;
        }
        if(tmp.size() > ans.size()) ans = tmp;
    }
    
    if(ans.empty()) ans.push_back(n);
     
    cout << ans.size() << endl;
    cout << ans[0];
    for(int i = 1; i < ans.size(); i ++)
    {
        cout << "*" << ans[i];
    }
    
    return 0;
}
7.1104 Sum of Number Segments (20point(s))

在这里插入图片描述
思🦌:
这题和组合数还不一样,是连续的子段集合。因此枚举左右端点相乘即可。

代🐎:

//1104 Sum of Number Segments (20point(s))
#include <iostream>
#include <cstdio>
using namespace std;

const int N = 100010;
double a[N];

int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++) scanf("%lf", &a[i]);

    double re = 0;
    for(int i = 1; i <= n; i ++)
    {
        re += a[i] * i * (n - i + 1);
    }
    
    printf("%.2lf", re);
    return 0;
}
8.1112 Stucked Keyboard (20point(s))

在这里插入图片描述

思🦌:
这题调了很久,忘了改变循环指针i要–了,因为下一轮要++回来

代🐎:

//1112 Stucked Keyboard (20point(s))
#include<iostream>
#include<cstring>
using namespace std;

const int N = 200;
int st[N]; // 0:坏的,1:好的,2:输出过的坏的

int main()
{
    int k;
    string str;
    cin >> k >> str;
    
    for(int i = 0; i < str.size(); i ++)
    {
        int j = i + 1;
        while(j < str.size() && str[j] == str[i]) j ++;
        int len = j - i;
        if(len % k)  st[str[i]] = 1; // 好的置1,坏的置0
        i = j - 1; // 还要i ++过来,所以i要提前-1
    }
    
    string re;
    for(int i = 0; i < str.size(); i ++)
    {
        if(!st[str[i]]) cout << str[i], st[str[i]] = 2;
        
        if(st[str[i]] == 1) re += str[i];
        else  // 坏的
        {
            re += str[i];
            i += k;
            i --; //回退一格
        }
    }
    
    cout << endl;
    cout << re << endl;
    
    return 0;
}
9.1116 Come on! Let’s C (20分)

在这里插入图片描述

思🦌:
模拟就完事了

代🐎:

// 1116 Come on! Let's C (20分)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unordered_map>
using namespace std;

const int N = 10010;
int is_list[N];
unordered_map<int, string> M;

bool is_prime(int x)
{
    if(x == 1) return false;
    for(int i = 2; i <= x / i; i ++)
    {
        if(x % i == 0) return false;
    }
    return true;
}

int main()
{
    int n;
    cin >> n;
    for(int i = 1; i <= n; i ++) 
    {
        int v;
        scanf("%d", &v);
        if(i == 1) M.insert({v, "Mystery Award"}); //1
        else if(is_prime(i)) M.insert({v, "Minion"});
        else M.insert({v, "Chocolate"});
        is_list[v] = 1;
    }
    

    int k;
    cin >> k;
    while(k --)
    {
        int m;
        scanf("%d", &m);
        printf("%04d: ", m);
        if(!is_list[m]) printf("Are you kidding?\n");
        else{
            if(is_list[m] == 1) printf("%s\n", M[m].c_str()), is_list[m] = 2;
            else printf("Checked\n");
        }
        
    }

    return 0;
}
10.1152 Google Recruitment (20分)

在这里插入图片描述

思🦌:
这题可以用字符串存,并用c11的stoi(str),substr(begin,len)进行转化
质数我们可以用筛法开一个表,否则的话4e4*1e3=4e7有超时的风险
判断大质数时,因子可以不要是2,4,6,,而是质数表里的数,这样速度会加快许多

代🐎:

//1152 Google Recruitment (20分)
#include <iostream>
#include <cstring>
using namespace std;

const int N = 40010;
bool p[N];
int prime[N];

void init()
{
    int id = 0;
    memset(p, true, sizeof p);
    for(int i = 2; i <= 40000; i ++)
        if(p[i]){
            prime[id++] = i;
            for(int j = i + i; j <= 40000; j += i)
                p[j] = false;
        }
            
}

bool check(int x)
{
    if(x == 1) return false;
    for(int i = 0; prime[i] <= x / prime[i]; i ++)
    {
        if(x % prime[i] == 0) return false;
    }
    return true;
}

int main()
{
    int l, k;
    cin >> l >> k;
    string n;
    cin >> n;
    
    init();
    
    for(int i = 0; i + k <= n.size(); i ++)
    {
        int t = stoi(n.substr(i, k));
        if(check(t))
        {
            cout << n.substr(i, k) << endl;
            return 0;
        }
    }

    puts("404");

    return 0;    
    
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值