1. 程式人生 > >BZOJ 刷題總結(持續更新!!)

BZOJ 刷題總結(持續更新!!)

背景

辣雞的人總要想法自救,便產生了寒假學些新演算法、在Bzoj刷些題的想法。一來為明年省賽做準備…壓力不小;二來寒假也可以有些事情做。

PS

1.不定時更新做題的思路和吐槽
2.按照hzw刷題順序訓練,具體依照BZOJ題表
3.希望寒假能夠至少刷夠100道題目吧,在此立個FLAG,希望別被青島的妖風吹跑

Problem & Solution

BZOJ 1003

題意

給定一個圖的具體資訊,但是在m天內,每一天都可能有一些點不可到達,現需要規劃m天內每天1到 n的最短路線,路線在相鄰的兩天變更會產生固定代價。

Solution

路線的規劃最終方案為一個時間段用一個路徑,若干時間段最後共同構成1-m天,每個時間段內使用的路徑一定是可用點組成的最短路徑。因此我們列舉時間段的起點和終點,然後依次進行spfa,然後用這些數值去做一個線性的dp
複雜度:O((nm)^2)

/**************************************************************
    Problem: 1003
    User: YuHsin
    Language: C++
    Result: Accepted
    Time:64 ms
    Memory:1388 kb
****************************************************************/

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector> #include<queue> using namespace std; #define inf 100000000 const int N = 300; int n, m, k, e; int f[N]; bool vis[N][N], point[N]; struct node { int y, d; }; int dis[N]; bool passed[N]; vector<node> edge[N]; int spfa(int start, int endd) { for(int i = 1; i <= m; i++) { point[i] = true
; for(int j = start; j <= endd; j++) { if (vis[i][j]) point[i] = false; } } if (!point[1] || !point[m]) return inf; queue<int> q; for(int i = 1; i <= m; i++) { passed[i] = false; dis[i] = inf; } dis[1] = 0; passed[1] = true; q.push(1); while(!q.empty()) { int x = q.front(); q.pop(); passed[x] = false; for(int j = 0; j < edge[x].size(); j++) { node e = edge[x][j]; if (!point[e.y]) continue; if (dis[e.y] > dis[x] + e.d) { dis[e.y] = dis[x] + e.d; if (!passed[e.y]) { passed[e.y] = true; q.push(e.y); } } } } return dis[m]; } int main() { scanf("%d%d%d%d", &n, &m, &k, &e); for(int i = 1; i <= e; i++) { int x, y, z; scanf("%d%d%d", &x, &y, &z); edge[x].push_back((node){y, z}); edge[y].push_back((node){x, z}); } int tot; scanf("%d", &tot); for(int i = 1; i <= tot; i++) { int p, l, r; scanf("%d%d%d", &p, &l, &r); for(int j = l; j <= r; j++) vis[p][j] = true; } for(int i = 1; i <= n; i++) f[i] = inf; for(int i = 1; i <= n; i++) { for(int j = 0; j < i; j++) { int res = spfa(j + 1, i); if (res < inf) f[i] = min(f[i], k + f[j] + (i - j) * res); } } printf("%d", f[n] - k); return 0; }

BZOJ 1192

題意

給定正整數n,將其分成儘可能少的若干正整數之和,任意兩個正整數均不可以相同,先詢問整數個數

Solution

按照二次冪一直分就可以

BZOJ 1303

題意

給出1~n的一個排列,統計該排列有多少個長度為奇數的連續子序列的中位數是b

Solution

一個連續序列的中位數是b,那麼這個序列中比b大的數和比b小的數字一定個數相同,因此我們在這個排列中找到b的位置,設為pos。我們用res代表一個區間比b大的數與比b小的數數字個數之差(可能為負值),用cnt1[i]代表在以pos作為右端點的區間中,區間res值為i的區間個數;cnt2[i]代表以pos作為左端點的取件中,區間res值為-i的區間個數,那麼我們可以O(n)的維護cnt1和cnt2
最終ans=∑(cnt1[i]*cnt2[i])

#include<cstdio>
#include<iostream>
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 210000;
int a[N];
int cnt1[N], cnt2[N];
int n, m;
int main()
{
    int base = 100005, pos;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++)  {
        scanf("%d", a + i);
        if (a[i] == m) pos = i;
    }
    int tot = base;
    for(int i = pos; i > 0; i--) {
        if (a[i] < m) tot--;
        else if (a[i] > m) tot++;
        cnt1[tot]++;
    }
    tot = base;
    for(int i = pos; i <= n; i++)  {
        if (a[i] < m) tot++;
        else if (a[i] > m)  tot--;
        cnt2[tot]++;
    }
    ll ans = 0;
    for(int i = base - 100000; i <= base + 100000; i++)  ans += 1LL * cnt1[i] * cnt2[i];
    cout << ans;
    return 0;
}

BZOJ 1191

Solution

每道題對應兩個錦囊,相當於一個男孩對於兩個女孩,直接二分圖匹配就可以

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 2003;
int n, m;
vector<int> edge[N];
bool used[N];
int girl[N];
bool find(int x)  {
    for(int i = 0; i < edge[x].size();i++)  {
        int y = edge[x][i];
        if (used[y]) continue;
        used[y] = true;
        if (girl[y] == 0 || find(girl[y])) {
            girl[y] = x;
            return 1;
        }
    }
    return 0;
}
int main()
{
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++)  {
        int x, y;
        scanf("%d%d", &x, &y);
        edge[i].push_back(++x);
        edge[i].push_back(++y);
    }
    for(int i = 1; i <= m; i++)  {
        for(int i = 1; i <= n; i++) used[i] = false;
        if (!find(i)) {
            printf("%d", i - 1);
            return 0;
        }
    }
    printf("%d", m);
    return 0;
}


BZOJ 1059

題意

給定01矩陣,現可以進行行交換和列交換操作,判斷是否可以通過進行一些操作,使得矩陣一條對角線的元素全為1

Solution

很nb的一道題目
我們先考慮題目的簡化版:假設操作只有行交換。對於同一行的元素,不管如何進行行操作,他們行座標都是相同的,即這些元素中只有一個可以作為對角線的元素,假設這一行i中只有第三列和第五列為1,那麼這一行可以通過行交換將a[3][3] 變成1, 或者a[5][5] 變成1,即第i行可以匹配的列為3和5。按照這個思想,我們按照行和列做二分圖匹配,最終判斷是否存在完美匹配即可
現在考慮列交換,實際上列交換就是相當於把之前的二分圖右面的點交換位置而已,並不影響最終答案的判斷

#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 503;
int n, m;
bool used[N];
int girl[N];
bool a[N][N];
bool find(int x)  {
    for(int j = 1; j <= n; j++) {
        if (!a[x][j] || used[j]) continue;
        used[j] = true;
        if (girl[j] == 0 || find(girl[j])) {
            girl[j] = x;
            return 1;
        }
    }
    return 0;
}
int main()
{
    int T;
    scanf("%d", &T);
    while(T--)  {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) girl[i] = 0;
        for(int i = 1; i <= n; i++) for(int j = 1; j <= n; j++) scanf("%d", &a[i][j]);
        int ans = 0;
        for(int i = 1; i <= n; i++)  {
            for(int j = 1; j <= n; j++) used[j] = false;
            if (find(i)) ans++;
        }
        if (ans == n) puts("Yes");
        else puts("No");
    }
    return 0;
}

BZOJ 1202

題意

給定m個區間和,判斷是否產生矛盾

Solution

矛盾的題目很容易想到並查集
我的思路很奇葩,首先將這些區間按照右端點排序,使用bool 的f[i][j]代表是否已知[j, i]的區間和,列舉區間[l,r]時,先判斷是否原本就已知該區間的和,判斷是否矛盾,然後將f[r][l]標記上,注意我們在已知[l,r]的區間和後,實際上f[l-1]為true的那些左端點,也可以被更新到f[r]中,更新時不要忘記判斷矛盾
複雜度O(Mlogn+mn)
正常思路:帶權並查集
每個集合代表一個連續的區間,f[i]代表所述集合,g[i]代表從i節點到所在樹root的區間和,合併的時候維護即可

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 200;
int value[N][N];
int n, m;
int inf = 210000000;
struct node {
    int l, r, w;
} a[3000];
bool cmp(node a, node b)  {
    if (a.r == b.r)  return a.l < b.l;
    return a.r < b.r;
}
bool Cal() {
    for(int i = 1; i <= m; i++) {
        int l = a[i].l, r = a[i].r, w = a[i].w;
        if (value[r][l] != inf && value[r][l] != w)  return false;
        value[r][l] = w;
        for(int j = 1; j <= l - 1; j++) if (value[l - 1][j] != inf) {
            if (value[r][j] != inf && value[r][j] != value[l - 1][j] + value[r][l]) return false;
            value[r][j] = value[l - 1][j] + value[r][l];
        }
    }
    return true;
}
int main()  {
    int T;
    scanf("%d", &T);
    while(T--)  {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++)  for(int j = 1; j <= i; j++) value[i][j]  = inf;
        for(int i = 1; i <= m; i++)  scanf("%d%d%d", &a[i].l, &a[i].r, &a[i].w);
        sort(a + 1, a + m + 1, cmp);
        if (Cal())  puts("true");  else puts("false");
    }
    return 0;
}
// 並查集
#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<algorithm>
using namespace std;
const int N = 200;
int n, m;
int f[N], g[N];
int find(int x)  {
    if (f[x] == x) return x;
    int t = find(f[x]);
    g[x] += g[f[x]];
    return f[x] = t;
}
bool flag;
bool unio(int x, int y, int z)  {
    int xx = find(x), yy = find(y);
    if (xx !=  yy)  {
        g[xx] = -g[x] + z + g[y];
        f[xx] = yy;
    }
    else  if (g[x] - g[y] != z) flag = false;
}
int main()  {
    int T;
    scanf("%d", &T);
    while(T--)  {
        scanf("%d%d", &n, &m);
        flag = true;
        for(int i = 0; i <= n; i++) f[i] = i, g[i] = 0;
        for(int i = 1; i <= m; i++) {
            int x, y, z;
            scanf("%d%d%d", &x, &y, &z);
            unio(x - 1, y, z);
        }
        if (flag)  puts("true");  else puts("false");
    }
    return 0;
}

BZOJ 3223

題意

平衡樹的裸題

Solution

splay模板即可

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
#define N 1000007
int ch[N][2], f[N], size[N], cnt[N], key[N];
int sz, root;
inline void clear(int x)  {
    ch[x][0] = ch[x][1] = f[x] = size[x] = cnt[x] = key[x] = 0;
}
inline bool get(int x)  {
    return ch[f[x]][1] == x;
}
inline void update(int x)  {
    if (x)  {
        size[x] = cnt[x];
        if (ch[x][0]) size[x] += size[ch[x][0]];
        if (ch[x][1]) size[x] += size[ch[x][1]];
    }
}
inline void rotate(int x)  {
    int old = f[x], oldf = f[old], whichx = get(x);
    ch[old][whichx] = ch[x][whichx^1]; f[ch[old][whichx]] = old;
    ch[x][whichx^1] = old; f[old] = x;
    f[x] =  oldf;
    if (f[x]) ch[oldf][ch[oldf][1] == old] = x;
    update(old); update(x);
}
inline void splay(int x)  {
    for(int fa; (fa = f[x]); rotate(x))
        if (f[fa]) rotate((get(x) == get(fa)) ? fa : x);
     root = x;
}
inline void insert(int x)  {
    if (root == 0)  {
        sz++; ch[sz][0] =  ch[sz][1] = f[sz] = 0;
        size[sz] = cnt[sz] = 1;
        key[sz] = x; root = sz; return;
    }
    int now = root, fa = 0;
    while(true)  {
        if (x == key[now]) {
            cnt[now]++; update(now); update(fa); splay(now); break;
        }
        fa = now;
        now = ch[now][x > key[now]];
        if (now == 0)  {
            sz++; ch[sz][0] = ch[sz][1] = 0;
            f[sz] = fa; key[sz] = x;
            size[sz] = cnt[sz] = 1;
            ch[fa][key[fa]<x] = sz;
            update(fa); splay(sz); break;
        }
    }
}
inline int find(int x)  {
    int now = root, ans = 0;
    while(true)  {
        if (x < key[now]) now = ch[now][0];
        else {
            ans += (ch[now][0]?size[ch[now][0]]:0);
            if (x == key[now]) {  splay(now); return ans + 1;  }
            ans += cnt[now];
            now = ch[now][1];
        }
    }
}
inline int findx(int x)  {
    int now = root;
    while(true)  {
        if (ch[now][0] && x <= size[ch[now][0]]) now = ch[now][0];
        else {
            int temp = cnt[now] + (ch[now][0]?size[ch[now][0]]:0);
            if (x <= temp) return key[now];
            x -= temp; now = ch[now][1];
        }
    }
}
inline int pre()  {
    int now = ch[root][0];
    while(ch[now][1]) now = ch[now][1];
    return now;
}
inline int nxt()  {
    int now = ch[root][1];
    while(ch[now][0]) now = ch[now][0];
    return now;
}
inline void del(int x)  {
    int whatever = find(x);
    if (cnt[root] > 1) {
        cnt[root]--; update(root); return;
    }
    if (!ch[root][0] && !ch[root][1]) {  clear(root); root = 0; return;  }
    if (!ch[root][0])  {
        int oldroot = root; root = ch[root][1]; f[root] = 0;
        clear(oldroot); return;
    }
    if (!ch[root][1])  {
        int oldroot = root;  root = ch[root][0]; f[root] = 0;
        clear(oldroot);  return ;
    }
    int leftbig = pre(), oldroot =  root;
    splay(leftbig);
    f[ch[oldroot][1]] = root;
    ch[root][1] = ch[oldroot][1];
    clear(oldroot);
    update(root); return;
}
int main(){
    int n,opt,x;
    scanf("%d",&n);
    for (int i=1;i<=n;++i){
        scanf("%d%d",&opt,&x);
        switch(opt){
            case 1: insert(x); break;
            case 2: del(x); break;
            case 3: printf("%d\n",find(x)); break;
            case 4: printf("%d\n",findx(x)); break;
            case 5: insert(x); printf("%d\n",key[pre()]); del(x); break;
            case 6: insert(x); printf("%d\n",key[nxt()]); del(x); break;
        }
    }
    return 0;
}

2018.03.04
隊伍發生變更,原來的一名隊員換成了學姐,寒假完成了預期的46%…..

BZOJ 1051

[Solution]
我們將“A認為B是受歡迎的”處理為“A->B連邊”,然後我們對此有向圖進行Tarjan縮點,如果存在多於一個出度為0的強連通分量,那麼無解;否則,出度為零的強聯通分量點的個數即為答案。

BZOJ 1588

[Solution]
可以直接用splay裸著做,或者通過set實現;通過set內部的lower_bound實現即可

BZOJ 1208

[Solution]
splay模板題

BZOJ 3224

[Solution]
splay模板題

BZOJ 1084

[Solution]
注意m很小,因此我們用dp[i][j]代表第一列的前i行和第二列的前j行的最優解
dp[ i ] [ j ] = max{ dp[i][j-1], dp[i-1][j],dp[k][j] + sum[1][k+1]~sum[1][i] (i > j, 0 <=k < i),
dp[k][k] + sum[1][k+1 ~i]+sum[2][k+1~i](i=j, 0 < k < i ),
dp[i][k] + sum[2][k+1~j] (i < j, 0 <=k < j) }
複雜度:O(N* N* N *K)

BZOJ 1491

[Solution]
我們令dp[i] [j] 代表i到j最短路徑數,可以通過n邊Dijlstra處理出來,然後通過N^3的複雜度處理出答案即可

BZOJ 1295

把障礙數作為距離跑spfa,將dis<=T的點的歐幾里得距離update到ans中

bzoj 1085

暴力的時候增加一個估價函式,即使用最優性剪枝