寒假每日一题(入门组)Week3

1208. 翻硬币(1月18日)

分析

长度100, 如果用dfs, 每个状态有2种, 2^100, 必定超时

需要想额外的方法去做
观察下一些性质

长度是n的话, 一共可以进行的操作n - 1种, 第1次操作前两个, 以此类推
在这里插入图片描述
能够改变第1个位置, 其实只有1种, 而且发现n - 1种操作有以下性质

  1. 操作顺序没影响
  2. 每个操作最多1次, 操作2次等于没有操作

可以列下列表

操作列表12n - 1
能否操作第1硬币与目标状态是否相同: 相同1, 不同0

所以第1个状态是唯一确定的

样例模拟:
o * o o * o *
* o o o * * o

操作列表123456
能否操作

  1. 第一个位置不一样(翻转)

翻转前:
o * o o * o *
* o o o * * o
翻转后:
* o o o * o *
* o o o * * o

操作列表123456
能否操作☑️

  1. 第2个硬币(因为第一个硬币已经操作过, 此时能影响第2个硬币的状态只有2), 因为状态相同, 必然不能做翻转

翻转前后:
* o o o * o *
* o o o * * o

操作列表123456
能否操作☑️

  1. 看下第3个硬币, 此时能够影响第3个硬币的操作只有3

翻转前:
* o o o * o *
* o o o * * o

操作列表123456
能否操作☑️

相同不能做


  1. 相同不能做
操作列表123456
能否操作☑️

  1. 相同不能做
操作列表123456
能否操作☑️

  1. 不同, 必须做
操作列表123456
能否操作☑️☑️

答案保证一定有解, 因此做完前6个操作以后, 最后一个硬币状态必然相同, 如果不同的话, 一定无解

因为如果最后两个硬币状态不同, 此时前面的操作被唯一确定, 能够影响最后一个硬币的操作已经没有了, 因此如果最后一个硬币状态不同, 就一定无解

因此最后一个状态不用枚举, 答案保证一定有解, 所以最后一个硬币状态一定相同

发现每个操作序列被唯一确定的, 然后从前往后递推下, 看下每个操作没有被做, 统计下操作次数

联动题

八数码

code

#include <iostream>
using namespace std;
const int N = 110;
string a, b;

void turn(int x){
    if (a[x] == 'o') a[x] = '*';
    else a[x] = 'o';
}
int main(){
    cin >> a >> b;
    
    int res = 0;
    for (int i = 0; i + 1 < a.size(); i ++ ){ // 只需要枚举到倒数第2个字符, 最后一个字符答案保证有解, 不需要判断, 必然相同
        if (a[i] != b[i]){
            turn(i), turn(i + 1);
            res ++;
        }
    }
    cout << res << endl;
    return 0;
}

1532. 找硬币 (1月19日)

分析(hash表)

code(hash表, 不开数组) O(n)

#include <iostream>
#include <unordered_set>
using namespace std;

const int INF = 100010;

int n, m;

int main(){
    
    scanf("%d%d", &n, &m);
    unordered_set<int> S;
    
    int v1 = INF, v2;
    for (int i = 0; i < n; i ++ ){
        int a, b;
        scanf("%d", &a);
        b = m - a;
        if (S.count(b)){
            S.insert(a);
            if (a > b) swap(a, b);
            if (a < v1) v1 = a, v2 = b;
        }
        else S.insert(a);
    }
    
    if (v1 == INF) puts("No Solution");
    else printf("%d %d\n", v1, v2);
    
    return 0;
        
}
n 0;
}

分析(双指针)

因此先将数组排序

对于每个i都要找到和它配对的j, 使得a[i] + a[j] <= m, 且j最大

定义完后, 如果存在a[i] + a[j] = m的话, 因为上述定义是j最大, 并且数组是拍完序的, 越往后越大, 因此通过上面的方式一定能够找到 a[i] + a[j] = m

当 i —> 往后走的时候, <—j只会往前走, 那么就可以用双指针算法, 否则不可以

证明(单调性)
i—> i’得时候, 假如j —>j’ 往后走了
那么a[i] + a[j] <= m(蓝色线) , a[i’] + a[j’] <= m(蓝色线),
那么a[i] + a[j’] <= a[i’] + a[j’] <= m (红色线)
那么说明与i匹配得应该是j’与, 假设矛盾

在这里插入图片描述

code(双指针, 需要单调性)(O(nlogn))

#include <iostream>
#include <algorithm>
using namespace std;

const int N = 100010;

int n, m;
int w[N];

int main(){
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ ) scanf("%d", &w[i]);
    sort(w, w + n);
    
    for (int i = 0, j = n - 1; i < j; i ++ ){
        while (i < j && w[i] + w[j] > m) j --;
        if (i < j && w[i] + w[j] == m) {
            printf("%d %d", w[i], w[j]);
            return 0;
        }
    }
    
    puts("No Solution");
    return 0;
}

1341. 十三号星期五(1月20日)

分析

通用的做法, 就是一般的星期的做法, 就是枚举天
400年 * 360天 ~= 1.2 ∗ 1 0 5 1.2*10^5 1.2105

另外的话可以枚举月
400年 * 12月 = 4800次

枚举每个月份第1天 距离1900-1-1过了多少天
1900-1-1 距离1900-1-1 过了0天
1900-2-1 距离1900-1-1 过了31天( 1月31 - 1月1 = 30, 再+1 = 31)
1900-3-1 距离1900-1-1 过了31 + 28 = 59天

当前年的某个月距离 1900-1-1 过了days天的话,
那么当前月的 13号 距离1900-1-1过了 days + 12天 (13 - 1 = 12天)

起点的话是星期一, 那么days + 12是周几呢(7天一循环)

(days + 12) % 7 == 0 (周一) 以此类推

剩下的关键: 就是求每个月的1-1 到1900-1-1过了多少天
直接枚举, 从1900开始 一年一年枚举
对于某一年的话, 枚举每个月的1月1号过了多少天

判断下闰年和平年
闰年:

  1. 100 不能整除年份的话, 4| 年份 (2020☑️)
  2. 100 | year, 400 | year (1900❌, 2000☑️)
for(用一个变量表示当前距离起点过了多少天days)
	for 每个月
		(内层循环, 将days+内层循环的天数)
	

为了写代码方便判断出来, 每个月有多少天, 可以开个数组记录下每个月多少天

	month[] = {0, 31, 27, 31, 30}; // 前面补个0, 因为数组从0开始

month[5] 直接求出5月多少天
在这里插入图片描述
int weekend[7] 表示每个星期几有多少天, 下标0-周一, 1-周二

code

#include <iostream>
using namespace std;
int month[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int weekend[7];
int n;

int main(){
    scanf("%d", &n);
    
    int days = 0; // 记录当前年当前月距离1900-1-1过了多少天
    for (int year = 1900; year < 1900 + n; year ++ ){
        for (int i = 1; i <= 12; i ++ ){
            weekend[(days + 12) % 7] ++;
            days += month[i];
            if (i == 2){
                if (year % 100 && year % 4 == 0 || year % 400 == 0) days ++;
            }
        }
    }
    
    for (int i = 5, j = 0; j < 7; j ++) // 从5开始 j枚举7次
        cout << weekend[(i + j) % 7] << ' ';
    cout << endl;
        
    return 0;
}

754. 平方矩阵 II(1月21日)

分析(对角线)

从对角线开始, 分别往右边/下边 延伸

在这里插入图片描述

code

#include <iostream>
using namespace std;
const int N = 110;
int a[N][N], n;

int main(){
    while (cin >> n, n){
        
        
        for (int i = 1; i <= n; i ++ )
            for (int j = i, k = 1; j <= n; j ++, k ++ ){ // j从对角线开始
                a[i][j] = k; // 向右延伸
                a[j][i] = k;// 行变动, 列不变, 向下延伸
            }
            
        for (int i = 1; i <= n; i ++ ){
            for (int j = 1; j <= n; j ++ )
                cout << a[i][j] << ' ';
            cout << endl;
        }
        cout << endl;
    }
    
    return 0;
}

分析(找规律)

每一行, 对角线之前的部分是从大到小, 对角线之后的部分从小到大
这样做的话, 直接按行枚举

比如
1 2 3 4
2 1 2 3 第2行, 第1个数就是2, 然后一直递减到1
3 2 1 2 第3行, 第1个数就是3, 然后一直递减到1, 1后面的2, j - i + 1
4 3 2 1

code

#include <iostream>
using namespace std;
const int N = 110;
int a[N][N], n;

int main(){
    while (cin >> n, n){
        
        for (int i = 1; i <= n; i ++ ){
            for (int j = i; j >= 1; j -- ) cout << j << ' ';
            
            for (int j = i + 1; j <= n; j ++ ) cout << j - i + 1 << ' ';
            cout << endl;
        }
        cout << endl;
            
    }
    
    return 0;
}

1432. 棋盘挑战(1月22日)

分析

类似八皇后
题目的意思是cnt > 3就不用输出方案了
所以用cnt记录下个数, 然后判断下cnt

code

#include <iostream>
using namespace std;
const int N = 50;
int col[N], row[N], udg[N], dg[N];
int cnt;
int n;
int path[N];

void dfs(int u){// u表示当前扫描到第u行。
    if (u == n + 1) {
        cnt ++;
        if (cnt > 3) return ;
        for (int i = 1; i <= n; i ++ ) cout << path[i] << ' ';
        cout << endl;
    }
    
    for (int i = 1; i <= n; i ++ )
        if (!col[i] && !udg[i - u + n] && !dg[i + u]){
            path[u] = i;
            col[i] = udg[i - u + n] = dg[i + u] = 1;
            dfs(u + 1);
            col[i] = udg[i - u + n] = dg[i + u] = 0;
            path[u] = 0;
        }
}
int main(){
    cin >> n;
    dfs(1);
    cout << cnt << endl;
    return 0;    
}

1371. 货币系统(1月23日)

分析

完全背包问题(与基础班整数划分问题相同)

code

#include <iostream>
using namespace std;
const int N = 100010;
typedef long long LL;
LL a[N], f[N];
int n, m;

int main(){
    scanf("%d%d", &n, &m);
    
    for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]);
    f[0] = 1;
    
    for (int i = 0; i < n; i ++ )
        for (int j = a[i]; j <= m; j ++ )
            f[j] = (f[j] + f[j - a[i]]);
            
    cout << f[m] << endl;
    
    return 0;
}

1381. 阶乘

分析

先要考虑下怎么去求末尾数字
要求非零数字的话, 那就是意味着我们要先将末尾的0都去掉

假设 n ! n! n!末尾有k个零
要求 n ! n! n!末尾的非零数字, n 1 0 k % 10 \frac{n}{10^k} \% 10 10kn%10

n ! n! n!分解质因数 2 α 5 β p 1 α 2 p 1 α 2 . . . . p m α m 2^{\alpha} 5^{\beta}p_1^{\alpha_2}p_1^{\alpha_2}....p_m^{\alpha_m} 2α5βp1α2p1α2....pmαm
k = m i n ( α , β ) k = min(\alpha, \beta) k=min(α,β)

n ! 1 0 k = 2 α − k 2 β − k p 1 α 2 . . . . p m α m \frac{n!}{10^k} = 2^{\alpha - k}2^{\beta - k}p_1^{\alpha_2}....p_m^{\alpha_m} 10kn!=2αk2βkp1α2....pmαm

我们可以将1, 2, 3, … n的所有质因数分成2,5和其他质因数, 其他质因数直接%10, 对于2, 5统计次数再-k % 10

时间复杂度

首先我们会枚举所有数, 每个数有 l o g ( n ) log(n) log(n)级别的2和5, 因此每个数logn时间复杂度, 总共nlogn

code

#include <iostream>
using namespace std;

int n;


int main(){
    scanf("%d", &n);
    
    int d2 = 0, d5 = 0;

    int res = 1;
    
    
    for (int i = 1; i <= n; i ++ ){
        int x = i;
        while (x % 2 == 0) d2 ++, x /= 2;
        while (x % 5 == 0) d5 ++, x /= 5;
        res = res * x % 10; // 计算其余数乘积%10
    }
    
    int k = min(d2, d5);
    for (int i = 0; i < d2 - k; i ++ ) res = res * 2 % 10;
    for (int i = 0; i < d5 - k; i ++ ) res = res * 5 % 10;
    
    cout << res << endl;
    
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值