jack pack 1.

收录数据结构题

1. CF1920F2 - Smooth Sailing (Hard Version)

tag:kruskal 重构树;link

观察:选取中间岛最下侧的一个点,将这个点右下角与边界上点连边。

image

则一条路径绕过整个岛等价于跨过这条边偶数次。那么每个点拆成 \((x,y,0),(x,y,1)\) 两个点就可以建图了,一个合法的环对应着 \((x,y,0),(x,y,1)\) 的一条路径。

bfs 可以求出每个点到最近火山的距离,最大点权瓶颈路即为答案。使用 kruskal 重构树求解。

点击查看代码
//CF1920F2
#include <bits/stdc++.h>
using namespace std;

const int N = 3e6 + 10;
const int dx[4] = {-1, 0, 0, 1}, dy[4] = {0, -1, 1, 0};
int n, m, q, mx = 0, my = 0;
vector<char> s[N];
vector<int> dis[N];
vector<int> g[N];
char st[N];
struct ege{
    int u, v, w;
} eg[N];
int tot = 0;
int fa[N], nd[N];
int ndc = 0;
int val[N];

int cg(int x, int y, int id){
    return id * n * m + (x-1) * m + y;
}
void add(int p, int q, int r){
    eg[++tot] = { p, q, r };
}
bool cmp(ege x, ege y){
    return x.w > y.w;
}
int gf(int x){
    return x == fa[x] ? x : fa[x] = gf(fa[x]);
}

int dep[N], anc[N][20];
void dfs(int x, int fat){
    dep[x] = dep[fat] + 1;
    anc[x][0] = fat;
    for(int i = 1; i < 20; ++ i){
        anc[x][i] = anc[anc[x][i-1]][i-1];
    }
    for(int i : g[x]){
        dfs(i, x);
    }
}
int lca(int x, int y){
    if(dep[x] > dep[y]){
        swap(x, y);
    }
    for(int i = 19; i >= 0; -- i){
        if(dep[anc[y][i]] >= dep[x]){
            y = anc[y][i];
        }
    }
    if(x == y){
        return y;
    }
    for(int i = 19; i >= 0; -- i){
        if(anc[x][i] != anc[y][i]){
            x = anc[x][i];
            y = anc[y][i];
        }
    }
    return anc[x][0];
}

int main(){
    scanf("%d%d%d", &n, &m, &q);
    queue<pair<int, int> > qu;
    for(int i = 1; i <= n; ++ i){
        s[i].resize(m+1);
        dis[i].resize(m+1);
        scanf("%s", st + 1);
        for(int j = 1; j <= m; ++ j){
            s[i][j] = st[j];
            dis[i][j] = -1;
            if(s[i][j] == 'v'){
                qu.push(make_pair(i, j));
                dis[i][j] = 0;
            } else if(s[i][j] == '#'){
                if(i > mx){
                    mx = i;
                    my = j;
                }
            }
        }
    }
    while(!qu.empty()){
        int x = qu.front().first;
        int y = qu.front().second;
        qu.pop();
        for(int i = 0; i < 4; ++ i){
            int xx = x + dx[i], yy = y + dy[i];
            if(xx >= 1 && xx <= n && yy >= 1 && yy <= m && dis[xx][yy] == -1){
                dis[xx][yy] = dis[x][y] + 1;
                qu.push(make_pair(xx, yy));
            }
        }
    }
    for(int i = 1; i <= n; ++ i){
        for(int j = 1; j <= m; ++ j){
            fa[cg(i, j, 0)] = nd[cg(i, j, 0)] = cg(i, j, 0);
            fa[cg(i, j, 1)] = nd[cg(i, j, 1)] = cg(i, j, 1);
            if(s[i][j] == '#'){
                continue;
            }
            for(int k = 2; k < 4; ++ k){
                int x = i + dx[k], y = j + dy[k];
                if(x >= 1 && x <= n && y >= 1 && y <= m && s[x][y] != '#'){
                    if(j == y && j > my && min(i, x) == mx){
                        add(cg(i, j, 0), cg(x, y, 1), min(dis[i][j], dis[x][y]));
                        add(cg(i, j, 1), cg(x, y, 0), min(dis[i][j], dis[x][y]));
                    } else {
                        add(cg(i, j, 0), cg(x, y, 0), min(dis[i][j], dis[x][y]));
                        add(cg(i, j, 1), cg(x, y, 1), min(dis[i][j], dis[x][y]));
                    }
                }
            }
        }
    }
    ndc = cg(n, m, 1);
    sort(eg + 1, eg + tot + 1, cmp);
    for(int i = 1; i <= tot; ++ i){
        int u = eg[i].u, v = eg[i].v, w = eg[i].w;
        if(gf(u) == gf(v)){
            continue;
        }
        u = gf(u), v = gf(v);
        fa[u] = v;
        ++ ndc;
        g[ndc].push_back(nd[u]);
        g[ndc].push_back(nd[v]);
        nd[v] = ndc;
        val[ndc] = w;
    }
    dfs(ndc, 0);
    
    while(q--){
        int x, y;
        scanf("%d%d", &x, &y);
        int st = cg(x, y, 0), ed = cg(x, y, 1);
        printf("%d\n", val[lca(st, ed)]);
    }
    return 0;
}

2. qoj5165/NOIP2022 - 比赛

tag:历史和线段树;link

考虑扫描线,在 \(r\) 处计算出所有 \([l,r]\) 区间的答案。

\(f_i=\sum_{j=i}^r a_{\max[i,j]}b_{\max[i,j]}\),即左端点为 \(i\) 的区间到现在的贡献。那么答案就相当于 $ f_{\sum[l,r]}$。

考虑 \(r-1\to r\) 会对 \(f\) 产生什么变化。对于 \(f_i\),会加上 \(a_{\max[i,r]}b_{\max[i,r]}\)。那么设 \(x_i=a_{\max[i,r]},y_i=b_{\max[i,r]}\),那么每次操作相当于:

  • \(x_i\) 更新为 \(\max\{x_i,a_r\}\)。可以使用单调栈转化为若干个 \(x\) 的区间加;
  • \(y_i\) 更新为 \(\max\{y_i,b_r\}\)。可以使用单调栈转化为若干个 \(y\) 的区间加;
  • 对于所有 \(i\),令 \(f_i\) 加上 \(x_iy_i\),即维护 \(xy\) 的历史和。

不会历史和怎么办?直接矩阵维护!

线段树每个节点维护如下行向量,非叶节点定义为所有叶节点行向量的和:

\[\begin{bmatrix} x&y&xy&f&1 \end{bmatrix}\]

那么每次 \(x+a\) 相当于对于区间内每一个节点右乘:

\[\begin{bmatrix} 1&0&0&0&0\\0&1&a&0&0\\0&0&1&0&0\\0&0&0&1&0\\a&0&0&0&1 \end{bmatrix}\]

\(y+b\) 类似,增加历史和即为乘上:

\[\begin{bmatrix} 1&0&0&0&0\\0&1&0&0&0\\0&0&1&1&0\\0&0&0&1&0\\0&0&0&0&1 \end{bmatrix}\]

维护矩阵 tag 即可。

但是太慢了!卡常方法:

  • 循环展开;
  • 维护 bool 表示 tag 是否为单位矩阵;
  • 发现矩阵只有 \(9\) 个位置有用。记录下来,矩阵乘法时只更新这些部分。
点击查看代码(卡常版)
//qoj5165
#include <bits/stdc++.h>
using namespace std;

const int N = 2.5e5 + 10;
int T, n, Q, sa[N], sb[N];
typedef unsigned long long ll;
ll a[N], b[N], ans[N];
vector<pair<int, int> > q[N];

struct mat{
    ll A, B, C, D, E, F, G, H, I;
    mat operator * (const mat &b) const {
        mat c;
        c.A = A + b.A;
        c.B = B + b.B + A * b.E;
        c.C = C + b.C;
        c.D = D + b.D + C * b.E;
        c.E = E + b.E;
        c.F = F + b.F;
        c.G = G + b.G;
        c.H = H + b.H + F * b.A + G * b.C;
        c.I = I + b.I + F * b.B + G * b.D + H * b.E;
        return c;
    }
} ini;
mat ma(ll x){
    mat k = ini;
    k.C = k.F = x;
    return k;
}
mat mb(ll x){
    mat k = ini;
    k.A = k.G = x;
    return k;
}
mat lsh(){
    mat k = ini;
    k.E = 1;
    return k;
}
struct node{
    ll x, y, xy, all, cnt;
    bool flg;
    mat tag;
    void mdf(mat val){
        all = x * val.B + y * val.D + xy * val.E + all + cnt * val.I;
        xy = x * val.A + y * val.C + xy + cnt * val.H;
        x += cnt * val.F;
        y += cnt * val.G;
    }
} t[N*4];
void psd(int p){
    if(!t[p].flg){
        return;
    }
    t[p].flg = 0;
    t[p<<1].mdf(t[p].tag);
    t[p<<1|1].mdf(t[p].tag);
    t[p<<1].tag = t[p<<1].tag * t[p].tag;
    t[p<<1|1].tag = t[p<<1|1].tag * t[p].tag;
    t[p<<1].flg = t[p<<1|1].flg = 1;
    t[p].tag = ini;
}
void upd(int p){
    t[p].x = t[p<<1].x + t[p<<1|1].x;
    t[p].y = t[p<<1].y + t[p<<1|1].y;
    t[p].xy = t[p<<1].xy + t[p<<1|1].xy;
    t[p].all = t[p<<1].all + t[p<<1|1].all;
    t[p].cnt = t[p<<1].cnt + t[p<<1|1].cnt;
}
void build(int p, int l, int r){
    t[p].cnt = r - l + 1;
    t[p].flg = 0;
    if(l != r){
        int mid = l + r >> 1;
        build(p<<1, l, mid);
        build(p<<1|1, mid+1, r);
        upd(p);
    }
}
void add(int p, int l, int r, int ql, int qr, mat val){
    if(qr < l || r < ql){
        return;
    } else if(ql <= l && r <= qr){
        t[p].mdf(val);
        t[p].tag = t[p].tag * val;
        t[p].flg = 1;
    } else {
        int mid = l + r >> 1;
        psd(p);
        add(p<<1, l, mid, ql, qr, val);
        add(p<<1|1, mid+1, r, ql, qr, val);
        upd(p);
    }
}
ll ask(int p, int l, int r, int ql, int qr){
    if(qr < l || r < ql){
        return 0;
    } else if(ql <= l && r <= qr){
        return t[p].all;
    } else {
        int mid = l + r >> 1;
        psd(p);
        return ask(p<<1, l, mid, ql, qr) + ask(p<<1|1, mid+1, r, ql, qr);
    }
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> T >> n;
    for(int i = 1; i <= n; ++ i){
        cin >> a[i];
    }
    for(int i = 1; i <= n; ++ i){
        cin >> b[i];
    }
    cin >> Q;
    for(int i = 1; i <= Q; ++ i){
        int l, r;
        cin >> l >> r;
        q[r].emplace_back(l, i);
    }
    build(1, 1, n);
    int ta = 0, tb = 0;
    for(int i = 1; i <= n; ++ i){
        int la = i, lb = i;
        add(1, 1, n, i, i, ma(a[i]));
        while(ta && a[sa[ta]] <= a[i]){
            add(1, 1, n, sa[ta], la-1, ma(a[i]-a[sa[ta]]));
            la = sa[ta];
            -- ta;
        }
        a[la] = a[i];
        sa[++ta] = la;
        add(1, 1, n, i, i, mb(b[i]));
        while(tb && b[sb[tb]] <= b[i]){
            add(1, 1, n, sb[tb], lb-1, mb(b[i]-b[sb[tb]]));
            lb = sb[tb];
            -- tb;
        }
        b[lb] = b[i];
        sb[++tb] = lb;
        add(1, 1, n, 1, n, lsh());
        for(auto j : q[i]){
            ans[j.second] = ask(1, 1, n, j.first, i);
        }
    }
    for(int i = 1; i <= Q; ++ i){
        cout << ans[i] << '\n';
    }
    return 0;
}

3. qoj361/JOISC2017 - 港湾設備 (Port Facility)

tag:优化建图;link

若两个区间有交但不包含,则这两个区间之间连一条边;那么若图不是二分图则答案为 \(0\),否则若图中有 \(k\) 个连通块,答案为 \(2^k\)

考虑优化建图。但是这里的优化建图与平时的不同,不是加点使边数更少;而是不加一些不必要的边。

将区间从左到右排序。遍历到一个区间 \([l,r]\) 时,应向之前出现过的所有右端点在 \([l,r]\) 之间的区间连边。使用线段树,每个节点维护右端点在一段值域区间内的所有区间,那么就可以连边。优化是若遍历到一个线段树节点后将这个节点除掉左端点最小的区间以外的其它区间全部删掉。

原因是:在遍历到一个线段树节点后,这个线段树节点内所有边都两两增加了一条偶路径;那么再次遍历节点时,若只连向其中一个,则依旧新区间与节点内有一条奇偶性相同的路径。

点击查看代码
//qoj361
#include <bits/stdc++.h>
using namespace std;

const int N = 2e6 + 10;
const long long P = 1e9 + 7;
int n, col[N], tot;
pair<int, int> a[N];
vector<int> t[N*4], g[N];

void ask(int p, int l, int r, int ql, int qr, int x){
    if(qr < l || r < ql){
        return;
    } else if(ql <= l && r <= qr){
        for(int i : t[p]){
            g[x].push_back(i);
            g[i].push_back(x);
        }
        while(t[p].size() > 1){
            t[p].pop_back();
        }
    } else {
        int mid = l + r >> 1;
        ask(p<<1, l, mid, ql, qr, x);
        ask(p<<1|1, mid+1, r, ql, qr, x);
    }
}
void add(int p, int l, int r, int x, int v){
    t[p].push_back(v);
    if(l != r){
        int mid = l + r >> 1;
        if(x <= mid){
            add(p<<1, l, mid, x, v);
        } else {
            add(p<<1|1, mid+1, r, x, v);
        }
    }
}

void dfs(int x, int cl){
    col[x] = cl;
    for(int i : g[x]){
        if(!col[i]){
            dfs(i, 3 - cl);
        }
    }
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; ++ i){
        scanf("%d%d", &a[i].first, &a[i].second);
    }
    sort(a + 1, a + n + 1);
    for(int i = 1; i <= n; ++ i){
        ask(1, 1, n + n, a[i].first, a[i].second, i);
        add(1, 1, n + n, a[i].second, i);
    }
    for(int i = 1; i <= n; ++ i){
        if(!col[i]){
            ++ tot;
            dfs(i, 1);
        }  
    }
    for(int i = 1; i <= n; ++ i){
        for(int j : g[i]){
            if(col[i] == col[j]){
                puts("0");
                return 0;
            }
        }
    }
    long long ans = 1;
    while(tot--){
        ans = ans * 2 % P;
    }
    printf("%lld\n", ans);
    return 0;
}
posted @ 2024-10-07 19:23  KiharaTouma  阅读(2)  评论(0编辑  收藏  举报