2020牛客寒假算法基礎集訓營第二場 解題報告

雷比較多,須要一個一個踩。node

A : 作遊戲

考察點 : 簽到題,是否細心(就是看你可否跳過坑)
坑點  :  注意都用 long long 數據類型(兩個數相加 可能也會爆 int 哦,剛開始就 卡在這了)

Code:ios

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

LL a,b,c,x,y,z;
LL res = 0;

int main(void) {
    scanf("%lld%lld%lld",&a,&b,&c);               // long long
    scanf("%lld%lld%lld",&x,&y,&z);
    res += min(a,y) + min(b,z) + min(c,x);
    cout << res << endl;
    return 0;
}

B : 排數字

考察點 : 字符串,及特判,對某些名詞的概念理解
坑點 : 必定要認真讀題,認真讀題,題中是子串,而不是子序列,子串是連續的,因此 616必定是連續的,好比 61616就是
       兩個,而不是三個,只統計616,不用統計61616,也不能隨意組合,由於是連續的。

Code :函數

#include <map>
#include <set>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

string str;
int n;
LL one,six;

int main(void) {
    scanf("%d",&n);
    cin >> str;
    for(int i = 0; i < str.length(); i ++) { // 只須要判斷 6 和 1 的個數便可
        if(str[i] == '1') one ++;
        if(str[i] == '6') six ++;
    }
    six --;
    if(one == 0 || six < 1) cout << 0 << endl;
    else cout << min(six,one);  // 子串是連續的,子序列能夠是不連續的

    return 0;
}

C:算機率


考察點 : dp ,機率, 同餘定理,模運算下的意義
坑點 : long long ,模的數通常都比較大,因此用 long long 比較保險
最好 + 上一個 MOD 防止出現 負數ui

析題得侃 :

遇到 DP 就歇菜,只能慢慢啃了。
DP 最容易的是隻須要一個轉移方程,最艱難的就是找到這個轉移方程。(通常是題最後問的是什麼咱們就設什麼)
假設 DP[i][j] 表示前 i 道題中 剛好作對 j 道的機率是多少,下一步就會出現兩種狀況:
一、第 i 道是對的,那麼 dp[i][j] = dp[i - 1][j - 1] * p[i];
二、第 i 道是錯的,那麼 dp[i][j] = dp[i - 1][j] * (1 - p[i]);  // 1 - p[i] 是作錯的機率
因此 dp[i][j] = dp[i - 1][j - 1] * p[i] + dp[i - 1][j] * (1 - p[i]);

Code:spa

#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;

const int MOD  = 1e9 + 7;
const int maxn = 2020;

LL dp[maxn][maxn],p[maxn];
int n;

int main(void) {
    scanf("%d",&n);
    for(int i = 1; i <= n; i ++) {
        scanf("%lld",&p[i]);
    }
    dp[0][0] = 1;
    for(int i = 1; i <= n; i ++) {                              // 前 i 道中 0 道正確的機率也就是沒有正確的機率(初始化,爲後面作準備)
        dp[i][0] = dp[i - 1][0] * (MOD + 1 - p[i]) % MOD;   // + 1是爲了防止取到負數 (同餘定理)
    }
    for(int i = 1; i <= n; i ++) {
        for(int j = 1; j <= i; j ++) {
            dp[i][j] =( (dp[i - 1][j - 1] * p[i]) % MOD + dp[i - 1][j] * (MOD + 1 - p[i]) ) % MOD;  // 單個模跟 + 括號 模總體是不同的
        }
    }
    for(int i = 0; i <= n; i ++ ) {
        cout << dp[n][i] << ' ';
    }
    return 0;
}

D : 數三角

考察點 : 數學知識,動手能力
坑點  :  多畫畫就都看出來了,三點共線的必定沒法組成三角形,須要咱們進行特判(太坑了)
         平面內任意三個點均可以組成一個三角形,只有是線段是咱們纔會用三角形定理去判斷。

析題得侃 :

這道題都能看出來,但能作對仍是不太容易的,尤爲對於像我這種渣渣來講,就是動手能力太差,其實看到這道題就應該先想一想
有哪些地方須要特判的(三點共線卡了將近半個小時,最後仍是從別人那裏聽來的,太不容易了)。

Code :3d

#include <map>
#include <set>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const int maxn = 505;

struct node {
    double x,y;
} dot[maxn];
int n;

double a[5];

double num(int x,int y) {
    return (dot[x].x - dot[y].x) * (dot[x].x - dot[y].x) + (dot[x].y - dot[y].y) * (dot[x].y - dot[y].y)  ;
}


int main(void) {
    scanf("%d",&n);
    for(int i = 1; i <= n; i ++) {
        scanf("%lf%lf",&dot[i].x,&dot[i].y);
    }
    LL res = 0;
    for(int i = 1; i <= n; i ++) {
        for(int j = i + 1; j <= n; j ++) {
            for(int k = j + 1; k <= n; k ++) {
                if((dot[k].y - dot[i].y) * (dot[j].x-dot[i].x) - (dot[j].y-dot[i].y)*(dot[k].x-dot[i].x) == 0) continue;
                                // 用斜率來判斷三點是否共線
                a[0] = num(i,j),a[1] = num(i,k),a[2] = num(j,k);
                                //鈍角三角形: 假設 C 是最長邊,需知足 A^2 + B^2 < C^2 或者一個角 > 90度 
                sort(a,a + 3);
                if(a[0] + a[1] < a[2]) res ++;
            }
        }
    }
    printf("%lld\n",res);
    return 0;
}

E : 拿物品


考察點 :數論,思惟
坑點 : 不一樣的兩個數交換位置也算一組(哈哈哈哈哈,這也能夠,服了,仍是想的不夠周到)code

析題得侃:

乍一看,數論呀,直接跳過,結束後看題解,真easy。(仍是能力不濟)
咱們須要將原來的式子簡化一下,而後縮減範圍,就像咱們以前學的,遇到根號開根號,而後觀察這個式子的性質。

Code:blog

#include <map>
#include <set>
#include <queue>
#include <deque>
#include <cmath>
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
int n;

vector<int>num;

int main(void) {
    scanf("%d",&n);
    LL res = 0;
    for(int i = 1; i * i <= n; i ++) {
        if(i  == sqrt(i * i)) {
            num.push_back(i * i);
        }
    }
    for(int i = 0; i < num.size(); i ++) {
        for(int j = 1; j * j <= num[i]; j ++) {
            if(num[i] % j == 0) {
                if(num[i] / j != j) res += 2;      // i 和 j 位置不相等的時候互換位置也算,啊啊啊 
                else res ++;
            }
        }
    }
    cout << res << endl;
    return 0;
}

F : 拿物品


考察點: 貪心(鄰項交換),排序
坑點 : 錯誤的貪心思想,哪一個屬性的值大就選哪一個,這個並非最優結果.
eg: a : 2 200
b : 1000 50
若是咱們按照上述貪心思想,先對 a 排序取最大的顯然不是最優的。排序

析題得侃: 這種類型是貪心得一種典型類型 : 鄰項交換得原則(相鄰得兩個交換並不會影響其餘得值,前提是 兩個數得屬性總體相加,

也有多是總體相乘再排序,這樣會獲得一個最優得結果)(最惋惜的是我前幾天剛作過一道相似得題目,可是當時沒有總結
        慘痛得教訓,學到得知識必定要及時整理和複習)

相關題型:

耍雜技的牛
國王遊戲
Code:遊戲

#include <map>
#include <set>
#include <queue>
#include <deque> 
#include <cmath> 
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const int maxn = 2e5 + 10;
struct node {
    LL a,b,pos;
}gooda[maxn];

int vis[maxn];

int n;

bool cmp1(node a,node b) {
    return a.a > b.a;
}

int main(void) {
    scanf("%d",&n);
    for(int i = 1; i <= n; i ++) {
        scanf("%lld",&gooda[i].a);
    }
    for(int i = 1; i <= n; i ++) {
        scanf("%lld",&gooda[i].b);
        gooda[i].a += gooda[i].b;
        gooda[i].pos = i;
    }
    sort(gooda + 1,gooda + 1 + n,cmp1);
    vector<LL>a,b;
    for(int i = 1; i <= n; i ++) {
        if(i % 2 == 1) a.push_back(gooda[i].pos);
        else b.push_back(gooda[i].pos);
    }
    for(int i = 0; i < a.size(); i ++) {
        cout << a[i] << " ";
    }
    cout << endl;
    for(int i = 0; i < b.size(); i ++) {
        cout << b[i] << " ";
    }
    cout << endl;
    return 0;
}

G : 判正誤

考察點 : 快速冪,取模
坑點  :  換幾個模試試 MOD 1e9 + 7 有可能過不了, MOD % 1e9 + 8 就過了(哈哈哈哈,服了)

析題得侃 :

這麼大次方,確定選 快速冪,又有這麼大的數,得取模呀(雖然題目中沒有這樣要求,可是咱們能夠這樣嘗試一下)

Code :

#include <map>
#include <set>
#include <queue>
#include <deque> 
#include <cmath> 
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int MOD = 1e9 + 8;
typedef long long LL;

LL a,b,c,d,e,f,g;

LL quick_mod(LL a,LL b) {
    LL res = 1;
    while(b) {
        if(b & 1) res = res % MOD * a % MOD;
        a = a % MOD * a % MOD; 
        b >>= 1;
    }
    return res;
} 

int main(void) {
    int t;
    scanf("%d",&t);
    while(t --) {
        scanf("%lld%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&e,&f,&g);
        LL ans = quick_mod(a,d) % MOD + quick_mod(b,e) % MOD + quick_mod(c,f) % MOD ;
        if(ans % MOD == g  % MOD) {
            cout << "Yes" << endl;
        } else {
            cout << "No" << endl;
        }
    }
    return 0;
    
    
}

H : 施魔法

考察點 : DP,思惟,推理
坑點   : 遇到 DP 就涼涼

Code : 待補(目前看題解還沒搞透徹)

I :建通道


考察點 : Tree,位運算,思惟
坑點 : 能想到就完事了,去重

析題得侃 :

初看這道題,這不是最小生成樹的模板嗎,可是定睛一看,發現沒邊權呀,這不是歇菜了。
後來在一位大佬的指導下,終於將其看懂,理解,而且AC。
這道題仍是蠻有特色的 : 求 異或(二進制位下相同 取 0 不一樣取 1),並且仍是 lowbit(異或)下的最小值。
咱們知道兩個不一樣的數在二進制下必定存在某一位是不一樣的(一個是 0 ,一個是 1),那麼在咱們的全部數中,也
必定存在在更低的位置(靠右的)有兩個不一樣的二進制位,咱們將這兩個不一樣的二進制位取異或,獲得的必定是 1 ,
再lowbit那麼這個確定就是這兩個點的之間的最小邊權(咱們說了是在最低的位置),而後咱們知道全部數均可以
經過二進制表示出來,那麼這個數的二進制位與最低位的同一個位置的數必定不是 0 就是 1 ,咱們知道最小邊
權的一端存在一個 0 ,另外一端存在一個 1,那麼咱們只要讓這個數的的該二進制位是 0 就和 1 相連,是 1 就和
0 相連,由於異或。到這裏,就都是最小邊權了,最後的最小花費就是 (m - 1) * 最小邊權。

lowbit(x) = x & (-x); 
lowbit () 取得的值 1,2,4,8........(因此最多隻須要枚舉30次)(能夠寫個數的二進制位模擬一下)
#include <map>
#include <set>
#include <queue>
#include <deque> 
#include <cmath> 
#include <vector>
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

typedef long long LL;
const int maxn = 2e5 + 10;
int n,value;
bool Exit[31][2];
set<int>sets; 
set<int>::iterator it;

int main(void) {
    scanf("%d",&n);
    for(int i = 1; i <= n; i ++) {
        scanf("%d",&value);
        sets.insert(value);     // 題中沒有說不會有重複的值,須要去重,這也是一個坑點
    }
    for(it = sets.begin(); it != sets.end(); it ++) {  // 將全部數的二進制位進行標記
        for(int i = 0; i <= 30; i ++) {
            int value = *it;
            if((value >> i) & 1) Exit[i][1] = true;
            else Exit[i][0] = true;
        }
    }
    LL res = 0;
    for(int i = 0; i <= 30; i ++ ) {
        if(Exit[i][0] && Exit[i][1]) {           // 尋找最小的異或的位置
            res = (sets.size() - 1) * (1 << i);
            break;
        }
    }
    cout << res << endl;
    return 0;
}

J :求函數

考察點 : 線段樹的基本操做
坑點 : 代碼比較長,注意細節

析題得侃 :

線段樹咱們確定要找到須要合併得東西,例如求區間和咱們須要父節點存儲得是兩個數得和,求區間最大值咱們須要
往上推送的是兩個數的最大值,那這個須要往上pushup的是啥呢 ?
咱們發現 : f(L) = kL + b
           因此 f(L1) = f(f(L)) = k(k * L + b) + b = k * k * L + k * b + b;
因此咱們pushup 的應該是 k * k + k * b + b (K, b 都是已知的)(能夠分紅兩部分)
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#include <algorithm>

#define int long long

using namespace std;

const int maxn = 2e5 + 10;
const int MOD = 1e9 + 7;

typedef long long LL;

struct node {
    int l,r,k,b;
} tree[maxn << 2];

int a[maxn],b[maxn];
int n,m;

int ans,res;

signed main(void) {
    void build(int u,int l,int r);
    void update(int u,int x);
    void query(int u,int l,int r);
    scanf("%lld%lld",&n,&m);
    for(int i = 1; i <= n; i ++) {
        scanf("%lld",&a[i]);
    }
    for(int i = 1; i <= n; i ++) {
        scanf("%lld",&b[i]);
    }
    build(1,1,n);
    while(m --) {
        int op,pos,l,r;
        scanf("%lld",&op);
        if(op == 1) {
            scanf("%lld",&pos);
            update(1,pos);
        } else {
            ans = 0,res = 1;                     // 這裏咱們設置成全局變量,就不用最後去合併了,最好仍是合併吧,這樣寫總感受有點不妥
            scanf("%lld%lld",&l,&r);
            query(1,l,r);
            cout << (ans + res) % MOD << endl;
        }
    }
    return 0;
}

void pushup(int u) {
    tree[u].k = (tree[u << 1].k * tree[u << 1 | 1].k) % MOD;
    tree[u].b = (tree[u << 1 | 1].k * tree[u << 1].b + tree[u << 1 | 1].b ) % MOD;
    return ;
}

void build(int u,int l,int r) {
    if(l == r) {
        tree[u].l = l;
        tree[u].r = r;
        tree[u].k = a[l];
        tree[u].b = b[l];
        return ;
    }
    tree[u].l = l;
    tree[u].r = r;
    int mid = l + r >> 1;
    build(u << 1,l,mid);
    build(u << 1 | 1,mid + 1,r);
    pushup(u);
    return ;
}


void update(int u,int x) {
    if(tree[u].l == tree[u].r) {
        scanf("%lld%lld",&tree[u].k,&tree[u].b);
        return ;
    }
    int mid = tree[u].l + tree[u].r >> 1;
    if(x <= mid) update(u << 1,x);
    else update(u << 1 | 1,x);
    pushup(u);

}

void query(int u,int l,int r) {
    if(tree[u].l >= l && tree[u].r <= r) {
        res = (res * tree[u].k) % MOD;
        ans = (ans * tree[u].k + tree[u].b) % MOD;
        return ;
    }
    int mid = tree[u].l + tree[u].r >> 1;
    if(l <= mid) query(u << 1,l,r);                    // 這裏判斷條件必定不要寫反,寫反可能會發生段錯誤,調了一個多小時,最後仍是對照着別人的纔看出來
    if(r > mid) query(u << 1 | 1,l,r);

    return ;
}
相關文章
相關標籤/搜索