1. 程式人生 > 實用技巧 >python之生成器

python之生成器

目錄

A. Robot Program

題意:

一個機器人要從\((0,0)\)位置走到\((x,y)\)位置,每次可以進行5種操作,分別是向上下左右方向走一格,或者是待在原地。機器人不能連續進行同樣的兩次操作(例如不能連續向右走兩次,但是可以走一次,停在原地一次,然後再走一次),問最少進行多少次操作可以到達目的地

思路:

\(x=y\)或者x與y的差只有1時,可以一直走,不需要停,否則必須要在中途停下來。

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
int t, x, y;
int main(){
    cin>>t;
    while(t--){
        cin >> x >> y;
        if(abs(x-y)<=1){
            cout << x + y << endl;
        }
        else{
            cout << min(x, y) * 2 + 1 + 2 * (max(x, y) - (min(x, y) + 1)) << endl;
        }
    }
    return 0;
}

B. Toy Blocks

題意:

有 n 個盒子,每個盒子有 \(a_i\) 個糖果。現在你可以向一些盒子中新增糖果,使得任選一個盒子,將其中的糖果分到其餘 n-1 個盒子中,每個盒子的糖果數量相等。問最少需要新增多少糖。

思路

直接暴力做,對於每個點,判斷一下把其他糖果填平的糖果即可,這裡的填平是需要將其他的糖果補充到他們的最大值,然後再看剩下的糖果數量,判斷是否還需要再填一層

#include<bits/stdc++.h>

using namespace std;

const int N = 1e5 + 5;
int t, n, x, a[N];
long long anss;
long long sum,maxn=-1,minn=0x3f3f3f3f;

int main(){
    cin >> t;
    while(t--){
        cin >> n;
        sum = 0;
        anss = 0;
        for (int i = 0; i < n;i++){
            cin >>a[i];
            sum += a[i];
        }
        sort(a, a + n);
        maxn = a[n - 1];
        minn = a[0];
        for (int i = 0; i < n;i++){
            minn = a[i];
            if(i==n-1){
                maxn = a[i - 1];
            }
            if (maxn * (n - 1) - (sum - minn) > minn)
            {
               anss=max(anss, maxn * (n - 1) - (sum - minn) - minn );
            }
            else if (maxn * (n - 1) - (sum - minn) > minn)
            {
                anss = max(anss, (long long)0);
            }
            else{
            if(((minn-(maxn*(n-1)-(sum-minn)))%(n-1)==0)){
                anss = max(anss, (long long)0);
            }
            else{
                anss = max(anss, (n - 1) - (minn - (maxn * (n - 1) - (sum - minn))) % (n - 1));
            }
        }
            
        }
        cout << anss << endl;
    }
    return 0;
}

C. Two Brackets

題意:

計算有多少配對的括號

思路:

記錄一下前面出現的左括號lsum,當遇到右括號時,如果lsum大於0,就將lsum--,然後答案++

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
int t,lnum1,lnum2,sum;
string s;
int main(){
    cin>>t;
    while(t--){
        cin >> s;
        sum = 0;
        lnum1 = 0;
        lnum2 = 0;
        for (int i = 0;i<s.size();i++){
            if(s[i]=='('){
                lnum1++;
            }
            else if(s[i]==')'){
                if(lnum1){
                    lnum1--;
                    sum++;
                }
            }
            else if(s[i]=='['){
                lnum2++;
            }
            else if(s[i]==']'){
                if(lnum2){
                    lnum2--;
                    sum++;
                }
            }
        }
        cout << sum << endl;
    }
    return 0;
}

D. Radio Towers

題意:

一共0到n+1共n+2個點,每個點上都可以放置或者不放置一個訊號塔,訊號塔的輻射範圍是1-n,每個點只能被一個訊號塔覆蓋,0號和n+1號點不能被覆蓋,問符合條件的放置方法佔全部的多少

思路:

找規律即可,分子為斐波那契數列,分母為\(2^n\)

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
const int mod = 998244353;
typedef long long LL;
LL n,fz,fm,fib[N];
LL get_fz(LL n){
    fib[1] = 1;
    fib[2] = 1;
    for (int i = 2; i <= n;i++){
        fib[i] = ((LL)fib[i - 1] + (LL)fib[i - 2]) % mod;
    }
    return fib[n];
}

LL qmi(LL a, LL k, LL p)   {
    LL res = 1 % p;  // res記錄答案, 模上p是為了防止k為0,p為1的特殊情況
    while(k) { // 只要還有剩下位數
        if (k & 1) res = (LL)res * a % p;  // 判斷最後一位是否為1,如果為1就乘上a,模上p, 乘法時可能爆int,所以變成long long
        k >>= 1;  // 右移一位
        a = (LL) a * a % p;  // 當前a等於上一次的a平方,取模,平方時可能爆int,所以變成long long
    }
    return res;
}

LL get_inv(LL a, LL p) {
    return a % p == 0? -1: qmi(a, p - 2, p);
}

LL get_fm(LL n){
    LL temp = qmi(2,n,mod);
    return get_inv(temp, mod);
}

int main(){
    cin>>n;
    fz = get_fz(n);
    fm = get_fm(n);
    cout << fz * fm % mod << endl;
    return 0;
}

E. Two Editorials

大意:

一共有m個位於[1,n]的區間p,現在有長度為K的區間b和c。設對於區間p[i],定義a[i]為p[i]分別與b,c相交長度的較大值,現在問區間b和c位於何處時,a[i]的和最大

思路:

純暴力肯定不行,所以需要考慮對其進行優化,因為對於區間p,如果它的中點在b和c的中點的左側,那麼一定是對b產生貢獻,反之則是對c產生貢獻,那麼對於每個點i,p個區間都可以分為兩部分,前一部分對b產生貢獻,後一部分對c產生貢獻,所以首先對m個p區間按照中點排序,然後遍歷n,找到對於每個點i的分界點,即\(pre[i]\)代表有多少區間的中點在i的左側。然後跑一遍字首和,\(sum[i][j]\)代表對於位置i,前j個區間可以產生的貢獻,最後暴力列舉區間b和c,算一下兩個區間獲得的貢獻即可。這樣複雜度為\(O(n*m)\)

#include<bits/stdc++.h>

using namespace std;

const int N = 2e3 + 5;
int n, m, k,pre[N],sum[N][N];
struct node
{
    int l, r;
}a[N];

bool cmp(node a,node b){
    return (a.l + a.r < b.l + b.r);
}

int main(){
    cin>>n>>m>>k;
    for (int i = 0; i < m;i++){
        cin >> a[i].l >> a[i].r;    
    }
    sort(a, a + m, cmp);
    int j = 0;
    for (int i = 1; i <= n;i++){
        while(j<m&&(a[j].l+a[j].r<=2*i)){
            j++;
        }
        pre[i] = j-1;
    }
    for (int i = 1; i <= n - k + 1;i++){
        int l = i, r = i + k - 1;   //當前區間的左右範圍
        for (int j = 1; j <= m;j++){
            sum[i][j] = sum[i][j - 1] + max(0, min(a[j - 1].r, r) - max(a[j - 1].l,l )+1);
        }
    }
    int res = 0;
    for (int i = 1; i <= n - k + 1;i++){
        for (int j = i; j <= n - k + 1;j++){
            int mid = (i + j + k - 1) >> 1;
            res = max(res, sum[i][pre[mid] + 1] + sum[j][m] - sum[j][pre[mid] + 1]);
        }
    }
    cout << res << endl;
    return 0;
}

F. Divide Powers

題意:

給出一個數組\(cnt_i\)代表當前有\(cnt_i\)\(2^i\),當\(i>0\)時,可以將一個\(2^i\)變成兩個\(2^{i-1}\),現在又兩個操作,一類是修改\(cnt_i\),另一類是詢問進行多少次修改後會有不少於\(k\)個元素小於等於\(2^x\)

思路:

對於大於\(2^x\)的元素,將其修改到\(2^x\),可以比修改小於\(2^x\)的元素要多出一個,所以優先修改大於\(2^x\)的元素,但是如果修改到\(2^x\)獲得的元素比需要的多,那麼需要判斷是否修改小於\(2^x\)的元素,也就是需要記錄當前已經選擇的元素,讓他們全部修改為\(2^0\)可以獲得的元素個數。

#include<bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
typedef long long LL;
LL n, q, cnt[35],op,a[35];
int main(){
    cin >> n >> q;
    a[0] = 1;
    for (int i = 0; i < n;i++){
        cin >> cnt[i];
        if(i){
            a[i] = a[i - 1] * 2;
        }
    }
    while(q--){
        cin >> op;
        if(op==1){
            LL pos, val;
            cin >> pos >> val;
            cnt[pos] = val;
        }
        else{
            LL x, k,sum=0,res=0;
            cin >> x >> k;
            for (int i = 0; i <= x;i++){
                k -= cnt[i];
                sum += (a[i]-1) * cnt[i];   //記錄當前選擇的還可以多產生多少符合條件的值
            }
            if(k<=0){
                cout << 0 << endl;
                continue;
            }
            for (int i = x + 1; i < n;i++){
                if((a[i-x]*cnt[i])<=k){
                    k -= a[i - x] * cnt[i];
                    res += (a[i - x] - 1) * cnt[i];
                    sum += (a[i] - a[i - x]) * cnt[i];
                }
                else{
                    int p=k/a[i-x];
					k%=a[i-x];
					sum+=p*(a[i]-a[i-x]);
					res+=p*(a[i-x]-1);
					for (int j=i;j>=x;j--)
					{
						if (k<=sum)
						{
							res+=k;
							k=0;
							break;
						} 
						if (a[j-x-1]<=k)
						{
							k-=a[j-x-1];
							sum+=a[j-1]-a[j-x-1];
							res+=a[j-x-1];
						}
						else res++;
					}
                    break;
                }
            }
            if(k<=sum){
                cout << res + k << endl;
            }
            else
            cout << -1 << endl;
        }
    }
        return 0;
}

G. Game On Tree

大意:

Alice 和 Bob 在玩一個遊戲。他們有一棵由 n 個結點組成的樹。一開始,Bob 有 k個卡片,其中第i個卡片位於結點 \(a_i\),在遊戲開始之前,Alice 將在這棵樹的一個結點上放置一個卡片。

這個遊戲由一些回合組成。每個回合都將有以下事件發生(完全按照以下順序):

  1. Alice 可以把她的卡片移到相鄰的結點,或者不移動;
  2. 對於 Bob 的每一張卡片,他可以把這張卡片移到相鄰的結點,或者不移動。注意:每個卡片的選擇都是獨立的。

當 Alice 的卡片與 Bob 的任意一張(或多張)卡片在同一結點時,遊戲結束。(Bob 自己的多張卡片可以置於同一結點上,即使它們的初始位置一定是不同的)。

Alice 希望遊戲回合越多越好,Bob則相反。如果某回合中間遊戲結束(即 Alice 把卡片移到了有 Bob 卡片的結點上),這回合依然算入總回合數。

對於每個結點,計算 Alice 一開始將卡片放在該結點時遊戲將持續的回合數。

思路:

首先利用拓撲排序求出來每個點距離Alice的最近的一個卡片的距離\(dis[i]\),對於Bob來說,必然是想前往\(dis[x]\)較大的點,而對於Alice來說,必然是所有的點都向Bob逼近,得到\(dis[x]\)陣列後,可以從最大的值開始更新周圍的點的答案,但是這樣複雜度會很高,所以引入\(h[i]\)陣列,代表上一次利用i點更新周圍點的答案時,i點距離最近的一個“敵人”的距離,所以只有噹噹前距離最近的"敵人"的距離大於\(h[i]\)時,才利用這個點更新答案,否則不需要更新。

#include<bits/stdc++.h>

using namespace std;

const int N = 2e5 + 5;
int n, m,res[N],dis[N],maxn,h[N];
vector<int> mp[N],D[N];
queue<int>q;

int main(){
    cin >> n;
    for (int i = 1; i < n;i++){
        int u,v;
        cin >> u >> v;
        mp[u].push_back(v);
        mp[v].push_back(u);
        dis[i] = -1;
    }
    dis[n] = -1;
    cin >> m;
    while(m--){
        int x;
        cin >> x;
        dis[x] = 0;
        q.push(x);
    }
    while(!q.empty()){
        int u = q.front();
        q.pop();
        for (int i = 0; i < mp[u].size();i++){
            int v = mp[u][i];
            if(dis[v]>=0)
                continue;
            dis[v] = dis[u] + 1;
            q.push(v);
        }
        D[dis[u]].push_back(u);
        maxn = max(maxn, dis[u]);
    }
    queue<pair<int, int>> q1;
    for (int i = maxn; i > 0;i--){
        for (int j = 0; j < D[i].size();j++){
            int u = D[i][j];
            if(h[u]<dis[u]){
                if(res[u]==0){
                    res[u] = i;
                }
                h[u] = dis[u];
                q1.push({dis[u],u});
            }
            while(!q1.empty()){
                int d = q1.front().first;
                int u = q1.front().second;
                q1.pop();
                if(--d==0)
                    continue;   //更新過來直接是敵人
                for(int j = 0;j<mp[u].size();j++){
                    int v = mp[u][j];
                    int temp = min(d, dis[v]);
                    if(temp>h[v]){
                        if(res[v]==0)
                            res[v] = i;
                        h[v] = temp;
                        q1.push({temp, v});
                    }
                }
            }
        }
    }
    for (int i = 1; i <= n;i++){
        cout << res[i] << ' ';
    }
    cout << endl;
    return 0;
}