2022NOIP A层联测22 极源流体 等差数列 送给好友的礼物 非常困难的压轴题

[最小生成树/坐标系转图论]T1:给出一个矩形长n宽m,1代表黑色,0代表白色,你每次可以选择从上下左右4个方向的一个方向,以及参数K,把从任一个黑色格子出发的这么多长度染成黑色,求最少\(\sum_{}^{}K\)使得所有黑色格子八联通(可以只挨着不覆盖)。(n,m<=700)

考场

发现一次操作实际上是覆盖了一个矩阵空间,4种操作可以等效成只有下和右的操作,但是不知道怎么正确地在确定操作后判断合法,于是挂了-->10pts

暴力35pts(打的好的有79分)

可以处理出b数组表示在合法范围内的位置,差分序列或者预处理矩形覆盖都可以,把任意一个点放入dequeBFS一下,如果能够在合法移动范围内遍历所有黑点就是可以的。然后发现x和y具有单调性,O(n+m)移动一下,\(O(n^3)\)的算法T了。。。
还有一些无关痛痒的剪枝,比如按照x递减,y增,这样如果y不合法就直接break了,因为越x小越不可能合法,\(ans<=y也break\),因为y是增的。

点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define chu printf
#define rint register int
#define ll long long
#define ull unsigned long long
inline ll re() {
    ll x = 0, h = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            h = -1;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * h;
}
const int N = 733;
bool a[N][N], v[N][N];
int b[N][N];
int dx[] = { 0, 0, 1, 1, 1, -1, -1, -1 };
int dy[] = { 1, -1, 0, 1, -1, 0, 1, -1 };
int n, m;
vector<pair<int, int> > g;  //存黑点位置
int spx, spy;
char s[N];
deque<pair<int, int> > q;
inline void get(int x1, int y1, int x2, int y2) {
    // chu("check(%d %d)(%d %d)\n",x1,y1,x2,y2);
    b[x1][y1]++;
    b[x2 + 1][y2 + 1]++;
    b[x1][y2 + 1]--;
    b[x2 + 1][y1]--;
}
inline int Min(int x, int y) { return (x < y) ? (x) : y; }
inline bool check(int S, int H)  //竖着和横着
{
    // chu("Try:%d %d\n",S,H);
    q.clear();
    for (rint i = 0; i <= n; ++i)
        for (rint j = 0; j <= m; ++j) v[i][j] = b[i][j] = 0;

    for (pair<int, int> ele : g) get(ele.first, ele.second, Min(ele.first + S, n), Min(ele.second + H, m));
    for (rint i = 1; i <= n; ++i)
        for (rint j = 1; j <= m; ++j) b[i][j] = b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1] + b[i][j];
    int tot = 0;
    v[spx][spy] = 1;
    q.push_back(make_pair(spx, spy));
    while (!q.empty()) {
        //  chu("out:%d\n",q.front().first);
        int x = q.front().first, y = q.front().second;
        q.pop_front();
        if (a[x][y])
            ++tot;
        if (tot >= g.size())
            return 1;
        for (rint i = 0; i < 8; ++i)
            for (rint j = 0; j < 8; ++j) {
                int nx = x + dx[i], ny = y + dy[i];
                if (nx <= n && nx >= 1 && ny <= m && ny >= 1 && !v[nx][ny] && b[nx][ny]) {
                    v[nx][ny] = 1;
                    q.push_back(make_pair(nx, ny));
                }
            }
    }
    return 0;
}
int main() {
    // freopen("1.in","r",stdin);
    //  freopen("1.out","w",stdout);
    freopen("fluid.in", "r", stdin);
    freopen("fluid.out", "w", stdout);
    n = re(), m = re();
    for (rint i = 1; i <= n; ++i) {
        scanf("%s", s + 1);
        for (rint j = 1; j <= m; ++j) {
            if (s[j] == '1') {
                a[i][j] = 1;
                if (!spx)
                    spx = i, spy = j;
                g.push_back(make_pair(i, j));
            }
        }
    }
    int nas = 1e9;
    for (rint i = n - 1, j = 0; i >= 0; --i) {
        while (j < m && !check(i, j)) ++j;
        if (j == m || i >= nas)
            break;
        nas = min(nas, i + j);
    }
    chu("%d", nas);
    return 0;
}
/*
5 4
1100
1000
0011
0000
0001
*/

正解100pts

上面的常数有点大(BFS?),所以换一个思路,考虑每个黑点向其他黑点连边,代价是\([x,y]\),就是坐标差-1,\(K^2\)条边会T,考虑如果存在\([x,y]->[x+d1,y],[x+d1,y]->[x+d1,y+d2],那么[x,y]->[x+d1,y+d2]就是完全没有必要的边\),所以直接只存对于每个\(black_{[x,y]}\)横坐标差值不同的纵坐标差值最小黑点(其实吧要求从上到下横坐标递减,但是不好操作),连边。对于生成树,设置第一维度x的上限,从小到大添加y直到联通,取min的答案。
注意一些剪枝,比如2维度边权<=0的就直接都放到树里,每次重新设置上界的时候对并查集只撤销已有联通块(注意把边赋值成father,否则路径压缩出问题)。
\(O(n^2logn)\)

点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define chu printf
#define rint register int
#define ll long long
#define ull unsigned long long
inline ll re() {
    ll x = 0, h = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            h = -1;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * h;
}
const int N = 733;
int n, m, id[N][N], tim, f[N * N], cnt, fof, tot;
vector<int> H[N], L[N];
vector<int> dot;
struct edge {
    int u, v, x, y;
} e[N * N * 10];
bool cmp(edge A, edge B) { return (A.x == B.x) ? (A.y < B.y) : (A.x < B.x); }
vector<edge> use[N];
char s[N][N];
inline int father(int x) { return (x == f[x]) ? x : (f[x] = father(f[x])); }
inline void merge(int x, int y) {
    x = father(x), y = father(y);
    if (x == y)
        return;
    f[x] = y;
    --cnt;
}
int main() {
    freopen("fluid.in", "r", stdin);
    freopen("fluid.out", "w", stdout);
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    n = re(), m = re();
    for (rint i = 1; i <= n; ++i) {
        scanf("%s", s[i] + 1);
        for (rint j = 1; j <= m; ++j) {
            if (s[i][j] == '1')
                H[i].push_back(j), L[j].push_back(i);
            id[i][j] = ++tot;
        }
    }
    for (rint i = 1; i <= n; ++i) {
        for (rint x : H[i]) {
            for (rint j = x; j <= m; ++j) {
                if (s[i][j] == '1' && j != x)
                    break;
                int p = upper_bound(L[j].begin(), L[j].end(), i) - L[j].begin();
                if (p >= L[j].size())
                    continue;
                p = L[j][p];
                e[++fof] = (edge){ id[i][x], id[p][j], p - i - 1, j - x - 1 };
            }
            for (rint j = x - 1; j >= 1; --j) {
                if (s[i][j] == '1')
                    break;
                int p = upper_bound(L[j].begin(), L[j].end(), i) - L[j].begin();
                if (p >= L[j].size())
                    continue;
                p = L[j][p];
                e[++fof] = (edge){ id[i][x], id[p][j], p - i - 1, x - j - 1 };
            }
        }
        for (rint j = 0; j + 1 < H[i].size(); ++j)
            e[++fof] = (edge){ id[i][H[i][j]], id[i][H[i][j + 1]], -1, H[i][j + 1] - H[i][j] - 1 };
    }
    sort(e + 1, e + 1 + fof, cmp);
    // for(rint i=1;i<=fof;++i)
    // {
    //     chu("%d %d %d %d\n",e[i].u,e[i].v,e[i].x,e[i].y);
    // }
    for (rint i = 1; i <= tot; ++i) f[i] = i;

    for (rint i = 1; i <= fof; ++i)
        if (e[i].x <= 0 && e[i].y <= 0)
            merge(e[i].u, e[i].v);

    for (rint i = 1; i <= fof; ++i) {
        e[i].u = father(e[i].u), e[i].v = father(e[i].v);
        if (e[i].v == e[i].u)
            continue;
        use[e[i].y + 2].push_back(e[i]);
    }
    for (rint i = 1; i <= n; ++i)
        for (rint j = 1; j <= m; ++j) {
            if (s[i][j] == '1') {
                int fat = father(id[i][j]);
                if (fat == id[i][j])
                    dot.push_back(fat);
            }
        }
    int eafoo = 1e9;
    for (rint i = 0; i <= n; ++i) {
        for (rint ele : dot) f[ele] = ele;
        cnt = dot.size();
        for (rint j = 1; j < m + 2; ++j) {
            for (edge ele : use[j])
                if (ele.x <= i)
                    merge(ele.u, ele.v);
                else
                    break;  // x递增的

            if (cnt == 1) {
                eafoo = min(eafoo, i + max(0, j - 2));
                break;
            }
            if (i + max(0, j - 2) >= eafoo)
                break;
        }
    }
    chu("%d", eafoo);
    return 0;
}
/*
5 4
1100
1000
0011
0000
0001
*/

[DP]T3:给出一棵树,有K个节点上有草莓,2个人同时从1出发一时刻移动1距离,去拿草莓回到1,求最少时间。(n<=415)

正解

只有叶子有草莓有用,有草莓的只有叶子有用,所以等价成把叶子集合分成2份使得max的覆盖路径和最小。
\(f[i][j][k]=t\)表示已经顺次找到i节点,路径和是k,不属于i集合的节点的最近一个是j,j属于的集合的最小价值。dfn序遍历一下就行。\(O(n^3)\)

点击查看代码
// ubsan: undefined
// accoders
#include <bits/stdc++.h>
using namespace std;
#define chu printf
#define rint register short int
#define ll long long
#define ull unsigned long long
inline ll re() {
    ll x = 0, h = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-')
            h = -1;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        x = (x << 1) + (x << 3) + (ch ^ 48);
        ch = getchar();
    }
    return x * h;
}
const short int N = 420;  //但是我只想到了n^2的DP,是不是假了?假了?假了?(真的真的真的)
short int n, K, head[N], tot, fa[N], leaf[N], top[N], mson[N], dep[N], f[N][N][N], Leaf;
short int lca[N][N];
bool up[N];
struct node {
    short int to, nxt;
} e[N << 1];
inline void add_e(short int u, short int v) {
    e[++tot] = (node){ v, head[u] };
    head[u] = tot;
}
inline void dfs(short int x, short int ff) {
    for (rint i = head[x]; i; i = e[i].nxt) {
        short int to = e[i].to;
        if (to == ff)
            continue;
        dfs(to, x);
        up[x] |= up[to];
    }
}
inline void getleaf(short int x, short int ff) {
    bool wson = 1;
    fa[x] = ff;
    for (rint i = head[x]; i; i = e[i].nxt) {
        int to = e[i].to;
        if (to == ff || up[to] == 0)
            continue;
        dep[to] = dep[x] + 1;
        getleaf(to, x);
        wson = 0;
    }
    if (wson)
        leaf[++Leaf] = x;  //叶子拿出来
}
inline int getlca(int x, int y) {
    while (x != y) {
        if (dep[x] > dep[y])
            x = fa[x];
        else
            y = fa[y];
    }
    return x;
}
inline void upd(short int& x, short int y) { x = min(x, y); }
int main() {
    freopen("gift.in", "r", stdin);
    freopen("gift.out", "w", stdout);
    // freopen("1.in","r",stdin);
    // freopen("1.out","w",stdout);
    n = re();
    K = re();
    for (rint i = 1; i < n; ++i) {
        short int u = re(), v = re();
        add_e(u, v);
        add_e(v, u);
    }
    for (rint i = 1; i <= K; ++i) {
        short int x = re();
        up[x] = 1;
    }
    dfs(1, 0);  //向上传递必须遍历的标记(有草莓)
    Leaf = 1;
    leaf[1] = 1;
    getleaf(1, 0);
    for (rint i = 1; i < Leaf; ++i)
        for (rint j = i + 1; j <= Leaf; ++j) lca[i][j] = lca[j][i] = getlca(leaf[i], leaf[j]);

    for (rint i = 0; i <= n; ++i)
        for (rint j = 0; j <= n; ++j)
            for (rint k = 0; k <= n; ++k) f[i][j][k] = 32767;
    // f[i][j][k]:前i个叶子,和i在一起的价值是k,和j在一起的价值是fijk
    f[1][0][0] = 0;
    dep[0] = 0;
    leaf[0] = 0;
    for (rint i = 1; i < Leaf; ++i)  //不靠谱哇
    {
        // chu("think:%d\n",leaf[i]);
        for (rint j = 0; j < i; ++j)  // j可以没有吗?应该可以吧?都给i集合也是合法的决策
        {
            for (rint k = 0; k <= n; ++k) {
                // chu("前%d个叶子,i集合价值:%d 上一个在另集合的是:%d 另集合价值:%d\n",i,k,j,f[i][j][k]);
                if (k + dep[leaf[i + 1]] - dep[lca[i][i + 1]] <= n)
                    upd(f[i + 1][j][k + dep[leaf[i + 1]] - dep[lca[i][i + 1]]], f[i][j][k]);

                if (f[i][j][k] + dep[leaf[i + 1]] - dep[lca[j][i + 1]] <= n)
                    upd(f[i + 1][i][f[i][j][k] + dep[leaf[i + 1]] - dep[lca[j][i + 1]]], k);
            }
        }
        //如果j是0,代表没有j,就是i+1新创建集合?或者i+1和i合并,新集合继续没有
    }
    short int ar = 32767;
    for (rint i = 0; i <= n; ++i)
        for (rint j = 0; j < n; ++j) upd(ar, max(f[Leaf][i][j], j));
    chu("%d", (int)ar * 2);
    return 0;
}
/*
7 4
1 2
2 3
2 4
1 5
5 6
6 7
3 4 5 7
真的是想不出来了
觉得2个人可以看成一个,枚举中间的过1的断点
觉得可以把叶子拿出来(要是拿一定是拿到叶子),枚举二进制数,
O(2^lef * n)
*/

posted on 2022-11-07 18:30  HZOI-曹蓉  阅读(44)  评论(0编辑  收藏  举报