1. 程式人生 > 實用技巧 >刷穿李煜東藍書(讀書筆記) 更新中

刷穿李煜東藍書(讀書筆記) 更新中

目錄

0x00 基本演算法

0x01 位運算

a^b

題目描述
求 a 的 b 次方對 p 取模的值,其中 0≤a,b≤10^9 , 0<p≤10^9

輸入
三個用空格隔開的整數a,b和p。

輸出
一個整數,表示a^b mod p的值。

樣例輸入
2 3 9

樣例輸出
8

思路
普通求冪時間複雜度為O(b),會TLE
設b的二進位制表示有k位,ci為0或1,則

\[b = \displaystyle\sum_{i=0}^{k-1} c_i * 2^i \]

\[a^b = a^{\displaystyle\sum\limits_{i=0}^{k-1} c_i*2^i} = \prod_{k=0}^{k-1}a^{c_{i}*2^i} = \prod_{k=0}^{k-1}{(a^{2^i})}^{c_i} \]

\[a^{2^i} = a^{2^{i-1}}*a^{2^{i-1}} \]

所以計算k-1次就可以求出答案,時間複雜度優化到O(logb)

還可以用分治的思想

\[a^b = \left\{ \begin{aligned} & a^{\frac b2}*a^{\frac b2} &(b為偶數) \\ & a^{\frac {b - 1}2}*a^{\frac {b-1}2}*a& (b為奇數)\\ \end{aligned} \right. \]

程式碼

非遞迴寫法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quick_power(ll a, ll b, ll p){ // 快速冪
    ll ans = 1 % p;
    while(b){
        if(b & 1) ans = a*ans % p;
        a = a*a % p;
        b >>= 1;
    }
    return ans;
}
int main(){
    
    ll a,b,p;
    cin>>a>>b>>p;
    ll ans = quick_power(a,b,p);
    cout<<ans;
}

遞迴寫法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quick_power(ll a, ll b, ll p){ //快速冪
    if(b == 0) return 1 % p;
    ll res = quick_power(a,b>>1,p)%p;
    if(b&1) return (res * res % p) * a % p; 
    else return  res * res % p;

}
int main(){
    ll a,b,p;
    cin>>a>>b>>p;
    ll ans = quick_power(a, b, p);
    cout<<ans;
}

64位整數乘法

題目描述
求a乘b對p取模的值,其中1≤a,b,p≤1018

輸入
輸入3個long long型整數,a,b,p

輸出
輸出a*b%p的值

樣例輸入
250182048980811753
413715569939057660
133223633696258584

樣例輸出
19308689043391716

思路
與快速冪類似

\[b = \sum\limits_{i=0}^{k-1} c_i*2^i a*b = a*\sum\limits_{i=0}^{k-1} c_i*2^i = \sum\limits_{i=0}^{k-1} c_i*(a*2^i) \]

\[a*2^i = a*2^{i-1}*2 \]

同樣也可以用分治的思想

\[a*b = \left\{ \begin{aligned} & a*{\frac b2} + a*{\frac b2}&(b為偶數) \\ & a*{\frac {b-1}2} + a*{\frac {b-1}2}+a& (b為奇數)\\ \end{aligned} \right. \]

程式碼

非遞迴寫法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quick_mul(ll a, ll b, ll p){ // 快速乘
    ll ans = 0;
    while(b){
        if(b&1) ans = (ans + a) % p;
        a = a*2%p;
        b >>= 1;
    }
    return ans;
}
int main(){
    ll a,b,p;
    cin>>a>>b>>p;
    ll ans = quick_mul(a,b,p);
    cout<<ans;
}

遞迴寫法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quick_mul(ll a, ll b, ll p){ // 快速乘
    if(b == 0) return 0;
    ll res = quick_mul(a,b>>1,p) %p;
    if(b & 1) return ((res+res)%p+a) %p;
    else return (res+res)%p; 
}
int main(){
    ll a,b,p;
    cin>>a>>b>>p;
    ll ans = quick_mul(a,b,p);
    cout<<ans;
}

當然這道題可以python過

print(int(input()) * int(input()) % int(input()))

Raising Modulo Numbers

題目描述
People are different. Some secretly read magazines full of interesting girls' pictures, others create an A-bomb in their cellar, others like using Windows, and some like difficult mathematical games. Latest marketing research shows, that this market segment was so far underestimated and that there is lack of such games. This kind of game was thus included into the KOKODáKH. The rules follow:

Each player chooses two numbers Ai and Bi and writes them on a slip of paper. Others cannot see the numbers. In a given moment all players show their numbers to the others. The goal is to determine the sum of all expressions AiBi from all players including oneself and determine the remainder after division by a given number M. The winner is the one who first determines the correct result. According to the players' experience it is possible to increase the difficulty by choosing higher numbers.

You should write a program that calculates the result and is able to find out who won the game.

輸入
The input consists of Z assignments. The number of them is given by the single positive integer Z appearing on the first line of input. Then the assignements follow. Each assignement begins with line containing an integer M (1 <= M <= 45000). The sum will be divided by this number. Next line contains number of players H (1 <= H <= 45000). Next exactly H lines follow. On each line, there are exactly two numbers Ai and Bi separated by space. Both numbers cannot be equal zero at the same time.

輸出
For each assingnement there is the only one line of output. On this line, there is a number, the result of expression
(A1B1+A2B2+ ... +AH^BH)mod M.

樣例輸入
3
16
4
2 3
3 4
4 5
5 6
36123
1
2374859 3029382
17
1
3 18132

樣例輸出
2
13195
13

思路
套快速冪模板就行

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll quick_power(ll a, ll b, ll p){ 
    ll ans = 1 % p;
    while(b){
        if(b & 1) ans = ans*a%p;
        a = a*a%p;
        b >>= 1;
    }
    return ans;
}
int main(){
    int t;
    cin>>t;
    while(t--){
        ll p;
        cin>>p;
        ll ans = 0;
        int num;
        cin>>num;
        for(int i = 0; i < num; i++){
            ll a,b;
            cin>>a>>b;
            ans = (ans + quick_power(a,b,p)) % p;
        }
        cout<<ans<<endl;
    }
}

最短Hamilton路徑

題目描述
給定一張 n(n≤20) 個點的帶權無向圖,點從 0~n-1 標號,求起點 0 到終點 n-1 的最短Hamilton路徑。 Hamilton路徑的定義是從 0 到 n-1 不重不漏地經過每個點恰好一次。

輸入
第一行一個整數n。

接下來n行每行n個整數,其中第i行第j個整數表示點i到j的距離(一個不超過10^7的正整數,記為a[i,j])。

對於任意的x,y,z,資料保證 a[x,x]=0,a[x,y]=a[y,x] 並且 a[x,y]+a[y,z]>=a[x,z]。

輸出
一個整數,表示最短Hamilton路徑的長度。

樣例輸入
4
0 2 1 3
2 0 2 1
1 2 0 1
3 1 1 0

樣例輸出
4

思路
二進位制狀態壓縮dp
用一個n位的二進位制數表示哪些點被經歷過(該數的第i位為1表示第i個點被經歷過)
dp[i][j] 表示"點被經歷過"的狀態為i,當前位置為j點的最短路徑。
由於當前位置在j,設是從k走到j,則前一狀態一定沒有經歷過j,於是前一狀態的經歷的點可以表示成(i xor (1 << j)),即將i的第j位置成0。
所以狀態轉移方程為

\[dp[i][j] = min(dp[i \; xor\; (i << j)][k] \;+ \; weight[k][j])\; (0<k<n) \]

當然由dp[i][j]的意義可知,i的第j位一定要是1,所以只有(i >> j & 1)和((i xor 1 << j)>> k & 1 )為真才轉移

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int weight[30][30];
int dp[1<<20][30];
int main(){
    int n;
    cin>>n;
    for(int i = 0; i < n; i++){
        for(int j = 0; j < n; j++) cin>>weight[i][j];
    }
    memset(dp,0x3f,sizeof(dp)); //將dp中所有狀態初始化成0x3f3f3f3f(INF)
    dp[1][0] = 0; // 初始狀態
    for(int i = 0; i < 1<<n; i++){
        for(int j = 0; j < n; j++)
            if(i >> j & 1){
                for(int k = 0; k < n; k++) 
                    if(i^(1<<j) >> k & 1){ 
                        dp[i][j] = min(dp[i][j],dp[i^(1<<j)][k]+weight[k][j]);
                    }
            }
    }
    cout<<dp[(1 << n) - 1][n-1]<<endl;
}

起床困難綜合徵

題目描述

背景
21 世紀,許多人得了一種奇怪的病:起床困難綜合症,其臨床表現為:起床難,起床後精神不佳。作為一名青春陽光好少年,atm 一直堅持與起床困難綜合症作鬥爭。通過研究相關文獻,他找到了該病的發病原因:在深邃的太平洋海底中,出現了一條名為 drd 的巨龍,它掌握著睡眠之精髓,能隨意延長大家的睡眠時間。正是由於 drd 的活動,起床困難綜合症愈演愈烈,以驚人的速度在世界上傳播。為了徹底消滅這種病,atm 決定前往海底,消滅這條惡龍。

描述

歷經千辛萬苦,atm 終於來到了 drd 所在的地方,準備與其展開艱苦卓絕的戰鬥。drd 有著十分特殊的技能,他的防禦戰線能夠使用一定的運算來改變他受到的傷害。具體說來,drd 的防禦戰線由 n扇防禦門組成。每扇防禦門包括一個運算op和一個引數t,其中運算一定是OR,XOR,AND中的一種,引數則一定為非負整數。如果還未通過防禦門時攻擊力為x,則其通過這扇防禦門後攻擊力將變為x op t。最終drd 受到的傷害為對方初始攻擊力x依次經過所有n扇防禦門後轉變得到的攻擊力。
由於atm水平有限,他的初始攻擊力只能為0到m之間的一個整數(即他的初始攻擊力只能在0,1,…,m中任選,但在通過防禦門之後的攻擊力不受 m的限制)。為了節省體力,他希望通過選擇合適的初始攻擊力使得他的攻擊能讓 drd 受到最大的傷害,請你幫他計算一下,他的一次攻擊最多能使 drd 受到多少傷害。

輸入
第1行包含2個整數,依次為n,m,表示drd有n扇防禦門,atm的初始攻擊力為0到m之間的整數。接下來n行,依次表示每一扇防禦門。每行包括一個字串op和一個非負整數t,兩者由一個空格隔開,且op在前,t在後,op表示該防禦門所對應的操作, t表示對應的引數。
n<=10^5 ,0<=m<=10^9 ,0<=t<=10^9 ,op一定為OR,XOR,AND 中的一種

輸出
一行一個整數,表示atm的一次攻擊最多使 drd 受到多少傷害。

樣例輸入
3 10
AND 5
OR 6
XOR 7

樣例輸出
1

思路
or , xor , and運算在二進位制下都不會產生進位,所以從高位到低位考慮所選的數x的二進位制表示的每一位就行。列舉第i位選0和選1與後面每個引數的第i位運算。
當且僅當滿足以下2個條件時,第k位選1:
1、 已經填好的數加上 1 << k 不超過m
2、 選1時結果的第k位為1,選0時結果的第k位為0
因為當選0和選1造成結果一樣時,應該優先選擇小的數,保證後面選1儘量滿足條件一。

程式碼

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
pair<string, int> P[100005];
int n,m;
int cal(int bit, int now){
    for(int i = 0; i < n; i++){
        int x = P[i].second >> bit & 1; //取第i個引數的第bit位
        if(P[i].first == "AND") now &= x;
        else if(P[i].first == "XOR") now ^= x;
        else now |= x;
    }
    return now;
}
int main(){
    cin>>n>>m;
    for(int i = 0; i < n; i++){
        cin>>P[i].first>>P[i].second;
    }
    int val = 0, ans = 0; // val維護所選的數,ans位結果
    for(int bit = 30; bit >= 0; bit--){ // 2^31 == 2,147,483,648
        int res0 = cal(bit,0); // x的bit位選0時,結果的bit位的數值
        int res1 = cal(bit,1); // x的bit位選1時,結果的bit位的數值
        if(val + (1<<bit) <= m && res0 == 0 && res1 == 1){
            val += 1 << bit;
            ans += 1 << bit;
        }
        else ans += res0 << bit;
    }
    cout<<ans<<endl;
}