tech pack 1.

收录技巧题

1. CF1973E - Cat, Fox and Swaps

tag:构造、贪心;link

可以发现有一个通过元素 \(z\) 交换 \(x,y\) 的方法(不妨 \(x<y\)):

\((x,y,z)\to (z,y,x)\to(y,z,x)\to(y,x,z)\),需求是覆盖区间 \([x+z,y+z]\)

而可以通过交换 \((x,x+1),(x+1,x+2),...\) 的方式交换两个较远的数。所以不妨所有 \(p_i\neq i\) 的数都位于 \([le,ri]\) 之间,那么长度 \(=2\) 的合法 \([l,r]\) 应该满足 \(l\in[ri,le+n]\)

枚举区间长度,对于长度 \(i\),合法区间的 \(l\in[\max(1,ri-i+2),\min(ri,n+n-i)]\)

但是还需两个特判:

  • 不存在 \(p_i\neq i\),输出 \(n(2n+1)\) 即可。
  • 对于所有 \(p_i\neq i\)\(p_i+i+1\) 为一个定值 \(k\),这样区间 \([k,k]\) 也是合法的。注意到 \(k\in [ri,le+n]\),所以未统计的区间有且仅有一个,加上即可。
点击查看代码
//CF1973E
#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;
int T, n, p[N];

int main(){
    scanf("%d", &T);
    while(T--){
        scanf("%d", &n);
        bool flg = 1;
        int le = n + 1, ri = 0;
        for(int i = 1; i <= n; ++ i){
            scanf("%d", &p[i]);
            if(p[i] != i){
                flg = 0;
                le = min(le, i);
                ri = max(ri, i);
            }
        }
        if(flg){
            printf("%lld\n", n * 1ll * (n + n + 1));
        } else {
            long long ans = 0;
            int val = p[le] + le;
            for(int i = le; i <= ri; ++ i){
                if(p[i] != i && val != p[i] + i){
                    flg = 1;
                }
            }
            if(!flg){
                ++ ans;
            }
            swap(le, ri); 
            ri += n;
            for(int i = 2; i <= n + n; ++ i){
                ans += ri - le + 1;
                le = max(1, le - 1);
                ri = min(ri, n + n - i);
            }
            printf("%lld\n", ans);
        }
    }
    return 0;
}

2. CF1973D - Cat, Fox and Double Maximum

tag:复杂度分析;link

首先有两个方向:\(n\) 次询问问出 \(a_1\)\(n\) 次询问问出 \(\max\)。发现前面一种没什么前途,所以考虑后一种。设求出的 \(\max=p\),则答案只可能为 \(p\) 的倍数,设为 \(tp\)

又有 \(ktp=\sum f(l,r)\leq f(1,n)=np\),所以有 \(t\leq\dfrac nk\),对于任意一个 \(t\) 至多问 \(k\) 次,所以复杂度是正确的!!!

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

int T, n, k, mx;

int ask(int l, int x){
    cout << "? " << l << ' ' << x << endl;
    int p;
    cin >> p;
    return p;
}
bool chk(int x){
    int tp = 1, cnt = 0;
    while(tp <= n){
        tp = ask(tp, x * mx) + 1;
        ++ cnt;
        if(cnt > k){
            return 0;
        }
    }
    return cnt == k && tp == n + 1;
}

int main(){
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &k);
        for(int i = n; i >= 1; -- i){
            if(ask(1, i*n) == n){
                mx = i;
                break;
            }
        }
        bool flg = 0;
        for(int i = n / k; i >= 1; -- i){
            if(chk(i)){
                flg = 1;
                cout << "! " << i * mx << endl;
                break;
            }
        }
        if(!flg){
            cout << "! -1" << endl;
        }
        int p;
        scanf("%d", &p);
    }
    return 0;
}

3. Toyota2023SpringF - Git Gud

tag:exchange argument;link

考虑计算每一条边的贡献。一条边在一次合并中有贡献,当且仅当这次合并两个点一端在这条边的子树中。所以计算出每个点的 \(deg\),假设合并 \((x,y)\)(均为两个并查集根),则答案加上 \(deg_y-1\)\(deg_x\) 加上 \(deg_y-2\)。最终的答案还需再加上 \(n-1\)

\(deg_i-2\) 替换 \(deg_i\),则最后答案加上 \(2(n-2)\),并且每次合并 \((x,y)\) 变为 \(ans+deg_y\to ans,deg_x+deg_y\to deg_y\)

发现一定存在一种最优解,使得每次合并后,被合并的点构成一个连通块。因为:

  • 若这次合并是最后一次,显然正确;
  • 否则若一次合并中合并了两个 \(\geq2\) 的连通块,则一定有一个连通块还有至少 \(2\) 条边未合并(意味着 \(\sum deg-2\geq 0\)),则将这个连通块先与另一连通块相邻的那个点合并,再合并另一个连通块肯定不劣。

那么就相当于:

  • 枚举一个根 \(rt\)
  • 找到一个 \(\{b_1,...,b_n\}=\{0,1,...,n-1\}\),满足对于树上 \((x,fa_x)\)\(b_x<b_{fa_x}\)
  • \(\max_{b}{\sum (deg_i-2)b_i}\) 更新答案。

使用 exchange argument 套路,每次维护平均值最小的连通块,接在它父亲连通块的后面。

最后记得答案加上 \(3(n-1)\)

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

const int N = 2010;
int n, deg[N], rs = -1e9;
vector<int> g[N];
int fa[N];

void dfs(int x, int fat){
    fa[x] = fat;
    for(int i : g[x]){
        if(i != fat){
            dfs(i, x);
        }
    }
}
struct ufs{
    int fa[N];
    void init(){
        for(int i = 1; i <= n; ++ i){
            fa[i] = i;
        }
    }
    int gf(int x){
        return fa[x] == x ? x : fa[x] = gf(fa[x]);
    }
    void mg(int x, int y){
        fa[gf(x)] = gf(y);
    }
} t;
struct node{
    int id, sum, siz;
    bool operator < (const node &b) const {
        return sum * b.siz < siz * b.sum;
    }
};

int sum[N], ans[N], siz[N]; 
int calc(int rt){
    dfs(rt, 0);
    priority_queue<node> q;
    for(int i = 1; i <= n; ++ i){
        q.push({ i, deg[i], 1 });
        sum[i] = deg[i];
        ans[i] = 0;
        siz[i] = 1;
    }
    t.init();
    while(!q.empty()){
        node x = q.top();
        q.pop();
        if(x.id == rt || t.gf(x.id) != x.id){
            continue;
        }
        int y = t.gf(fa[x.id]);
        ans[y] += sum[y] * siz[x.id] + ans[x.id];
        sum[y] += sum[x.id];
        siz[y] += siz[x.id];
        q.push({ y, sum[y], siz[y] });
        t.mg(x.id, y);
    }
    return ans[rt] + 3 * (n - 1);
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i < n; ++ i){
        int u, v;
        scanf("%d%d", &u, &v);
        ++ u;
        ++ v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for(int i = 1; i <= n; ++ i){
        deg[i] = g[i].size() - 2;
    }
    for(int i = 1; i <= n; ++ i){
        rs = max(rs, calc(i));
    }
    printf("%d\n", rs);
    return 0;
}

4. CCO2021 - Travelling Merchant

https://www.luogu.com.cn/problem/P7831

考虑如何求出所有 \(-1\):取出所有出度为 \(0\) 的点,把这些点删掉,然后把新产生的出度为 \(0\) 的点删掉,类似拓扑排序,所有删掉的点均为 \(-1\),其它点答案 \(\leq \max\{r_i\}\)

\(r_i\) 从大到小枚举每一条边,假设目前 \(r\) 最大的一条边为 \((a,b,r,p)\),则若到达 \(a\) 点的金币数 \(\geq r\),那么可以随便走。更新完后就删掉这一条边。

若一个点的所有出边都被删除,那么它的答案就确定了。枚举以它作为 \(b\) 的边 \((a,b,r,p)\),那么可以用 \(\max(r,f_b-p)\) 更新 \(f_a\),然后把这条边删掉,若 \(a\) 的出边被删空则继续拓扑排序即可。

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

const int N = 4e5 + 10;
int n, m, ind[N], vis[N];
typedef long long ll;
ll ans[N];
struct edge{
    int a, b, r, p, id;
} g[N];
vector<int> fr[N];
bool cmp(edge x, edge y){
    return x.r > y.r;
}

int hd[N], top;
struct Edge{
    int eg, r, p, nx, id;
} e[N];

int main(){
    scanf("%d%d", &n, &m);
    int mx = 0;
    for(int i = 1; i <= m; ++ i){
        int a, b, r, p;
        scanf("%d%d%d%d", &a, &b, &r, &p);
        g[i] = {a, b, r, p, i};
        mx = max(mx, r);
        e[++top] = {a, r, p, hd[b], i};
        hd[b] = top;
        ++ ind[a];
    }
    sort(g + 1, g + m + 1, cmp);
    memset(ans, 0x3f, sizeof(ans));
    queue<int> q;
    for(int i = 1; i <= n; ++ i){
        if(!ind[i]){
            q.push(i);
        }
    }
    for(int i = 1; i <= m; ++ i){
        while(!q.empty()){
            int x = q.front();
            q.pop();
            for(int i = hd[x]; i; i = e[i].nx){
                if(vis[e[i].id]){
                    continue;
                }
                int y = e[i].eg;
                vis[e[i].id] = 1;
                if(ans[x] <= mx){
                    ans[y] = min(ans[y], max(ans[x] - e[i].p, (ll)e[i].r));
                }
                -- ind[y];
                if(!ind[y]){
                    q.push(y);
                }
            }
        }
        int a = g[i].a, b = g[i].b, r = g[i].r, p = g[i].p;
        if(!vis[g[i].id]){
            vis[g[i].id] = 1;
            ans[a] = min(ans[a], (ll)r);
            -- ind[a];
            if(!ind[a]){
                q.push(a);
            }
        }
    }
    for(int i = 1; i <= n; ++ i){
        if(ans[i] > mx){
            ans[i] = -1;
        }
        printf("%lld ", ans[i]);
    }
    return 0;
}
posted @ 2024-10-07 19:24  KiharaTouma  阅读(34)  评论(0)    收藏  举报