BZOJ 刷题总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Yancy_/article/details/79195508

##背景
辣鸡的人总要想法自救,便产生了寒假学些新算法、在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+1i](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

暴力的时候增加一个估价函数,即使用最优性剪枝

没有更多推荐了,返回首页