暑假N天樂【比賽篇】 —— 2019牛客暑期多校訓練營(第二場)

最近幾天都沒寫博客,真是沒什麼時間寫了,專題卡着,一週四場比賽,場場爆零,補題都補傻了。第一場還差兩題可能今天補掉吧,昨天的杭電也是徹底沒動,感受...很煩html

第二場牛客斷斷續續也是補了幾天...大概一天也就兩題這樣,而後補了六題感受差很少了,就先放上來好了。node

如下題解包括:\(A \ \ \ D \ \ \ E \ \ \ F \ \ \ H \ \ \ J\)ios

比賽地址: https://ac.nowcoder.com/acm/contest/882#question算法

【A】 Eddy Walker 數學

題目極長,賽中看了幾眼讀不下去了,而後就放掉了。問別人經過的全是暴力找規律...數組

給定圓上有 n 個點,初始點 0,每次會向左或向右移動一步(等可能),若是某一時刻全部點均被至少訪問過一次則中止移動,問最終停留在 m 點的機率。ui

\(m \neq 0\)\(n \neq 1\),則 \(ans = \frac{1}{n-1}\)。emmm公式咋獲得的建議去博客 DeaphetS 看,我懶得敲了....而後答案就是再求個逆元就完事了。spa

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

ll q_pow(ll a, ll b) {
    ll res = 1;
    while(b) {
        if(b & 1) {
            res = res * a % mod;
        }
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int main() {
    int t;
    scanf("%d", &t);
    ll ans = 1;
    while(t--) {
        int n, m;
        scanf("%d%d", &n, &m);
        if(n == 1) {
            printf("%lld\n", ans);
            continue;
        }
        if(m == 0){
            ans = 0;
        }
        else {
            ans = ans * q_pow((long long)n-1, (long long)mod-2) % mod;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

【D】 Kth Minimum Clique 優先隊列BFS+狀壓

給定一個有 n 個頂點的無向圖,求它的第 K 小徹底子圖(團)。debug

反着推,最小團就是空集,不斷向空集里加點,從而找到第 K 小團。code

採用優先隊列,把權值小的團出隊,拿去拓展其餘狀態。爲了避免重複加點,須要每次在當前狀態的已選中的點中下標最大的點後面拓展,這樣就能夠把全部點都遍歷一次了。htm

用 bitset 來保存點鏈接狀態能夠直接判斷該點是否與團的每一個點相連。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 100+5;

ll w[maxn];
char a[maxn][maxn];
bitset<maxn> mp[maxn];

struct G {
    bitset<maxn> st;
    ll sum;
    bool operator < (const G &x) const {
        return sum > x.sum;
    }
};

ll bfs(int n, int k) {
    priority_queue<G> q;
    G temp;
    temp.st.reset();    // clear
    temp.sum = 0;
    q.push(temp);
    while(!q.empty()) {
        G u = q.top();
        q.pop();
        k --;
        // cout << u.st << endl;
        // cout << u.sum <<endl;
        if(k == 0) {
            return u.sum;
        }
        int pos = 0;
        for(int i = 0; i < n; i++) {
            if(u.st[i])
                pos = i+1;
        }
        for(int i = pos; i < n; i++) {
            if(u.st[i] == 0) {
                if((u.st & mp[i]) == u.st) {
                    u.st[i] = 1;
                    u.sum += w[i];
                    q.push(u);
                    u.st[i] = 0;
                    u.sum -= w[i];
                }
            }
        }
    }
    return -1;
}

int main() {
    int n, k;
    scanf("%d%d", &n, &k);
    for(int i = 0; i < n; i++) {
        scanf("%lld", &w[i]);
    }
    for(int i = 0; i < n; i++) {
        scanf("%s", a[i]);
    }
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < n; j++) {
            if(a[i][j] == '1') {
                mp[i].set(j);   // mp[i][j] = 1
            }
        }
    }
    printf("%lld\n", bfs(n, k));
    return 0;
}

【E】 MAZE 線段樹+矩陣乘法

給定一個 n*m 的迷宮,0表示能走的路,1表示不能。每次能夠向左、向右、向下移動一格且不能回頭。有 q 次操做,1表示把 [x, y] 位置進行翻轉(0變一、1變0),2表示查詢 「從 [1, x] 到 [n, y]的方案數」。【\(n \leq 5e四、m \leq 10\)

首先,看到這個取值範圍就知道這題確定怪怪的對吧。而後?我也不會,如下題解來自 [http://www.javashuo.com/article/p-tnbxlama-ed.html] 。(http://www.javashuo.com/article/p-tnbxlama-ed.html)

\(f(i,j)\) 爲走到 \((i,j)\) 的方案數,且第 \(i\) 行裏包含點 \((i,j)\) 的區間爲 \([l,r]\),則有 \(f(i,j)=\sum^{r}_{k=l} f(i−1,k)\),這裏的 \(k\) 就表明着從前一行的第 \(k\) 列走下來。能夠發現這個轉移方程能夠轉換成一個矩陣形式: \[(f(i,1),f(i,2),...,f(i,m))=(f(i−1,1),f(i−1,2),...,f(i−1,m))*A\]

其中 \(A\) 爲狀態轉移矩陣。求從第 \(i−1\) 行到第 \(i\) 行的轉移矩陣能夠用 \(o(m^2)\) 的時間複雜度來實現的。而最後一行的答案就是第一行的狀態矩陣乘上這 \(n\) 行轉移矩陣的乘積。在本題中,因爲給出了起點和終點,因此若設這 \(n\) 行轉移矩陣的乘積爲\(A\),則答案就是 \(A(a,b)\)。用線段樹維護每行的矩陣以及區間的矩陣乘積便可。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;

const int maxn = 5e4+5;
const int maxm = 10+5;

int n, m, q;
int a[maxn][maxm];

struct mat {
    ll a[maxm][maxm];
    inline mat operator * (const mat &x) const {
        mat temp;
        memset(temp.a, 0, sizeof(temp.a));
        for(int i = 1; i <= m; i++) {
            for(int j = 1; j <= m; j++) {
                for(int k = 1; k <= m; k++) {
                    temp.a[i][j] += 1ll * a[i][k] * x.a[k][j] % mod;
                    temp.a[i][j] = temp.a[i][j] % mod;
                }
            }
        }
        return temp;
    }
}T[maxn << 2];

void build(int l, int r, int rt) {
    if(l == r) {
        memset(T[rt].a, 0, sizeof(T[rt].a));
        for(int i = 1; i <= m; i++) {
            int k = i;
            while(k >= 1 && a[l][k] == 0) {
                T[rt].a[i][k] = 1;
                k--;
            }
            k = i;
            while(k <= m && a[l][k] == 0) {
                T[rt].a[i][k] = 1;
                k++;
            }
        }
        return ;
    }
    int mid = (l+r) >> 1;
    build(l, mid, 2*rt);
    build(mid+1, r, 2*rt+1);
    T[rt] = T[2*rt] * T[2*rt+1];
}

void update(int l, int r, int rt, int x) {
    if(l == r) {
        memset(T[rt].a, 0, sizeof(T[rt].a));
        for(int i = 1; i <= m; i++) {
            int k = i;
            while(k >= 1 && a[l][k] == 0) {
                T[rt].a[i][k] = 1;
                k--;
            }
            k = i;
            while(k <= m && a[l][k] == 0) {
                T[rt].a[i][k] = 1;
                k++;
            }
        }
        return ;        
    }
    int mid = (l+r) >> 1;
    if(x <= mid) {
        update(l, mid, 2*rt, x);
    }
    else {
        update(mid+1, r, 2*rt+1, x);
    }
    T[rt] = T[2*rt] * T[2*rt+1];
}

int main() {
    scanf("%d%d%d", &n, &m, &q);
    for(int i = 1; i <= n; i++) {
        char s[15];
        scanf("%s", s+1);
        for(int j = 1; j <= m; j++) {
            a[i][j] = s[j]-'0';
        }
    }
    build(1, n, 1);
    while(q--) {
        int f, x, y;
        scanf("%d%d%d", &f, &x, &y);
        if(f == 1) {
            if(a[x][y] == 0) {
                a[x][y] = 1;
            }
            else {
                a[x][y] = 0;
            }
            update(1, n, 1, x);
        }
        else {
            printf("%lld\n", T[1].a[x][y]);
        }
    }
    return 0;
}

【F】 Partition problem 暴力搜索

三我的在那邊互相否認,結果沒一個複雜度算對的 ······

有 2*n 我的要平均分紅兩隊,給定 \(v[i, j]\) 表示 \(i\)\(j\) 在不一樣隊伍的「競爭值」。問最大的競爭值是多少。

爆搜,\(C^{14}_{28}*28 = 1123264800\) (應該沒算錯)。固然加了一點點的剪枝,不過好像真的很暴力就是了。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
 
const int maxn = 35;
 
int n;
int a[maxn][maxn];
ll sum[maxn] = {0};
int choose[maxn] = {0};
ll ans = 0;
 
void dfs(int now, int cnt, ll temp) {
    if(cnt*2 == n) {
        ans = max(ans, temp);
        return ;
    }
    for(int i = now+1; i <= n; i++) {
        if(choose[i]) {
            continue;
        }
        choose[cnt+1] = i;
        ll x = temp;
        for(int j = 1; j <= cnt; j++) {
            x = x - 2ll*a[i][choose[j]];
        }
        x = x + sum[i];
        dfs(i, cnt+1, x);
        choose[cnt+1] = 0;
    }
}
 
int main() {
    scanf("%d", &n);
    n *= 2;
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            scanf("%d", &a[i][j]);
            sum[i] += 1ll*a[i][j];
        }
    }
    choose[1] = 1;
    dfs(1, 1, sum[1]);
    printf("%lld\n", ans);
    return 0;
}

【H】 Second Large Rectangle 單調棧

我也不知道爲什麼比賽中就死機了,對着一個假算法debug到死 ······

得定一個由 01 構成的矩陣,求這個矩陣裏徹底由 1 構成的 第二大矩形。

其實就是維護每一點上方的連續 1 的數量,而後和以前一列的高度進行比較,以前的大就不能用以前的(出隊),而後計算 3 次可能的矩形面積。而後...就沒有而後了。以前本身的假算法卡死在了去重...我也不知道腦子爲啥抽了。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
 
const int maxn = 1e3+5;
 
char a[maxn][maxn];
int num[maxn][maxn];
 
struct node {
    int h, w;
};
 
int main() {
    // fopen("in.txt", "r", stdin);
    // fopen("out.txt", "w", stdout);
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
        scanf("%s", a[i]+1);
    }
    int MAX = 0, ans = 0;
    for(int i = 1; i <= n; i++) {
        stack<node> s;
        for(int j = 1; j <= m; j++) {
            if(a[i][j] == '0') {
                num[i][j] = 0;
            }
            else {
                num[i][j] = num[i-1][j]+1;
            }
        }
        for(int j = 1; j <= m+1; j++) {
            int w = 0;
            while(!s.empty() && s.top().h > num[i][j]) {
                int h = s.top().h;
                w += s.top().w;
                s.pop();
                if(h*w >= MAX) {
                    ans = MAX;
                    MAX = h*w;
                }
                else if(h*w > ans) {
                    ans = h*w;
                }
                if((h-1)*w >= MAX) {
                    ans = MAX;
                    MAX = (h-1)*w;
                }
                else if((h-1)*w > ans) {
                    ans = (h-1)*w;
                }
                if(h*(w-1) >= MAX) {
                    ans = MAX;
                    MAX = h*(w-1);
                }
                else if(h*(w-1) > ans) {
                    ans = h*(w-1);
                }       
            }
            s.push(node{num[i][j], w+1});
        }
    }
    printf("%d\n", ans);
    return 0;
}

【J】 Subarray 貪心

固定長度爲 1e9 的字符串只包含 1 和 -1 ,其中有 \(n (\leq 1e6)\) 段由 1 構成且 1 的數量小於 1e7,其他都是 -1。問存在多少個區間 \([l,r]\),使得區間和大於0。

貪心。先預處理對於每一個 1 區間左端和右端分別能夠延伸到哪裏。以後須要從頭至尾依次枚舉,注意須要用 pos 標記以防重複。因爲存在負值因此數組須要翻倍。因爲不存在重複跑一個點,所以複雜度最多也是 1e7 級別。

#include <map>
#include <set>
#include <list>
#include <cmath>
#include <ctime>
#include <deque>
#include <stack>
#include <queue>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <vector>
#include <string>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iomanip>
#include <numeric>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const double PI = acos(-1.0);
const double eps = 1e-6;
const int inf = 1e9;
const int mod = 1e9 + 7;
 
const int maxn = 1e6+5;
 
int l[maxn], r[maxn];
int lmore[maxn], rmore[maxn];
int f[20000005];
 
int main() {
    int n, s;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d%d", &l[i], &r[i]);
    }
    l[0] = r[0] = -1;
    l[n+1] = r[n+1] = inf;
    s = r[1] - l[1] + 1;
    for(int i = 1; i <= n; i++) {
        rmore[i] = min(s, l[i+1]-r[i]-1);   // 多減 1 保證大於 0
        s = s - (l[i+1]-r[i]-1);
        if(s < 0) {
            s = 0;
        }
        s = s + (r[i+1]-l[i+1]+1);
    }
    s = r[n] - l[n] + 1;
    for(int i = n; i >= 1; i--) {
        lmore[i] = min(s, l[i]-r[i-1]-1);
        s = s - (l[i]-r[i-1]-1);
        if(s < 0) {
            s = 0;
        }
        s = s + (r[i-1]-l[i-1]+1);
    }
    s = 10000000;
    f[s] = 1;
    ll ans = 0, temp = 1;
    int pos = 0;
    for(int i = 1; i <= n; i++) {
        for(int j = max(pos, l[i]-lmore[i]); j <= r[i]+rmore[i]; j++) {
            if(j >= l[i] && j <= r[i]) {
                s ++;
                ++f[s];
                temp = temp + f[s];
            }
            else {
                s --;
                ++f[s];
                temp = temp - (f[s+1] - 1);
            }
            ans = ans + (temp - f[s]);
        }
        pos = r[i] + rmore[i] + 1;
    }
    printf("%lld\n", ans);
    return 0;
}
相關文章
相關標籤/搜索