天天看點

寒假每日一題(入門組)Week31208. 翻硬币(1月18日)1532. 找硬币 (1月19日)1341. 十三号星期五(1月20日)754. 平方矩陣 II(1月21日)1432. 棋盤挑戰(1月22日)1371. 貨币系統(1月23日)1381. 階乘

1208. 翻硬币(1月18日)

分析

長度100, 如果用dfs, 每個狀态有2種, 2^100, 必定逾時

需要想額外的方法去做

觀察下一些性質

長度是n的話, 一共可以進行的操作n - 1種, 第1次操作前兩個, 以此類推

寒假每日一題(入門組)Week31208. 翻硬币(1月18日)1532. 找硬币 (1月19日)1341. 十三号星期五(1月20日)754. 平方矩陣 II(1月21日)1432. 棋盤挑戰(1月22日)1371. 貨币系統(1月23日)1381. 階乘

能夠改變第1個位置, 其實隻有1種, 而且發現n - 1種操作有以下性質

  1. 操作順序沒影響
  2. 每個操作最多1次, 操作2次等于沒有操作

可以列下清單

操作清單 1 2 n - 1
能否操作 第1硬币與目标狀态是否相同: 相同1, 不同0

是以第1個狀态是唯一确定的

樣例模拟:

o * o o * o *

* o o o * * o

操作清單 1 2 3 4 5 6
能否操作
  1. 第一個位置不一樣(翻轉)

翻轉前:

o * o o * o *

* o o o * * o

翻轉後:

* o o o * o *

* o o o * * o

操作清單 1 2 3 4 5 6
能否操作 ☑️
  1. 第2個硬币(因為第一個硬币已經操作過, 此時能影響第2個硬币的狀态隻有2), 因為狀态相同, 必然不能做翻轉

翻轉前後:

* o o o * o *

* o o o * * o

操作清單 1 2 3 4 5 6
能否操作 ☑️
  1. 看下第3個硬币, 此時能夠影響第3個硬币的操作隻有3

翻轉前:

* o o o * o *

* o o o * * o

操作清單 1 2 3 4 5 6
能否操作 ☑️

相同不能做

  1. 相同不能做
操作清單 1 2 3 4 5 6
能否操作 ☑️
  1. 相同不能做
操作清單 1 2 3 4 5 6
能否操作 ☑️
  1. 不同, 必須做
操作清單 1 2 3 4 5 6
能否操作 ☑️ ☑️

答案保證一定有解, 是以做完前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’與, 假設沖突

寒假每日一題(入門組)Week31208. 翻硬币(1月18日)1532. 找硬币 (1月19日)1341. 十三号星期五(1月20日)754. 平方矩陣 II(1月21日)1432. 棋盤挑戰(1月22日)1371. 貨币系統(1月23日)1381. 階乘

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.2∗105

另外的話可以枚舉月

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月多少天

寒假每日一題(入門組)Week31208. 翻硬币(1月18日)1532. 找硬币 (1月19日)1341. 十三号星期五(1月20日)754. 平方矩陣 II(1月21日)1432. 棋盤挑戰(1月22日)1371. 貨币系統(1月23日)1381. 階乘

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日)

分析(對角線)

從對角線開始, 分别往右邊/下邊 延伸

寒假每日一題(入門組)Week31208. 翻硬币(1月18日)1532. 找硬币 (1月19日)1341. 十三号星期五(1月20日)754. 平方矩陣 II(1月21日)1432. 棋盤挑戰(1月22日)1371. 貨币系統(1月23日)1381. 階乘

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α2​​p1α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;
}