Loading

亿些原古博客汇总 I

由于不想保存了所以全部汇总到这来了。正好从新审视一下那些题再评个级。

然后发现之前的题解都在讲谜语。

CF1111C [1]

这题刚一看觉得很水,仔细一看数据范围……

其实也不难,主要是数据范围过大。要用一种类似离散化的做法。

对数组a排序。对于一段区间[l,r]的英雄数量等于a中第一个比r大的数的下标减1减a中第一个大于等于l的数的下标加1,这样就可以做了(k很小)。

不过要注意剪枝:若区间[l,r]的英雄数量等于0,就直接返回A。

(还可以从动态开点线段树来理解

#include <bits/stdc++.h>
using namespace std;
long long n, k, a, b;
long long hero[1000001];
long long solve(long long l, long long r) {
    long long num = upper_bound(hero + 1, hero + 1 + k, r) - lower_bound(hero + 1, hero + 1 + k, l);
    if (num == 0) return a;
    long long ans = (r - l + 1) * b * num;
    if (l >= r) return ans;
    ans = min(ans, solve(l, (l + r) / 2) + solve((l + r) / 2 + 1, r));
    return ans;
}
int main() {
    cin >> n >> k >> a >> b;
    for (long long i = 1; i <= k; i++) cin >> hero[i];
    sort(hero + 1, hero + 1 + k);
    cout << solve(1, (1 << n));
}

JSOI2015 送礼物 [4]

由于分母越小越好,所以我们尽量想要极值取在两端点处。如果极值距离小于 \(L\) 那么就强制取到 \(L\)。那么思路就清晰了,要求 \(\frac{a_j-a_i}{j-i+k}\) 的极值,那么直接 01 分数规划,二分答案即可。还可能是 \(\frac{M(i,i+L)-m(i,i+L)}{L+k}\),这时直接单调队列即可。

这题的关键是发现 \(i,j\) 可以缩起来。所以有时不好直接做的问题可以先想想贪心简化问题。

#include <bits/stdc++.h>
using namespace std;
const long long MAX = 500005;
const double INF = 1e9;
long long t, n, k, l, r;
long long a[MAX], q1[MAX], q2[MAX], q[MAX];
double val[MAX];
double ans;
//读入优化 
double read() {
    double ret = 0, f = 1;
    char ch = getchar();
    while ('0' > ch || ch > '9') {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while ('0' <= ch && ch <= '9') {
        ret = ret * 10 + ch - '0';
        ch = getchar();
    }
    return ret * f;
}
//判断 
bool judge(double m) {
    double ret = -INF;
    for (long long i = 1; i <= n; i++) {
        val[i] = a[i] - m * i;
    }
    long long head = 1, tail = 0;
    for (long long i = l + 1; i <= n; i++) {
        while (head <= tail && i - q[head] >= r) head++;
        while (head <= tail && val[q[tail]] >= val[i - l]) tail--;
        q[++tail] = i - l;
        ret = max(ret, val[i] - val[q[head]]);
    }
    for (long long i = 1; i <= n; i++) {
        val[i] = a[i] + m * i;
    }
    head = 1, tail = 0;
    for (long long i = n - l; i >= 1; i--) {
        while (head <= tail && q[head] - i >= r) head++;
        while (head <= tail && val[q[tail]] >= val[i + l]) tail--;
        q[++tail] = i + l;
        ret = max(ret, val[i] - val[q[head]]);
    }
    //k是题目给的常数 
    return ret >= k * m;
}
int main() {
    t = read();
    while (t--) {
        ans = -INF;
        n = read(), k = read(), l = read(), r = read();
        for (long long i = 1; i <= n; i++) a[i] = read();
        long long h1 = 1, h2 = 1, t1 = 0, t2 = 0;
        for (long long i = 1; i < l; i++) {
            while (h1 <= t1 && a[q1[t1]] >= a[i]) t1--;
            while (h2 <= t2 && a[q2[t2]] <= a[i]) t2--;
            q1[++t1] = q2[++t2] = i; 
        }
        for (long long i = 1; i <= n; i++) {
            while (h1 <= t1 && i - q1[h1] >= l) h1++;
            while (h2 <= t2 && i - q2[h2] >= l) h2++;
            while (h1 <= t1 && a[q1[t1]] >= a[i]) t1--;
            while (h2 <= t2 && a[q2[t2]] <= a[i]) t2--;
            q1[++t1] = q2[++t2] = i;
            ans = max(ans, 1.0 * (a[q2[h2]] - a[q1[h1]]) / (l + k - 1));
        }
        //注意精度 
        double l = 0, r = 1000;
        while (r - l >= 0.000001) {
            double mid = (l + r) / 2;
            if (judge(mid)) ans = max(ans, mid), l = mid + 0.000001;
            else r = mid - 0.000001;
        }
        printf("%.4lf\n", ans);
    }
    return 0;
}

[TJOI2018]数学计算 [2]

我们构建一棵范围\([1, Q]\)的线段树。

每个叶节点对应一次操作。

每个节点的权值对应其区间积。

每有一次\(1\)操作我们就把对应的位置(第几次操作)改成相应的值然后维护线段树。

设当前是第\(p\)次操作,则查询输出\([1, p]\)的积即可。

\(2\)操作时我们先把当前位置设成1,再把\(pos\)设成1即可。

查询还是\([1, p]\)

其实我们可以偷个小懒……

初始时所有叶节点的权值设为\(1\),输出时只用输出\([1, Q]\)的值即可。

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
struct Segment{
    ll val;
}st[400010];
ll t;
ll q, mod;
ll opt, m[100010];
void build(ll p, ll l, ll r) {
    if (l == r) {
        st[p].val = 1;
        return;
    }
    ll mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    st[p].val = (st[p << 1].val * st[p << 1 | 1].val) % mod;
}
void change(ll p, ll l, ll r, ll pos, ll v) {
    if (l == r) {
        st[p].val = v;
        return;
    }
    ll mid = (l + r) >> 1;
    if (pos <= mid) change(p << 1, l, mid, pos, v);
    else change(p << 1 | 1, mid + 1, r, pos, v);
    st[p].val = (st[p << 1].val * st[p << 1 | 1].val) % mod;
}
int main() {
    scanf("%lld", &t);
    while (t--) {
        scanf("%lld%lld", &q, &mod);
        build(1, 1, q);
        for (ll i = 1; i <= q; i++) {
            scanf("%lld%lld", &opt, &m[i]);
            if (opt == 1) {
                change(1, 1, q, i, m[i]);
                printf("%lld\n", st[1].val);
            } else {
                change(1, 1, q, m[i], 1);
                printf("%lld\n", st[1].val);
            }
        }
    }
    return 0;
}

BZOJ4636 蒟蒻的序列 [2]

也还算一道经典题,因为 bzoj 挂了(虽然现在有替代品)所以放一个题面。

给定一个的序列,初始全为 \(0\),每次将一段区间中的所有元素与 \(k\) 取 max。求最后所有元素的值。\(1\le l,r\le 10^9\)

由于是离线问题,容易转化为从小到大排序后直接区间赋值。通过动态开点线段树可以做到 \(n\log V\)

或者你从大到小枚举然后再多记录一个标记表示有多少个数没有被改过。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
struct Segment{
    int lson, rson;//左孩子,右孩子
    ll val;//有多少被改过的数
    int tag;//懒惰标记
}tr[1000010];
int rt, tot;
int n;
ll ans;
struct node{
    int a, b;
    ll k;
}ASUKA[40010];
void push_down(int p, int l, int r) {
    if (tr[p].tag) {
        tr[p].tag = 0;
        int mid = (l + r) >> 1;
        if (!tr[p].lson) tr[p].lson = ++tot;
        tr[tr[p].lson].tag = 1;
        tr[tr[p].lson].val = mid - l + 1;
        if (!tr[p].rson) tr[p].rson = ++tot;
        tr[tr[p].rson].tag = 1;
        tr[tr[p].rson].val = r - mid;
    }
}
void work(int &p, int l, int r, int x, int y, ll k) {
    if (!p) {
        p = ++tot;
    }
    if (x <= l && r <= y) {
        ans += (r - l + 1 - tr[p].val) * k;
        tr[p].val = r - l + 1;
        tr[p].tag = 1;
        return;
    }
    push_down(p, l, r);
    int mid = (l + r) >> 1;
    if (x <= mid) work(tr[p].lson, l, mid, x, y, k);
    if (y > mid) work(tr[p].rson, mid + 1, r, x, y, k);
    tr[p].val = tr[tr[p].lson].val + tr[tr[p].rson].val;
}
bool cmp(node x, node y) {
    return x.k > y.k;
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> ASUKA[i].a >> ASUKA[i].b >> ASUKA[i].k;
    }
    sort(ASUKA + 1, ASUKA + n + 1, cmp);
    for (int i = 1; i <= n; i++) {
        work(rt, 1, 1000000000, ASUKA[i].a, ASUKA[i].b - 1, ASUKA[i].k);
    }
    cout << ans;
    return 0;
}

我们发现最后是全局查询,所以还可以标记永久话,然后遍历整颗树,同样还是动态开点。其实离线然后离散化也可以。

/*
Time : 2021/11/27 16:30
Author : Gemini7X
Problem : https://hydro.ac/d/bzoj/p/4636
*/

#include <iostream>

using namespace std;

typedef long long ll;

const int maxn = 40005, LG = 20;

int tag[maxn * LG], ls[maxn * LG], rs[maxn * LG], tot, rt;

void modify(int &p, int l, int r, int x, int y, int v) {
	if (l > y || r < x) return;
	if (!p) p = ++tot;
	if (x <= l && r <= y) return tag[p] = max(tag[p], v), void();
	int mid = (l + r) >> 1;
	modify(ls[p], l, mid, x, y, v); modify(rs[p], mid + 1, r, x, y, v);
}

ll solve(int p, int l, int r, int v) {
	int mid = (l + r) >> 1;
	ll ret = 0;
	if (ls[p]) ret += solve(ls[p], l, mid, max(v, tag[ls[p]]));
	else ret += 1ll * (mid - l + 1) * v;
	if (rs[p]) ret += solve(rs[p], mid + 1, r, max(v, tag[rs[p]]));
	else ret += 1ll * (r - mid) * v;
	return ret;
}

int main() {
	int N;
	scanf("%d", &N);
	while (N--) {
		int l, r, k;
		scanf("%d%d%d",&l, &r, &k); r--;
		modify(rt, 1, 1e9, l, r, k);
	}
	if (!rt) puts("0");
	else printf("%lld\n", solve(rt, 1, 1e9, tag[rt]));
	return 0;
}

[HAOI2015]树上操作 [2]

树链剖分模板题。。。

省选也出模板题,唉。。。

对于操作一,直接线段树单点修改即可。

对于操作二,根据同一子树节点编号连续的性质,直接区间修改即可。

对于操作三,就是查询1->x的答案(太模板了。。。)

#include <iostream>
#include <cstdio>
typedef long long ll;
using namespace std;
const ll N = 100010;
struct seg_tree{
    ll val, tag, l, r;
}st[4 * N];
struct node{
    ll pre, to;
}edge[2 * N];
ll head[N], tot;
ll n, m;
ll w[N], sz[N], pos[N], len, bl[N], dep[N], fa[N], lst[N];
namespace chain{
    void dfs1(ll x, ll f) {
        sz[x] = 1;
        for (ll i = head[x]; i; i = edge[i].pre) {
            ll y = edge[i].to;
            if (y == f) continue;
            dep[y] = dep[x] + 1;
            fa[y] = x;
            dfs1(y, x);
            sz[x] += sz[y];
        }
    }
    void dfs2(ll x, ll chain) {
        ll k = 0;
        lst[x] = pos[x] = ++len;
        bl[x] = chain;
        for (ll i = head[x]; i; i = edge[i].pre) {
            ll y = edge[i].to;
            if (dep[y] < dep[x]) continue;
            if (sz[y] > sz[k]) {
                k = y;
            }
        }
        if (k) dfs2(k, chain);
        lst[x] = max(lst[x], lst[k]);
        for (ll i = head[x]; i; i = edge[i].pre) {
            ll y = edge[i].to;
            if (dep[y] < dep[x] || k == y) continue;
            dfs2(y, y);
            lst[x] = max(lst[x], lst[y]);
        }
    }
}using namespace chain;
namespace segment_tree{
    void build(ll x, ll l, ll r) {
        st[x].l = l, st[x].r = r;
        if (l == r) return;
        ll mid = (l + r) >> 1;
        build(x << 1, l, mid);
        build(x << 1 | 1, mid + 1, r);
    }
    void update(ll x) {
        if (st[x].tag) {
            st[x << 1].tag += st[x].tag;
            st[x << 1 | 1].tag += st[x].tag;
            st[x << 1].val += (st[x << 1].r - st[x << 1].l + 1) * st[x].tag;
            st[x << 1 | 1].val += (st[x << 1 | 1].r - st[x << 1 | 1].l + 1) * st[x].tag;
            st[x].tag = 0;
        }
    }
    //单点修改 
    void change1(ll x, ll p, ll v) {
        ll l = st[x].l, r = st[x].r;
        if (l == r) {
            st[x].val += v;
            return;
        }
        ll mid = (l + r) >> 1;
        update(x);
        if (p <= mid) change1(x << 1, p, v);
        else change1(x << 1 | 1, p, v);
        st[x].val = st[x << 1].val + st[x << 1 | 1].val;
    }
    //区间修改 
    void change2(ll x, ll L, ll R, ll v) {
        ll l = st[x].l, r = st[x].r;
        if (r < L || l > R) return;
        if (L <= l && r <= R) {
            st[x].tag += v;
            st[x].val += (r - l + 1) * v;
            return;
        }
        update(x);
        change2(x << 1, L, R, v);
        change2(x << 1 | 1, L, R, v);
        st[x].val = st[x << 1].val + st[x << 1 | 1].val;
    }
    //区间求和 
    ll ask(ll x, ll L, ll R) {
        ll l = st[x].l, r = st[x].r;
        if (r < L || l > R) return 0;
        if (L <= l && r <= R) {
            return st[x].val;
        }
        update(x);
        return ask(x << 1, L, R) + ask(x << 1 | 1, L, R);
    }
    ll Qsum(ll x) {
        ll ret = 0;
        while (bl[x] != bl[1]) {
            ret += ask(1, pos[bl[x]], pos[x]);
            x = fa[bl[x]];
        }
        ret += ask(1, pos[bl[x]], pos[x]);
        return ret;
    }
}using namespace segment_tree;
void add(ll u, ll v) {
    edge[++tot] = node{head[u], v};
    head[u] = tot;
}
int main() {
    cin >> n >> m;
    for (ll i = 1; i <= n; i++) cin >> w[i];
    for (ll i = 1, a, b; i < n; i++) {
        cin >> a >> b;
        add(a, b);
        add(b, a);
    }
    dfs1(1, 0);
    dfs2(1, 1);
    build(1, 1, len);
    for (ll i = 1; i <= n; i++) {
        change1(1, pos[i], w[i]);
    }
    while (m--) {
        ll opt, x, a;
        cin >> opt >> x;
        if (opt == 1) {
            cin >> a;
            change1(1, pos[x], a);
        } else if (opt == 2) {
            cin >> a;
            change2(1, pos[x], lst[x], a);
        } else {
            cout << Qsum(x) << "\n";
        }
    }
    return 0;
}

CF833B The Bakery [2]

我们先来想 \(dp\) 部分。

考虑子状态 \(dp[i][j]\) 代表前 \(i\) 个值分成 \(j\) 段所得大的最大价值。

则有 \(dp[i][j]=max{dp[l][j-1]+val(l+1,i)},0 \leq l < i\)\(val(x,y)\) 代表 \([x,y]\) 之间不同的数的个数。

而这样写的时间复杂度是 \(O(n^2 \times k)\) 的。

我们考虑如何去优化它。

我们发现段数 \(j\) 的值只与 \(j-1\) 有关,所以我们不妨先枚举段数。

再来看我们的转移方程 “\(dp[i][j]=max{dp[l][j-1]+val(l+1,i)},0 \leq l < i\)\(val(x,y)\)”,其实就是取一个区间最大值。

不过这里有一个 \(val(l+1,i)\) 非常的讨厌。

我们来研究它的值。设当前值是 \(a[i]\)

因为现在枚举的位置已经到 \(i\),所以 \(val(l+1,i-1)\) 的值是我们已知的。

如果区间 \([l+1,i-1]\) 内已经有 \(a[i]\) 了,那么就不会产生贡献。我们记录 \(a[i]\) 这个值上一次出现的位置记为 \(pre[i]\)

那么只有属于 \([pre[i],i)\)\(l\)\(val(l+1,i)\) 相比 \(val(l+1,i-1)\) 才会加 \(1\)

接下来看如何用线段树维护。

其实很简单。

线段树上的节点 \([x, y]\)(这里用区间来代表节点)存的就是所有 \(l\) 属于 \([x,y]\) 的最大的 \(dp[l][j-1]+val(l+1,i)\)

考虑现在枚举到 \(i\),就把所有 \([pre[i],i)\) 的值加 \(1\)。转移就是 \([0,i)\) 中的最大值。

区间修改+区间查询搞定。

#include <iostream>
#include <cstdio>
using namespace std;
struct Segment{
    int val, tag;
}st[35010 << 2];
int n, k;
int a[35010];
int pre[35010], head[35010];
int dp[35010];
void push_down(int p) {
    if (st[p].tag) {
        st[p << 1].val += st[p].tag;
        st[p << 1].tag += st[p].tag;
        st[p << 1 | 1].val += st[p].tag;
        st[p << 1 | 1].tag += st[p].tag;
        st[p].tag = 0;
    }
}
void build(int p, int l, int r) {
    st[p].tag = 0;
    if (l == r) {
        st[p].val = dp[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(p << 1, l, mid);
    build(p << 1 | 1, mid + 1, r);
    st[p].val = max(st[p << 1].val, st[p << 1 | 1].val);
}
void change(int p, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
        st[p].tag++;
        st[p].val++;
        return;
    }
    push_down(p);
    int mid = (l + r) >> 1;
    if (L <= mid) change(p << 1, l, mid, L, R);
    if (mid < R) change(p << 1 | 1, mid + 1, r, L, R);
    st[p].val = max(st[p << 1].val, st[p << 1 | 1].val);
}
int ask(int p, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
        return st[p].val;
    }
    push_down(p);
    int mid = (l + r) >> 1, ret = 0;
    if (L <= mid) ret = max(ret, ask(p << 1, l, mid, L, R));
    if (mid < R) ret = max(ret, ask(p << 1 | 1, mid + 1, r, L, R));
    return ret;
}
int main() {
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    for (int i = 1; i <= n; i++) {
        pre[i] = head[a[i]];
        head[a[i]] = i;
    }
    
    for (int j = 1; j <= k; j++) {
        build(1, 0, n);
        for (int i = j; i <= n; i++) {
            change(1, 0, n, pre[i], i - 1);
            dp[i] = ask(1, 0, n, 0, i - 1);
        }
    }
    cout << dp[n];
    return 0;
}

[NOI2015]软件包管理器 [2]

我们将 \(i\) 所依赖的软件包连向 \(i\) 一条边,因为没有环而且仅依赖一个(只有一个父亲)所以这就形成了一棵树。

从根到节点 \(x\) 的路径上的点就是建 \(x\) 是所需要提前建好的。

节点 \(x\) 的子树即为删去 \(x\) 所需要提前删的。

这题线段树维护区间内有多少没被删的。

然后对于查询1查询路径 \(1->x\) 得到路径上已经安装的软件包的个数 \(num\),输出 \(x\) 的深度减去 \(num\)即可。修改将路径 \(1->x\) 上的所有数改成1(即安装)。

对于查询2,输出子树中有多少以安装的就行。修改就把子树全部改成0即可。

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 100010;
struct node{
    int pre, to;
}edge[N];
int head[N], tot;
struct seg_tree{
    int val, tag, l, r;
}st[4 * N];
int n, m;
int sz[N];
int depend[N], pos[N], dfn, lst[N], bl[N], dep[N];
int read() {
    int ret = 0, f = 1;
    char ch = getchar();
    while ('0' > ch || ch > '9') {
        if (ch == '-') {
            f = -1;
        }
        ch = getchar();
    }
    while ('0' <= ch && ch <= '9') {
        ret = (ret << 1) + (ret << 3) + ch - '0';
        ch = getchar();
    }
    return ret * f;
}
void add(int u, int v) {
    edge[++tot] = node{head[u], v};
    head[u] = tot;
}
void dfs1(int x) {
    sz[x] = 1;
    for (int i = head[x]; i; i = edge[i].pre) {
        int y = edge[i].to;
        dep[y] = dep[x] + 1;
        dfs1(y);
        sz[x] += sz[y];
    }
}
void dfs2(int x, int chain) {
    pos[x] = lst[x] = ++dfn;
    bl[x] = chain;
    int k = n;
    for (int i = head[x]; i; i = edge[i].pre) {
        int y = edge[i].to;
        if (sz[y] > sz[k]) {
            k = y;
        }
    }
    if (k != n) dfs2(k, chain), lst[x] = max(lst[x], lst[k]);
    for (int i = head[x]; i; i = edge[i].pre) {
        int y = edge[i].to;
        if (y != k) {
            dfs2(y, y);
            lst[x] = max(lst[x], lst[y]);
        }
    }
}
void build(int x, int l, int r) {
    st[x].l = l, st[x].r = r;
    st[x].tag = -1;
    if (l == r) return;
    int mid = (l + r) >> 1;
    build(x << 1, l, mid);
    build(x << 1 | 1, mid + 1, r);
}
void update(int p) {
    if (st[p].tag != -1) {
        st[p << 1].tag = st[p].tag;
        st[p << 1 | 1].tag = st[p].tag;
        st[p << 1].val = (st[p << 1].r - st[p << 1].l + 1) * st[p].tag;
        st[p << 1 | 1].val = (st[p << 1 | 1].r - st[p << 1 | 1].l + 1) * st[p].tag;
        st[p].tag = -1; 
    }
}
int ask(int p, int x, int y) {
    int l = st[p].l, r = st[p].r;
    if (x > r || y < l) return 0;
    if (x <= l && r <= y) {
        return st[p].val;
    }
    update(p);
    return ask(p << 1, x, y) + ask(p << 1 | 1, x, y);
}
void change(int p, int x, int y, int v) {
    int l = st[p].l, r = st[p].r;
    if (x > r || y < l) return;
    if (x <= l && r <= y) {
        st[p].tag = v;
        st[p].val = (r - l + 1) * v;
        return;
    }
    update(p);
    change(p << 1, x, y, v);
    change(p << 1 | 1, x, y, v);
    st[p].val = st[p << 1].val + st[p << 1 | 1].val;
}
int Q_sum(int u, int v) {
    int ret = 0;
    while (bl[u] != bl[v]) {
        if (dep[bl[u]] < dep[bl[v]]) swap(u, v);
        ret += ask(1, pos[bl[u]], pos[u]);
        u = depend[bl[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    ret += ask(1, pos[u], pos[v]);
    return ret;
}
void cc(int u, int v, int val) {
    while (bl[u] != bl[v]) {
        if (dep[bl[u]] < dep[bl[v]]) swap(u, v);
        change(1, pos[bl[u]], pos[u], val);
        u = depend[bl[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    change(1, pos[u], pos[v], val);
}
int main() {
    cin >> n;
    for (int i = 1; i < n; i++) {
        cin >> depend[i];
        add(depend[i], i);
    }
    dfs1(0);
    dfs2(0, 0);
    build(1, 1, dfn);
    cin >> m;
    while (m--) {
        string opt;
        int x;
        cin >> opt;
        x = read();
        if (opt == "install") {
            cout << dep[x] + 1 - Q_sum(0, x) << "\n";
            cc(0, x, 1);
        } else {
            cout << ask(1, pos[x], lst[x]) << "\n";
            change(1, pos[x], lst[x], 0);
        }
    }
    return 0;
}

NOIP2009 Hankson 的趣味题 [1]

题目所求即为 \(\gcd(x,a)=b,lcm(x,c)=d\)\(x\) 的个数。那么

所以 \(x\)\(d\) 的约数,然后直接暴力枚举即可。时间复杂度 \(O(nd(V)\log V)\) 完全可以通过。

但其实还有更高效的做法,对四个数分解质因数然后即可选出每个质因子的指数的范围即可计算答案,复杂度为质因数分解的复杂度,而这显然可以做到 \(soft(V^{\frac{1}{4}})\)

#include <iostream>
using namespace std;
//快读 
long long read() {
    long long ret = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-') f = -1;
        ch = getchar(); 
    }
    while (isdigit(ch)) {
        ret = ret * 10 + ch - '0';
        ch = getchar();
    }
    return ret * f;
}
//快写
void write(long long x) {
    if (x >= 10) write(x / 10);
    putchar(x % 10 + '0');
}
long long t, a, b, c, d, ans;
//求最大公约数(欧几里得算法)
long long gcd(long long x, long long y) {
    return y == 0 ? x : gcd(y, x % y);
}
//求最小公倍数
long long lcm(long long x, long long y) {
    return x * y / gcd(x, y);
}
int main() {
    t = read();
    while (t--) {
        ans = 0;
        a = read(), b = read(), c = read(), d = read();
        for (long long i = 1; i * i <= d; i++) {//从1到sqrt(d)
            if (d % i == 0) {
                if (gcd(a, i) == b && lcm(c, i) == d) ans++;
                if (d / i != i) {
                    if (gcd(a, d / i) == b && lcm(c, d / i) == d) ans++;
                }
            }
        }
        write(ans);
        putchar('\n');
    }
    return 0;
}

NOip2104联合权值 [1]

这道题刚开始看上去很怪异,就想着求一下深度再求一下LCA然后枚举每个满足条件的点对。不过后来我发现这是一棵树,两个点之间的距离为2的话只有一种可能:

A -- B -- C

(A, C)是有序点对。

所以我们只需要枚举中转点(B),然后对于每一个中转点会产生∑

很明显,这两个值都可以通过前缀和预处理出来。

之后对于每个中转点再预处理一个最大值个一个次大值得MAX = max{最大值 * 次大值}。

#include <iostream>
using namespace std;
const int MOD = 10007, N = 200010;
struct node{
    int pre, to;
}edge[2 * N];
int head[N], tot;
int s1[N], s2[N], ma1[N], ma2[N];
int n, maxi, sum;
int x[N], y[N], w[N];
void add(int u, int v) {
    //存边,预处理 
    edge[++tot] = node{head[u], v};
    head[u] = tot;
    s1[u] = (s1[u] + w[v]) % MOD;
    s2[u] = (s2[u] + w[v] * w[v]) % MOD;
    if (w[v] > ma1[u]) {
        ma2[u] = ma1[u];
        ma1[u] = w[v];
    } else if (w[v] > ma2[u]) {
        ma2[u] = w[v];
    }
}
int main() {
    cin >> n;
    for (int i = 1; i < n; i++) cin >> x[i] >> y[i];
    for (int i = 1; i <= n; i++) cin >> w[i];
    for (int i = 1; i < n; i++) add(x[i], y[i]), add(y[i], x[i]);
    for (int pos = 1; pos <= n; pos++) {
        sum = (sum + s1[pos] * s1[pos] - s2[pos]) % MOD;//注意要取模 
        maxi = max(maxi, ma1[pos] * ma2[pos]);
    }
    cout << maxi << " " << sum << endl;
    return 0;
}

[NOIP2014 提高组] 解方程 [3]

其实这道题就是求一个1元n次方程在区间[1, m]上的整数解。

我们枚举[1, m]上的所有整数,带进多项式中看看结果是不是0即可。

这里有一个技巧就是秦九韶算法,请读者自行查看学习。

时间复杂度O(n*m)。

然后你应该可以拿30分。

我们发现这些数都太大了,要开高精度。然后你愉快地拿了50分——复杂度O(nmlength)会爆炸。

这里我们考虑hash的思想,对结果取模(最好是一个很大的质数P),如果结果是零就说明这是一个解。

应为如果结果是零,那么要么这是一个解,要么结果是p的倍数(这样的概率很小,小到不需要考虑)。

如果你运气真的不好,就多试几个不同的质数。如果这还不行,你就可以去买彩票了

#include <iostream>
using namespace std;
const long long p = 1e9 + 7; 

long long n, m, a[110], ans[1000010], cnt;

long long read() {
    //读入时要取模
        long long ret = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-') f = -1;
        ch = getchar();
    }
    while (isdigit(ch)) {
        ret = (ret * 10 + ch - '0') % p;
        ch = getchar();
    }
    return ret * f;
}



int main() {
    cin >> n >> m;
    for (long long i = 0; i <= n; i++) {
        a[i] = read();//这里不能直接读入(这不是快读)
    }
    for (long long i = 1; i <= m; i++) {
        long long x = i, fx = 0;
                //秦九韶算法
        for (long long j = n; j >= 0; j--) {
            fx = ((a[j] + fx) * x) % p;
        }
        if (fx == 0) {
            ans[++cnt] = x;
        }
    }
    cout << cnt << endl;
    for (long long i = 1; i <= cnt; i++) {
        cout << ans[i] << endl;
    }
    return 0;
}

洛谷p1325雷达安装 [2]

很明显雷达应该安装在海岸线上
而为了满足一个点被覆盖那在区间[x - sqrt(d ^ 2 - y ^ 2), x + sqrt(d ^ 2 - y ^ 2)]之中必有一个雷达
现在就转换为一个区间覆盖问题:选尽量少的点使得每一个区间之内都有一个点
把这些区间按右端点排序,记录last为上次雷达安装的点,若一个区间的左端点>last,那这个区间就不能被之前的点覆盖
更新last=该区间的右端点(贪心选择,右端点有几率覆盖更多的区间),ans++

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

int n;
double d;
double x[1010], y[1010];
double l[1010], r[1010];
int ra[1010], ans;
double last = -1000000000.0;

bool cmp(int a, int b) {
    return r[a] < r[b];
}

int main() {
    cin >> n >> d;
    for (int i = 1; i <= n; i++) {
        cin >> x[i] >> y[i];
        if (y[i] > d) {
            cout << -1;
            return 0;
        }
        l[i] = x[i] - sqrt(d * d - y[i] * y[i]);
        r[i] = x[i] + sqrt(d * d - y[i] * y[i]);
    }
    for (int i = 1; i <= n; i++) ra[i] = i;
    sort(ra + 1, ra + n + 1, cmp);
    for (int i = 1; i <= n; i++) {
        if (l[ra[i]] > last) {
            ans++;
            last = r[ra[i]];
        }
    }
    cout << ans;
    return 0;
}

[noip 2014]飞扬的小鸟 [2]

题意:小鸟游戏,从最左边出发,到达最右边,假设,小鸟在(x,y),现在有两种走法,一种是点击K4,还有一种是保持不动,这两种情况分别可以让小鸟到(x+1,y+kX)。第二种情况是(x+1,y-Y)。地图中你有不可以到的地方,不能碰到水管和地面。现在求最小的点击次数,使得小鸟可以到达右边。(注意:不能超过最上面一行,点击以后,不会超过最上面一行)

题解:这道题刚开始看是最短路,可以用djstela去做,但是会超时。每个起点开始,发现会超时。仔细一看,发现可以用背包做。就是一个01背包套一个完全背包的问题。01背包就是往下掉,完全背包是可以往上点,两种背包套在一起。子状态dp(i,j)代表最少需要点击几次到达(i,j)这个位置,不能到达就设成正无穷大。转移方程dp(i,j)=min(dp(i-1,j+Y),dp(i,j-X)),把不能到的地方在设成正无穷。如果不能到达,就从后往前找,找到第一个可以到的列,就是最远的列far,然后记录一下哪些列有水管,输出[1, far]里的水管即可。

#include <iostream>
#include <cstring>
using namespace std;
int n, m, k;
int x[10010], y[10010];
int dp[10010][2010];
int low[10010], high[10010];
bool flag[10010];
int ans = 0x7f7f7f7f;
int read() {
    int ret = 0, f = 1;
    char ch = getchar();
    while (!isdigit(ch)) {
        if (ch == '-') {
            f = -1;
        }
        ch = getchar();
    }
    while (isdigit(ch)) {
        ret = ret * 10 + ch - '0';
        ch = getchar();
    }
    return ret * f;
}
int main() {
    n = read(), m = read(), k = read();
    for (int i = 1; i <= n; i++) {
        x[i] = read();
        y[i] = read();
    }
    for (int i = 1; i <= n; i++) {
        high[i] = m + 1;
        low[i] = 0;
    }
    for (int i = 1, p; i <= k; i++) {
        p = read(), low[p] = read(), high[p] = read();
        flag[p] = 1;
    }
    memset(dp, 0x3f, sizeof(dp));
    for (int i = 1; i <= m; i++) {
        dp[0][i] = 0;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = x[i] + 1; j <= m + x[i]; j++) {
            dp[i][j] = min(dp[i][j - x[i]] + 1, dp[i - 1][j - x[i]] + 1);
        }
        for (int j = m + 1; j <= x[i] + m; j++) {
            dp[i][m] = min(dp[i][j], dp[i][m]);
        }
        for (int j = 1; j + y[i] <= m; j++) {
            dp[i][j] = min(dp[i][j], dp[i - 1][j + y[i]]);
        }
        for (int j = 1; j <= low[i]; j++) {
            dp[i][j] = 0x3f3f3f3f;
        }
        for (int j = high[i]; j <= m; j++) {
            dp[i][j] = 0x3f3f3f3f;
        }
    }
    for (int i = 1; i <= m; i++) {
        ans = min(ans, dp[n][i]);
    }
    if (ans < 0x3f3f3f3f) {
        cout << 1 << endl;
        cout << ans;
    } else {
        ans = 0;
        cout << 0 << endl;
        int far = 0;
        for (int i = n; i >= 0; i--) {
            for (int j = 1; j <= m; j++) {
                if (dp[i][j] < 0x3f3f3f3f) {
                    far = i;
                    break;
                }
            }
            if (far) break;
        }
        for (int i = far; i; i--) {
            if (flag[i]) {
                ans++;
            }
        }
        cout << ans;
    }
    return 0;
}

[UVA644] Immediate Decodability [1]

题意:给出一些数字串,判断是否有一个数字串是另一个串的前缀。

这题真的可以算是Trie树的一道模板题了。

先把Trie树建好,建树的时候记录一个sum表示一个节点有多少个串会包含此节点,然后再记录一个end表示这个节点是不是一个串的结尾。

然后dfs/bfs遍历整个Trie树若一个节点x满足end[x]=true && sum[x]>=2则题目条件成立。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
string s;
int trie[1000][2];
int tot, sum[1000];
bool ennd[1000], ans;
void insert(string t) {
    int p = 1;
    for (int i = 0; i < (int)t.length(); i++) {
        int k = t[i] - '0';
        sum[p]++;
        if (!trie[p][k]) trie[p][k] = ++tot;
        p = trie[p][k];
    }
    sum[p]++;
    ennd[p] = 1;
}
void dfs(int x) {
    if (ennd[x] && sum[x] >= 2) ans = 1;
    if (trie[x][0]) {
        dfs(trie[x][0]);
    }
    if (trie[x][1]) {
        dfs(trie[x][1]);
    }
}
int case_num;
int main() {
    while (cin >> s) {
        case_num++;
        if (s[0] == '9') continue;
        ans = 0;
        tot = 1;
        memset(ennd, 0, sizeof(ennd));
        memset(sum, 0, sizeof(sum));
        memset(trie, 0, sizeof(trie));
        insert(s);
        while (cin >> s) {
            if (s[0] == '9') break;
            insert(s);
        }
        dfs(1);
        if (ans) printf("Set %d is not immediately decodable\n", case_num);
        else printf("Set %d is immediately decodable\n", case_num);
    }
    return 0;
}

[USACO06FEB]Stall Reservations S [2]

题目描述:

约翰的N(l<N< 50000)头奶牛实在是太难伺候了,她们甚至有自己独特的产奶时段.当 然对于某一头奶牛,她每天的产奶时段是固定的,为时间段A到B包括时间段A和时间段B.显然,约翰必须开发一个调控系统来决定每头奶牛应该被安排到哪个牛棚去挤 奶,因为奶牛们显然不希望在挤奶时被其它奶牛看见.

约翰希望你帮他计算一下:如果要满足奶牛们的要求,并且每天每头奶牛都要被挤过奶,至少需要多少牛棚 •每头牛应该在哪个牛棚被挤奶。如果有多种答案,你只需任意一种即可。

题解:

网上很多人都是用贪心,我这里给出一种另类做法。

显然牛棚是有一个上限的,也就是n。

我们先假设有n个牛棚。把奶牛的开始和结束时间都放到一个数组并打上不同的标记,把他们按时间排序(时间相同的开始时间要在结束时间前)。

首先刚开始这n个牛棚都是空闲的。

遇到一个开始时间空闲牛棚数就减一,遇到一个结束时间空闲牛棚数就加一。

我们找出在每次操作后空闲牛棚的最小值,显然这些牛棚从未被用过,是不需要的,要用的就是剩下的牛棚。

我们知道了最小的牛棚数构造就很简单了。

我们先把所有空闲的牛棚放进一个队列里,每次要用就从队列里去一个,每次用完就放回队列里。

不知来源的题目——复制黏贴 [2]

就是一个字符串s(只包含小写字母),现在可以复制黏贴从位置i到位置j的所有字符(位置x为第x个字符与第x+1个字符中间的空隙)放到位置k。  复制黏贴的总长度不超过m,求n次复制黏贴后前L个字符分别是什么。

\(L \le 200,m \le 10^9, n \le 10^5, |s| \le 10^5\)

6 100
jjooii
3
5 6 2
4 6 1
1 2 3
joioji

我们发现如果暴力去模拟的话肯定会TLE(m <= 10^9),所以肯定不能暴力。我们再看要求输出的内容:前L(L <= 200)个字符,很舒服,所以应该就是从这里入手。首先我们想对于每一个i(1 <= i <= L),我们开始分类讨论:现在的位置记为P,刚开始P = i;从第n条指令到第1条:j从n到1。1,若P <= c[j] P肯定不会改变(在前面不会受到影响)2,若c[j] < p && p <= c[j] + b[j] - a[j], 则P += a[j] - cj3,若P > c[j] + b[j] - a[j] 则p -= b[j] - a[j](P属于往后挪的部分)

#include <bits/stdc++.h>
using namespace std;
const int SIZE = 2 * 1e5 + 10;
int L, m, n, a[SIZE], b[SIZE], c[SIZE];
char s[SIZE];
int main() {
    cin >> L >> m;
    scanf("%s", s + 1);
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i] >> b[i] >> c[i];
    for (int i = 1; i <= L; i++) {
        int P = i;
        for (int j = n; j >= 1; j--) {
            if (P <= c[j]) {
                P = P;
            } else if (c[j] < P && P <= c[j] + b[j] - a[j]) {
                P += a[j] - c[j];
            } else {
                P -= (b[j] - a[j]);
            }
        }
        cout << s[P];
    }
    puts("");
}

Dynamic Ranking [3]

动态区间第k小。写一个数据结构,支持单点修改,查询区间第k小。块状链表

Solution 1
我们回想一下静态第k小是怎么做的,其实就是查询前缀和。

而这道题如果还按之前的写法,修改的时间复杂度是\(O(N)\)的,显然不优。

这时我们就要换一种思路,那就是用树状数组维护前缀和。

这时树状数组的每个节点维护的不是一个值了,而是一棵主席树(虽然普通的权值线段树也行)。

对于修改操作,和普通的树状数组一样找到所有要改的节点,让后再它们所代表的主席树上修改,历史版本就是这个节点上一次修改的值。如下:

void change(int x, int v) {
    int tmp = a[x];
    while (x <= n) {
        ins(rt[x], rt[x], 1, nn, tmp, v);
        x += lowbit(x);
    }
}

对于查询,我们首先要预处理出所有要选的主席树(类似树状数组的查询),之后在那些主席树上查询即可。

查询的思路同静态第k小。

时间
复杂度:\(O(N \times \log^2{N})\)

代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 4000010;
struct Chairman_Tree{
    int ls, rs;
    int val;
}tr[N * 30];
int rt[N], tot;
int n, m;
int a[N], b[N], nn;
int L[N], R[N], K[N];
char opt[N];
int top1, top2;
int sk1[N], sk2[N];
inline int lowbit(int x) {
    return x & -x;
}
void ins(int &cur, int pre, int l, int r, int pos, int v) {
    cur = ++tot, tr[cur] = tr[pre], tr[cur].val += v;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (pos <= mid) ins(tr[cur].ls, tr[pre].ls, l, mid, pos, v);
    else ins(tr[cur].rs, tr[pre].rs, mid + 1, r, pos, v);
}
void change(int x, int v) {
    int tmp = a[x];
    while (x <= n) {
        ins(rt[x], rt[x], 1, nn, tmp, v);
        x += lowbit(x);
    }
}
void init(int x, int y) {
    top1 = top2 = 0;
    while (x) {
        sk1[++top1] = rt[x];
        x -= lowbit(x);
    }
    while (y) {
        sk2[++top2] = rt[y];
        y -= lowbit(y);
    }
}
int query(int l, int r, int k) {
    if (l == r) return l;
    int num = 0;
    for (int i = 1; i <= top1; i++) num -= tr[tr[sk1[i]].ls].val;
    for (int i = 1; i <= top2; i++) num += tr[tr[sk2[i]].ls].val;
    int mid = (l + r) >> 1;
    if (k <= num) {
        for (int i = 1; i <= top1; i++) sk1[i] = tr[sk1[i]].ls;
        for (int i = 1; i <= top2; i++) sk2[i] = tr[sk2[i]].ls;
        return query(l, mid, k);
    } else {
        for (int i = 1; i <= top1; i++) sk1[i] = tr[sk1[i]].rs;
        for (int i = 1; i <= top2; i++) sk2[i] = tr[sk2[i]].rs;
        return query(mid + 1, r, k - num);
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b[++nn] = a[i];
    }
    for (int i = 1; i <= m; i++) {
        cin >> opt[i];
        scanf("%d%d", &L[i], &R[i]);
        if (opt[i] == 'Q') {
            scanf("%d", &K[i]);
        } else {
            b[++nn] = R[i];
        }
    }
    sort(b + 1, b + nn + 1);
    nn = unique(b + 1, b + nn + 1) - b - 1;
    for (int i = 1; i <= n; i++) {
        int p = lower_bound(b + 1, b + nn + 1, a[i]) - b;
        a[i] = p;
    }
    for (int i = 1; i <= n; i++) {
        change(i, 1);
    }
    for (int i = 1; i <= m; i++) {
        if (opt[i] == 'C') {
            change(L[i], -1);
            a[L[i]] = lower_bound(b + 1, b + nn + 1, R[i]) - b;
            change(L[i], 1);
        } else {
            init(L[i] - 1, R[i]);
            printf("%d\n", b[query(1, nn, K[i])]);
        }
    }
    return 0;
}

显然因为历史版本是自己所以此题维护权值线段树即可。

可如果要求查看历史版本(比如回到某一次修改前)就必须要用主席树了。

Solution 2
区间第 k 小还可以在平衡树上二分,这题修改用平衡树也很好实现(删一个点加一个点),那么就变成一个模板题了。

Solution 3
整体二分,二分答案,然后按时间更新答案。也差不多是模板。

[POI2014]KUR-Couriers [3]

题目传送门

题意
给一个数列,每次询问一个区间内有没有一个数出现次数超过一半

Solution
主席树入门题。

显然一个区间内出现次数超过一半的数最多只有一个。

查询类似普通的区间第k大问题。

判断左区间内出现的数的次数,如果大于一半,则左区间内有可能有答案(此时右区间肯定没有),如果右区间大于一半同理。

如果都不大于一半则显然没有答案。

#include <iostream>
#include <cstdio>
using namespace std;
const int N = 500010;
struct Chairman_Tree{
    int ls, rs;
    int val;
}tr[N * 30];
int rt[N], tot;
int n, m;
int a[N];
void ins(int &cur, int pre, int l, int r, int pos) {
    cur = ++tot, tr[cur] = tr[pre], tr[cur].val++;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (pos <= mid) ins(tr[cur].ls, tr[pre].ls, l, mid, pos);
    else ins(tr[cur].rs, tr[pre].rs, mid + 1, r, pos);
}
int ask(int p, int q, int l, int r, int k) {
    if (l == r) return l;
    int mid = (l + r) >> 1;
    int num_l = tr[tr[q].ls].val - tr[tr[p].ls].val;
    int num_r = tr[tr[q].rs].val - tr[tr[p].rs].val;
    if (num_l > k) return ask(tr[p].ls, tr[q].ls, l, mid, k);
    else if (num_r > k) return ask(tr[p].rs, tr[q].rs, mid + 1, r, k);
    else return 0;
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        ins(rt[i], rt[i - 1], 1, n, a[i]);
    }
    while (m--) {
        int l, r;
        scanf("%d%d", &l, &r);
        printf("%d\n", ask(rt[l - 1], rt[r], 1, n, (r - l + 1) >> 1));
    }
    return 0;
}
posted @ 2021-11-21 15:49  Gemini7X  阅读(60)  评论(0编辑  收藏  举报
Title