代码源每日一题 div1(101-109)

本文通过多个编程题目展示了二分查找算法在解决区间问题、求最值、因子链计算、序列操作等方面的巧妙应用。通过二分查找不仅可以实现高效求解,还能简化问题的处理,如求最长因子链、子串最大差值等。文章深入浅出地解析了如何运用二分法来优化算法,提高代码效率。

代码源每日一题

这个专栏很多题都参考了大佬的题解,大佬链接如下:
Link

二分答案

[Link](二分答案 - 题目 - Daimayuan Online Judge)

思路

  • 二分

​ 找最小值最大,很明显的二分,二分答案, O ( n ) O(n) O(n)检查答案是否合法。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
LL n, m, k;
int a[N];
bool check(LL x) {
    LL sum = 0;
    for (int i = 1; i <= n; i ++)
        sum += max(0ll, x - a[i]);

    return sum <= k;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> k;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    LL l = 0, r = 2e13;
    while (l < r) {
        LL mid = l + r + 1 >> 1;
        if (check(mid)) l = mid;
        else r = mid - 1;
    }
    
    cout << r << '\n';
    return 0;
}

最长因子链

[Link](最长因子链 - 题目 - Daimayuan Online Judge)

思路

  • 线性 d p dp dp

n n n只有 1000 1000 1000,仿照最长上升子序列,将序列排个序(数的因子只会是比它小的数),做一个 n 2 n^2 n2 d p dp dp,可以转移的条件即 a [ i ] % a [ i ] = 0 a[i]\%a[i]=0 a[i]%a[i]=0

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    sort(a + 1, a + 1 + n);
    vector<int> f(n + 1);
    int res = 0;
    for (int i = n; i >= 1; i --) {
        f[i] = 1;
        for (int j = i + 1; j <= n; j ++)
            if (a[j] % a[i] == 0)
                f[i] = max(f[i], f[j] + 1);
        res = max(res, f[i]);
    } 

    cout << res << '\n';
    return 0;
}

子串最大差值

[Link](子串的最大差 - 题目 - Daimayuan Online Judge)

思路

  • 单调栈+ d p dp dp

​ 暴力枚举每个子串复杂度过高,这是一个算贡献的题,重点在于怎么划分不漏的算贡献。

​ 首先将所有的子串按照右端点分类,对于以 i i i为右端点的子串的贡献为 s u m ( 每个串最大 ) − s u m ( 每个串最小 ) sum(每个串最大)-sum(每个串最小) sum(每个串最大)sum(每个串最小),因此最大最小是独立的,可以分开来算最大值,最小值的和最后想减。

​ 对于求最大值,设 f i : 以 i 结尾的所有子串的最大值的和 f_i:以i结尾的所有子串的最大值的和 fi:i结尾的所有子串的最大值的和,对于当前的 i i i往前枚举一定存在某个 j j j使得 a [ j ] > a [ i ] a[j]>a[i] a[j]>a[i],左端点在 j j j右边的区间的最大值均是 a i a_i ai,左端在 j j j左边的区间的最大值等价于求以 j j j结尾的左右子串的最大值(这里可以就存在转移关系了),这些区间的贡献就是 f j f_j fj,转移为 f i = a [ i ] ∗ ( i − j ) + f j f_i=a[i]*(i-j)+f_j fi=a[i](ij)+fj,求某个数左边第一个比他大的数可以用单调栈来维护,最小值求法同理。

​ 最后枚举每个点加上以这个点结尾的区间和最大值和最小值相减。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<LL, LL> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 5e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
LL a[N];
LL f[N], g[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    stack<PII> stk;
    for (int i = 1; i <= n; i ++) {
        while (stk.size() && stk.top().x <= a[i]) stk.pop();
        if (stk.size()) {
            f[i] = a[i] * (i - stk.top().y) + f[stk.top().y];
            stk.push({a[i], i});
        }
        else {           
            f[i] = a[i] * i;
            stk.push({a[i], i});
        }
    }

    while (stk.size()) stk.pop();
    for (int i = 1; i <= n; i ++) {
        while (stk.size() && stk.top().x >= a[i]) stk.pop();
        if (stk.size()) {
            g[i] = a[i] * (i - stk.top().y) + g[stk.top().y];
            stk.push({a[i], i});
        }
        else {
            g[i] = a[i] * i;
            stk.push({a[i], i});
        }
    }
  
    LL res = 0;
    for (int i = 1; i <= n; i ++)   
        res += f[i] - g[i];
    
    cout << res << '\n';
    return 0;
}

no crossing

[Link](no crossing - 题目 - Daimayuan Online Judge)

思路

  • 区间 d p dp dp

​ 一开始写了个暴力的搜索, T 9 T9 T9。转化一下题意就等价于在一个长度为 n n n的数轴上,从某个点开始走 k − 1 k-1 k1步的最短距离,并且当前走的区间不能包含住前面走的点,只有两种走法,往外或者往里,这样区间应该是不断缩小的。

​ 为了方便 d p dp dp转移,我们可以反向思考它,由小的区间往外走,可以用 f [ k ] [ i ] [ j ] [ s ] : 当前走了 k 步的区间左端点在 i 右端点 j 且 s = 0 表示当前在左边 s = 1 表示当前在右边的最短路径 f[k][i][j][s]:当前走了k步的区间左端点在i右端点j且s=0表示当前在左边s=1表示当前在右边的最短路径 f[k][i][j][s]:当前走了k步的区间左端点在i右端点js=0表示当前在左边s=1表示当前在右边的最短路径,用滚动数组,第一维可以优化掉,做区间 d p dp dp即可,转移满足包含当前区间或者不相交即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> k >> m;
    vector<PII> g[n + 1];
    for (int i = 1; i <= m; i ++) {
        int x, y, z; cin >> x >> y >> z;
        g[y].push_back({x, z});
    }
    vector<vector<vector<int>>> f(n + 1, vector<vector<int>>(n + 1, vector<int>(2, INF)));
    for (int i = 1; i <= n; i ++) f[i][i][0] = f[i][i][1] = 0;

    for (int i = 0; i < k - 1; i ++) {
        vector<vector<vector<int>>> nf(n + 1, vector<vector<int>>(n + 1, vector<int>(2, INF)));
        for (int l = 1; l <= n; l ++)
            for (int r = l; r <= n; r ++) {
                if (f[l][r][0] != INF) {
                    for (auto t : g[l]) 
                        if (t.first < l || t.first > r) {
                            int k1 = min(t.first, l), k2 = max(t.first, r);
                            bool ok = (t.first == k2);
                            nf[k1][k2][ok] = min(nf[k1][k2][ok], f[l][r][0] + t.second);  
                        }                
                       
                }
                if (f[l][r][1] != INF) {                             
                    for (auto t : g[r])
                        if (t.first > r || t.first < l) {
                            int k1 = min(t.first, l), k2 = max(t.first, r);
                            bool ok = (t.first == k2);
                            nf[k1][k2][ok] = min(nf[k1][k2][ok], f[l][r][1] + t.second);
                        }
                       
                }                               
            }
                        
        f = nf;                            
    }

    int res = INF;
    for (int i = 1; i <= n; i ++)
        for (int j = i; j <= n; j ++)
            res = min(res, min(f[i][j][0], f[i][j][1]));

    if (res == INF) res = -1;
    cout << res << '\n';
    return 0;
}

Dis

[Link](Dis - 题目 - Daimayuan Online Judge)

思路

  • l c a lca lca

​ 由于异或满足 a ⊕ b ⊕ b = a a\oplus b\oplus b=a abb=a,即满足消去,因此求 u , v u,v u,v的路径异或等价于求他们到根节点的异或和的异或再异或上他们的最近公共祖先的值即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 2e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int f[N][21], dep[N];
int a[N], sum[N];
void dfs(int u, int fa) {
    dep[u] = dep[fa] + 1;
    sum[u] = sum[fa] ^ a[u];
    f[u][0] = fa;
    for (int i = 1; i <= 20; i ++)
        f[u][i] = f[f[u][i - 1]][i - 1];
    for (int i = h[u]; ~i; i = ne[i]) {
        int j = e[i];
        if (j == fa) continue ;
        dfs(j, u);
    }
}
int lca(int x, int y) {
    if (dep[x] < dep[y]) return lca(y, x);
    for (int i = 20; i >= 0; i --)
        if (dep[f[x][i]] >= dep[y])
            x = f[x][i];

    if (x == y) return x;
    
    for (int i = 20; i >= 0; i --)
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++) cin >> a[i];
    for (int i = 1; i < n; i ++) {
        int x, y; cin >> x >> y;
        add(x, y), add(y, x);
    }

    dfs(1, 0);
    while (m --) {
        int x, y; cin >> x >> y;
        int u = lca(x, y);
        cout << (sum[x] ^ sum[y] ^ a[u]) << '\n';
    }
    return 0;
}

选数

[Link](选数 - 题目 - Daimayuan Online Judge)

思路

  • 抽屉原理

​ 如果数据范围小,可以写个背包记录个前驱,还原方案。考虑一下性质,一共有 n n n个数他们 m o d   n mod\ n mod n只会出现 n n n个不同的数,我们求个前缀和,因此如果不存在某个数 m o d   n = 0 mod\ n=0 mod n=0,那么就一定存在两个数 m o d   n mod\ n mod n相同,假设是 s l % n = s r % n s_l\%n=s_r\%n sl%n=sr%n,那么代表 [ l + 1 , r ] [l+1,r] [l+1,r]这个区间和 m o d   n = 0 mod \ n=0 mod n=0,因为将余数减去了。

​ 因此 O ( n ) O(n) O(n)遍历一下即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i ++) {
        cin >> a[i];
        a[i] = (a[i - 1] + a[i]) % n;        
    }
    
    vector<int> last(n);
    for (int i = 1; i <= n; i ++) {
        if (a[i] == 0) {
            cout << i << '\n';
            for (int j = 1; j <= i; j ++)
                cout << j << ' ';
            cout << '\n';
            return 0;
        }
        if (last[a[i]]) {
            cout << i - last[a[i]] << '\n';
            for (int j = last[a[i]] + 1; j <= i; j ++)
                cout << j << ' ';
                cout << '\n';
            return 0;
        }
        last[a[i]] = i;
    }
    return 0;
}

序列操作

[Link](序列操作 - 题目 - Daimayuan Online Judge)

思路

线段树

​ 区间问题可以用线段树暴力的处理,对于区间修改我们直接给根节点打标记即可,标记维护一个最大值。

Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
struct Node {
    int l, r;
    int tag;    
    int mx;
}tr[N << 2];
void pushup(int u) {
    tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
}
void pushdown(int u) {
    if (tr[u].tag != -1) {
        tr[u << 1].tag = max(tr[u].tag, tr[u << 1].tag);
        tr[u << 1 | 1].tag = max(tr[u].tag, tr[u << 1 | 1].tag);
        tr[u].tag = -1;
    }
}
void build(int u, int l, int r) {
    if (l == r) {
        tr[u] = {l, r, -1, a[l]};
        return ;
    }
    int mid = l + r >> 1;
    tr[u] = {l, r, -1};
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    pushup(u);
}
int query(int u, int v) {
    if (tr[u].l == tr[u].r) return max(tr[u].mx, tr[u].tag);
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    if (v <= mid) return query(u << 1, v);
    else          return query(u << 1 | 1, v);
}
void modify(int u, int v, int x) {
    if (tr[u].l == tr[u].r) {
        tr[u].mx = x;
        tr[u].tag = -1; 
        return ;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (v <= mid) modify(u << 1, v, x);
    else          modify(u << 1 | 1, v, x);
    pushup(u);  
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> a[i];
    build(1, 1, n);
  
    while (m --) {
        int op; cin >> op;
        if (op == 1) {
            int x, y; cin >> x >> y;
            modify(1, x, y);
        }
        else {
            int y; cin >> y;
            tr[1].tag = max(tr[1].tag, y);
        }
    }

    for (int i = 1; i <= n; i ++)
        cout << query(1, i) << ' ';
    return 0;
}

倒序模拟

​ 对于这种修改问题的一个思考方式基板上就是倒着考虑,分开来看对于没有单点修改过的点,它的值最终取决于 m a x ( a [ i ] ,操作 2 的最大值 ) max(a[i],操作2的最大值) max(a[i],操作2的最大值),对于单点修改的点它的值取决于,他后面操作 2 2 2的最大值和他最后修改的值,因此对于操作 2 2 2我们记录一个后缀最大值,然后对于每个点分类讨论一下即可。

Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e6 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
PII b[N];
int c[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= n; i ++) cin >> a[i], b[i].y = -1;
    for (int i = 1; i <= m; i ++) {
        int op; cin >> op;
        if (op == 1) {
            int x, y; cin >> x >> y;
            b[x].x = i, b[x].y = y;
        }
        else {
            int x; cin >> x;
            c[i] = x;
        }
    }
    for (int i = m; i; i --) c[i] = max(c[i], c[i + 1]);

    for (int i = 1; i <= n; i ++) 
        if (b[i].y == -1) cout << max(a[i], c[1]) << ' ';
        else cout << max(b[i].y, c[b[i].x]) << ' ';
    
    cout << '\n';
    return 0;
}

数数

[Link](数数 - 题目 - Daimayuan Online Judge)

思路

线段树

类似于势能线段树,记录一个区间最大最小值,然后我们同个这个剪枝即可。

Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
int a[N];
struct Node {
    int l, r;
    int mx, sum, mn;
}tr[N << 2];
void pushup(int u) {
    tr[u].mx = max(tr[u << 1].mx, tr[u << 1 | 1].mx);
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
    tr[u].mn = min(tr[u << 1].mn, tr[u << 1 | 1].mn);
}
void build(int u, int l, int r) {
    if (l == r) {
        tr[u] = {l, r, a[l], 1, a[l]};
        return ;
    }
    tr[u] = {l, r};
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    pushup(u);
}
int query(int u, int l, int r, int v) {    
    if (tr[u].mn > v) return 0;
    if (l <= tr[u].l && tr[u].r <= r && tr[u].mx <= v) return tr[u].sum;
    if (tr[u].l == tr[u].r)  return tr[u].mx <= v;
    int mid = tr[u].l + tr[u].r >> 1;
    int sum = 0;
    if (l <= mid)  sum += query(u << 1, l, r, v);
    if (r > mid)   sum += query(u << 1 | 1, l, r, v);
    return sum;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T; cin >> T;
    while (T --) {
        cin >> n >> m;
        for (int i = 1; i <= n; i ++) cin >> a[i];
        build(1, 1, n);
        while (m --) {
            int l, r, v;
            cin >> l >> r >> v;
            cout << query(1, l, r, v) << " ";
        }
        cout << '\n';
    }    
    return 0;
}
离线树状数组

​ 我们开一个值域树状数组,将所有的数从小到大排序,将所有的查询按 H i H_i Hi从小到大排序,对于然后每次将小于等于当前这个查询的 H i H_i Hi的点的位置设立权值,然后查询区间即可。

​ 这样相当于认为定了个序,使得每次到当前位置使得只有小于当前位置的点的位置才会有贡献,因此直接查询即可。

Code
#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
PII b[N];
int tr[N];
int lowbit(int x) {
    return x & -x;
}
void add(int x) {
    for (; x <= n; x += lowbit(x)) tr[x] ++; 
}
int sum(int x) {
    int res = 0;
    for (; x; x -= lowbit(x)) res += tr[x];    
    return res;
}
struct Node {
    int id, l, r, h;
    bool operator<(Node t)const {
        return h < t.h;
    }
}q[N];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    int T;
    cin >> T;
    while (T -- ) {
        cin >> n >> m;
        for (int i = 1, x; i <= n; i ++) {
            cin >> x;
            b[i] = {x, i};
        }        
        sort(b + 1, b + 1 + n);
        for (int i = 1; i <= m; i ++)
            cin >> q[i].l >> q[i].r >> q[i].h, q[i].id = i;
        sort(q + 1, q + 1 + m);
        vector<int> res(m);
        int pos = 1;

        for (int i = 1; i <= m; i ++) {
            while (pos <= n && q[i].h >= b[pos].x) add(b[pos ++].y);
            res[q[i].id - 1] = sum(q[i].r) - sum(q[i].l - 1);                    
        }
        for (auto t : res) cout << t << ' ';
        cout << '\n';
        for (int i = 1; i <= n; i ++) tr[i] = 0;
    } 
    return 0;
}

Minimum Or Spanning Tree

[Link](Minimum Or Spanning Tree - 题目 - Daimayuan Online Judge)

思路

  • 贪心,并查集

​ 涉及二进制操作的一般可以看二进制,对于我们最终的答案,我们想让高位尽量为 0 0 0,因为高位对低位具有决定性,因此从高到低贪心确定每一位,能取零就为零。

​ 如何判断当前这一位取零是否合法,假设我们前面确定了这些位构成的数位 r e s res res,且当前是第 k k k位,我们假设当前位取 0 0 0,那么什么样的边会会在我们的答案中呢,首先这个边边权 v v v的第 k k k位一定是 0 0 0,否则一或会使这一位位 1 1 1,由于后面的位不确定因此后面取什么都行,但是前面一定要符合我们前面的 r e s res res即这条边或完 r e s res res,第 k k k位之前的位置和 r e s res res中应该相同,不同的话代表这条边在前面贪心的时候就去除了,要判段能否生成树,直接判断最后这个图是否连通即可。
​ 因此从高到底依次贪心即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 4e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};
int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int v = 0) {
    e[idx] = b, w[idx] = v, ne[idx] = h[a], h[a] = idx ++;
}
int n, m, k;
array<int, 3> a[N];
int f[N];
int find(int x) {
    return x == f[x] ? x : f[x] = find(f[x]);
}
void init() {
    for (int i = 1; i <= n; i ++) f[i] = i;
}
int res;
int check(int k) {
    init();
    int x = res;

    for (int i = 1; i <= m; i ++) {
        int t = (1 << k) - 1;
        t &= a[i][2];
        t |= x;
        if ((a[i][2] >> k & 1) == 0 && t == (x | a[i][2])) 
            f[find(a[i][0])] = find(a[i][1]);
        }
        

    int cnt = 0;
    for (int i = 1; i <= n; i ++)
        if (i == f[i]) cnt ++;    
    return cnt != 1;
}
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> m;
    for (int i = 1; i <= m; i ++) cin >> a[i][0] >> a[i][1] >> a[i][2];

    for (int i = 30; i >= 0; i --) 
        if (check(i))
            res |= (1 << i);

    cout << res << '\n';    
    return 0;
}
第 1 章 Lin u x 安 全 渗 透 简 介 1.1 什 么 是 安 全 渗 透 1.2 安 全 渗 透 所 需 的 工 具 1.3 K ali Lin u x 简 介 1.4 安 装 K ali Lin u x 1.5 K ali更 新 与 升 级 1.6 基 本 设 置 第 2 章 配 置 K ali Lin u x 2.1 准 备 内 核 头 文 件 2.2 安 装 并 配 置 N VIDIA 显 卡 驱 动 2.3 应 用 更 新 和 配 置 额 外 安 全 工 具 2.4 设 置 P r o x y C h ain s 2.5 目 录 加 密 第 3 章 高 级 测 试 实 验 室 3.1 使 用 V M w a r e W o r k s t a tio n 3.2 攻 击 W o r d P r e s s 和 其 他 应 用 程 序 第 4 章 信 息 收 集 4.1 枚 举 服 务 4.2 测 试 网 络 范 围 4.3 识 别 活 跃 的 主 机 4.4 查 看 打 开 的 端 口 4.5 系 统 指 纹 识 别 4.6 服 务 的 指 纹 识 别 4.7 其 他 信 息 收 集 手 段 4.8 使 用 M alt e g o 收 集 信 息 4.9 绘 制 网 络 结 构 图 第 5 章 漏 洞 扫 描 5.1 使 用 N e s s u s 5.2 使 用 O p e n VA S 第 6 章 漏 洞 利 用 6.1 M e t a s ploit a ble 操 作 系 统 6.2 M e t a s ploit 基 础 6.3 控 制 M e t e r p r e t e r 6.4 渗 透 攻 击 应 用 大 学 霸 K ali Lin u x 安 全 渗 透 教 程 2 6.5 7 7.1 7.2 7.3 7.4 8 8.1 8.2 8.3 8.4 8.5 8.6 8.7 8.8 8.9 9 9.1 9.2 9.3 9.4 9.5 9.6 9.7 9.8 6.5 免杀Payload生成工具Veil 第7章 权限提升 7.1 使用假冒令牌 7.2 本地权限提升 7.3 使用社会工程学工具包(SET) 7.4 使用SET实施攻击 第8章 密码攻击 8.1 密码在线破解 8.2 分析密码 8.3 破解LM Hashes密码 8.4 绕过Utilman登录 8.5 破解纯文本密码工具mimikatz 8.6 破解操作系统用户密码 8.7 创建密码字典 8.8 使用NVIDIA计算机统一设备架构(CUDA) 8.9 物理访问攻击 第9章 无线网络渗透测试 9.1 无线网络嗅探工具Kismet 9.2 使用Aircrack-ng工具破解无线网络 9.3 Gerix Wifi Cracker破解无线网络 9.4 使用Wifite破解无线网络 9.5 使用Easy-Creds工具攻击无线网络 9.6 在树莓派上破解无线网络 9.7 攻击路由器 9.8 Arpspoof工具
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值