人生难得几回搏,|

bryce_yyds

园龄:2年6个月粉丝:7关注:12

2024-10-12 16:55阅读: 22评论: 0推荐: 0

2024 停课做题总结

[ABC372D] Buildings

思路

正着做不方便,倒着用单调栈做一遍就行了。

代码

#include<iostream>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int n, ans[N];
int h[N], stk[N], top;

int main(){
    n = read();
    for (int i = 1; i <= n; i++) h[i] = read();
    h[0] = 0x7fffffff;
    for (int i = n; i >= 1; i--){
        ans[i] = top;
        while (h[stk[top]] < h[i]) top--;
        stk[++top] = i;
    }
    for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
    return 0;
}

[ABC372E] K-th Largest Connected Components

思路

注意到,k10,所以暴力维护前 10 个点,然后使用并查集实现连边,利用归并排序实现前 10 个点,注意:自己也算能走到自己。

代码

#include<iostream>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10, M = 15;
int n, q;
int fa[N], e[N][M], a[N], len[N];
int find(int x){
    return (x == fa[x] ? fa[x] : fa[x] = find(fa[x]));
}
void merge(int x, int y){
    int fx = find(x), fy = find(y);
    if (fx == fy) return;
    fa[fx] = fy;
    for (int i = 1, j = 1, k = 1; k <= min(10, len[fx] + len[fy]); k++){
        if (e[fx][i] > e[fy][j]) a[k] = e[fx][i++];
        else a[k] = e[fy][j++];
    }
    len[fy] = min(10, len[fx] + len[fy]);
    for (int i = 1; i <= len[fy]; i++) e[fy][i] = a[i];
}

int main(){
    n = read(), q = read();
    for (int i = 1; i <= n; i++) fa[i] = i, e[i][1] = i, len[i] = 1;
    for (int i = 1; i <= q; i++){
        int opt = read(), u = read(), v = read();
        if (opt == 1) merge(u, v);
        else{
            int x = find(u);
            cout << (e[x][v] == 0 ? -1 : e[x][v]) << '\n';
        }
    }
    return 0;
}

[ABC372F] Teleporting Takahashi 2

思路

好题!看完题目,可以很快想到一个 dp,但是空间和时间复杂度显然都不允许,于是集中注意力,发现 m50,这意味着只有不到 100 个点转移时会变,那么,就把两两之间有多余连边的点中间没有多余连接的点看成一条边,边权为所有边的总和,接着 dp

考虑怎么统计答案,很显然,有多余连接的点可以直接求,而没有多余连接的点就是它所在的有向边指向的点的 ki 步的方案数(i 为自己到达有向边指向的点的距离)。

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10, M = 55, K = 2e5 + 10, mod = 998244353;
int n, m, k, ans;
int x[M], y[M];
bool vis[N];
struct edge{
    int v, w, nxt;
}e[N];
int head[N], cnt, id[N], tot;
void add(int u, int v, int w){
    e[++cnt] = (edge){v, w, head[u]};
    head[u] = cnt;
}
int dp[M << 2][K << 2];

signed main(){
    n = read(), m = read(), k = read();
    for (int i = 1; i <= m; i++) x[i] = read(), y[i] = read(), add((id[x[i]] == 0 ? id[x[i]] = ++tot : id[x[i]]), (id[y[i]] == 0 ? id[y[i]] = ++tot : id[y[i]]), 1);
    for (int i = 1, last = 1; i <= n; i++){
        bool ok = 0;
        vis[i] = 1;
        for (int j = 1; j <= m; j++) if (x[j] == i || y[j] == i) ok = 1;
        if (ok && i != 1) add((id[last] == 0 ? id[last] = ++tot : id[last]), (id[i] == 0 ? id[i] = ++tot : id[i]), i - last), vis[i] = 0, last = i;
        if (i == n){
            if (last != 1) add((id[last] == 0 ? id[last] = ++tot : id[last]), (id[1] == 0 ? id[1] = ++tot : id[1]), n - last + 1);
            else add((id[last] == 0 ? id[last] = ++tot : id[last]), 1, n);
        }
    }
    vis[1] = 0;
    dp[(id[1] == 0 ? id[1] = ++tot : id[1])][0] = 1;
    for (int i = 0; i <= k; i++){
        for (int u = 1; u <= tot; u++){
            for (int j = head[u]; j; j = e[j].nxt){
                int v = e[j].v, w = e[j].w;
                if (i + w <= k) dp[v][i + w] = (dp[v][i + w] + dp[u][i]) % mod;
            }
        }
    }
    for (int i = 1, last = 1; i <= n; i++){
        if (!vis[i]) ans = (ans + dp[(id[i] == 0 ? id[i] = ++tot : id[i])][k]) % mod, last = i;
        else{
            ans = (ans + dp[(id[last] == 0 ? id[last] = ++tot : id[last])][k - (i - last)]) % mod;
        }
    }
    cout << ans;
    return 0;
}

Invertible Bracket Sequences

思路

考虑一个合法的括号序列是怎样的,将 "(" 抽象成 1,将 ")" 抽象成 1,那么整个序列的和为 0,同时每个位置的前缀和 0

枚举一个反转的左端点,看有多少个右端点能与它反转后符合题目条件。设左端点为 l,右端点为 r,前缀和为 pre,反转区间的和为 pre,那么 prer=prel1+pre ,反转过后,prer0,prepre,所以 prel1pre,代入上式,2prel1prer,且 lr 之间的所有 prei 都要满足上式,而一个反转区间的和为 0,即 pre=prel1,所以倒着做,存下等于 prel1pre 的位置,对于一个 l,求 lpre 的位置之间的最大值,如果最大值满足 prel1maxn 那么这个位置一定可以,于是二分求最多的 r

代码

#include<iostream>
#include<cstring>
#include<vector>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int T, n;
long long ans;
char s[N];
int a[N], pre[N], t[N << 2];
void build(int now, int l, int r){
    if (l == r){
        t[now] = pre[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(now << 1, l, mid);
    build(now << 1 | 1, mid + 1, r);
    t[now] = max(t[now << 1], t[now << 1 | 1]);
}
int query(int now, int l, int r, int x, int y){
    if (x <= l && r <= y) return t[now];
    int mid = (l + r) >> 1, res = 0;
    if (x <= mid) res = max(res, query(now << 1, l, mid, x, y));
    if (mid + 1 <= y) res = max(res, query(now << 1 | 1, mid + 1, r, x, y));
    return res;
}
vector<int> cnt[N];

signed main(){
    T = read();
    while (T--){
        cin >> s + 1;
        n = strlen(s + 1);
        for (int i = 1; i <= n; i++) a[i] = (s[i] == '(' ? 1 : -1), pre[i] = pre[i - 1] + a[i];
        build(1, 1, n);
        for (int i = n; i >= 1; i--){
            int l = 0, r = cnt[pre[i]].size() - 1, k = r + 1;
            while (l <= r){
                int mid = (l + r) >> 1, res = query(1, 1, n, i, cnt[pre[i]][mid]);
                if (2 * pre[i] >= res){
                    r = mid - 1;
                    k = mid;
                }else{
                    l = mid + 1;
                }
            }
            ans += cnt[pre[i]].size() - k;
            cnt[pre[i]].emplace_back(i);
        }
        cout << ans << '\n';
        for (int i = 0; i <= n; i++) cnt[i].clear();
        ans = 0;
    }
    return 0;
}

Beauty of the mountains

思路

首先,将整个有雪与无雪的差记录下来,记为 sum,设有雪的系数为 1,无雪的系数为 1,那么对于每一个 k×k 的区块做一个前缀和,即为这一块增加值所对应的系数,设系数为 ai,每个区块增加 bi,由于 ai 已知,所以使用裴蜀定理,必有一个序列 bi 满足 a1b1+a2b2++anbn=gcd(a1,a2,,an),所以只要满足 gcd(a1,a2,,an)|sum 即可。

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 5e2 + 10;
int T, n, m, k, ans;
int a[N][N], b[N][N], s[N][N], sum;
int gcd(int a, int b){
    return (b == 0 ? a : gcd(b, a % b));
}

signed main(){
    T = read();
    while (T--){
        n = read(), m = read(), k = read();
        sum = ans = 0;
        for (int i = 0; i <= n; i++)
            for (int j = 0; j <= n; j++) s[i][j] = 0;
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++) a[i][j] = read();
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++){
                char c;cin >> c;
                b[i][j] = (c == '1' ? 1 : -1);
                sum += b[i][j] * a[i][j];
            }
        if (sum == 0){
            cout << "YES\n";
            continue;
        }
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= m; j++) s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + b[i][j];
        bool f = 0;
        for (int i = k; i <= n; i++)
            for (int j = k; j <= m; j++){
                int res = s[i][j] - s[i][j - k] - s[i - k][j] + s[i - k][j - k];
                f = 1;
                if (!f) ans = res;
                ans = gcd(ans, res);
            }
        if (ans == 0){
            cout <<"NO\n";
            continue;
        }
        if (-sum % ans == 0) cout << "YES\n";
        else cout << "NO\n";
    }
    return 0;
}

[ABC268E] Chinese Restaurant (Three-Star Version)

思路

由于沮丧值与菜的移动的次数有关,所以求出每个移动次数的贡献,而每个移动次数的贡献又由前一个移动次数的贡献转移而来。

所以,当一个菜移动时,会对一个人产生连续的沮丧值增加或减少,特别地,当 n 为奇数时,会有沮丧值不变的情况,这些改变沮丧值走向的端点可以分类讨论快速求出,于是考虑差分,当第 i 个人的菜靠近他时,沮丧值会 1,反之会 +1,有时还会不变,即为 0,对差分数组求前缀和即为当前这个菜移动此时次数对沮丧值增加或减少了多少。那么先求出初始状态的总的沮丧值,由于 +11 都是连续的,可以用线段树实现,然后在 n 为奇数时特判 0 的情况,最后统计答案时对每个移动次数做一次前缀和,即为总的沮丧值改变量,用初始状态的沮丧值加上它求最小值即可。

代码

#include<iostream>
#include<cmath>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int n, sum, ans = 0;
int a[N];
int get(int a, int b){
    if (a < 0) return (a % b == 0 ? a / b : a / b - 1);
    return a / b;
}
struct tree{
    int sum, tag;
}t[N << 2];
void pushup(int now){
    t[now].sum = t[now << 1].sum + t[now << 1 | 1].sum;
}
void pushdown(int now, int l, int r){
    int mid = (l + r) >> 1;
    t[now << 1].sum += t[now].tag * (mid - l + 1), t[now << 1 | 1].sum += t[now].tag * (r - mid);
    t[now << 1].tag += t[now].tag, t[now << 1 | 1].tag += t[now].tag;
    t[now].tag = 0;
}
void modify(int now, int l, int r, int x, int y, int k){
    if (x <= l && r <= y){
        t[now].sum += k * (r - l + 1);
        t[now].tag += k;
        return;
    }
    pushdown(now, l, r);
    int mid = (l + r) >> 1;
    if (x <= mid) modify(now << 1, l, mid, x, y, k);
    if (mid + 1 <= y) modify(now << 1 | 1, mid + 1, r, x, y, k);
    pushup(now);
}
int query(int now, int l, int r, int x, int y){
    if (x <= l && r <= y){
        return t[now].sum;
    }
    pushdown(now, l, r);
    int mid = (l + r) >> 1, res = 0;
    if (x <= mid) res += query(now << 1, l, mid, x, y);
    if (mid + 1 <= y) res += query(now << 1 | 1, mid + 1, r, x, y);
    return res;
}

signed main(){
    n = read();
    for (int i = 0; i < n; i++){
        a[i] = read();
        int x = min(i, a[i]), y = max(i, a[i]);
        sum += min(y - x, x + n - y);
    }
    for (int i = 0; i < n; i++){
        if (n & 1){
            if (i <= a[i]){
                int k = get(n + 2 * i - 2 * a[i], 2), g = get(3 * n + 2 * i - 2 * a[i], 2);
                if (1 <= min(k, n)) modify(1, 1, n, 1, min(k, n), 1);
                if (1 <= k + 1 && k + 1 <= n) modify(1, 1, n, k + 1, k + 1, 0);
                if (max(k + 2, 1ll) <= min(n + i - a[i], n)) modify(1, 1, n, max(k + 2, 1ll), min(n + i - a[i], n), -1);
                if (max(n + i - a[i] + 1, 1ll) <= min(g, n)) modify(1, 1, n, max(n + i - a[i] + 1, 1ll), min(g, n), 1);
                if (1 <= g + 1 && g + 1 <= n) modify(1, 1, n, g + 1, g + 1, 0);
                if (max(g + 2, 1ll) <= n) modify(1, 1, n, max(g + 2, 1ll), n, -1);
            }else{
                int k = n / 2, g = get(2 * i - n - 2 * a[i], 2);
                if (1 <= min(g, n)) modify(1, 1, n, 1, min(g, n), 1);
                if (1 <= g + 1 && g + 1 <= n) modify(1, 1, n, g + 1, g + 1, 0);
                if (max(g + 2, 1ll) <= min(i - a[i], n)) modify(1, 1, n, max(g + 2, 1ll), min(i - a[i], n), -1);
                if (max(i - a[i] + 1, 1ll) <= min(i - a[i] + k, n)) modify(1, 1, n, max(i - a[i] + 1, 1ll), min(i - a[i] + k, n), 1);
                if (1 <= i - a[i] + k + 1 && i - a[i] + k + 1 <= n) modify(1, 1, n, i - a[i] + k + 1, i - a[i] + k + 1, 0);
                if (max(i - a[i] + k + 2, 1ll) <= n) modify(1, 1, n, max(i - a[i] + k + 2, 1ll), n, -1);
            }
        }else{
            if (i <= a[i]){
                int k = get(n + 2 * i - 2 * a[i], 2), g = get(3 * n + 2 * i - 2 * a[i], 2);
                if (1 <= min(k, n)) modify(1, 1, n, 1, min(k, n), 1);
                if (max(k + 1, 1ll) <= min(n + i - a[i], n)) modify(1, 1, n, max(k + 1, 1ll), min(n + i - a[i], n), -1);
                if (max(n + i - a[i] + 1, 1ll) <= min(g, n)) modify(1, 1, n, max(n + i - a[i] + 1, 1ll), min(g, n), 1);
                if (max(g + 1, 1ll) <= n) modify(1, 1, n, max(g + 1, 1ll), n, -1);
            }else{
                int k = n / 2, g = get(2 * i - n - 2 * a[i], 2);
                if (1 <= min(g, n)) modify(1, 1, n, 1, min(g, n), 1);
                if (max(g + 1, 1ll) <= min(i - a[i], n)) modify(1, 1, n, max(g + 1, 1ll), min(i - a[i], n), -1);
                if (max(i - a[i] + 1, 1ll) <= min(i - a[i] + k, n)) modify(1, 1, n, max(i - a[i] + 1, 1ll), min(i - a[i] + k, n), 1);
                if (max(i - a[i] + k + 1, 1ll) <= n) modify(1, 1, n, max(i - a[i] + k + 1, 1ll), n, -1);
            }
        }
    }
    for (int i = 1; i <= n; i++) ans = min(ans, query(1, 1, n, 1, i));
    cout << min(sum, sum + ans);
    return 0;
}

P11188 「KDOI-10」商店砍价

思路

没有直接想出 dp 做法,看到数据 vi105,直接就想到当剩余的 n>5×105 时,绝对不会用 n 的代价去消除所有数,那么直接从 1 枚举到 5×105 表示最后留下来一次清除的数,用子序列自动机判一遍是否是子序列,直接算答案即可。

代码

#include<iostream>
#include<cstring>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1e5 + 10, M = 11;
int id, T, n, sum, ans;
char c[N];
int a[N], v[M], dp[N][M], f[7];
bool check(int cnt){
    for (int i = cnt, pos = 0; i >= 1; i--){
        int p = dp[pos][f[i]];
        if (p == 0) return 0;
        pos = p;
    }
    return 1;
}

signed main(){
    id = read(), T = read();
    while (T--){
        cin >> c + 1;
        n = strlen(c + 1);
        for (int i = 1; i <= 9; i++) v[i] = read();
        for (int i = 1; i <= n; i++) a[i] = (c[i] ^ 48), sum += v[a[i]];
        ans = sum;
        for (int i = n; i >= 0; i--){
            for (int j = 1; j <= 9; j++){
                if (a[i + 1] == j) dp[i][j] = i + 1;
                else dp[i][j] = dp[i + 1][j];
            }
        }
        for (int i = 0; i <= 500000; i++){
            int x = i, cnt = 0, s = sum;
            while (x){
                f[++cnt] = x % 10;
                s -= v[f[cnt]];
                x /= 10;
            }
            if (!check(cnt)) continue;
            ans = min(ans, s + i);
        }
        cout << ans << endl;
        sum = 0, ans = 0;
        for (int i = 0; i <= n; i++)
            for (int j = 1; j <= 9; j++) dp[i][j] = 0;
        for (int i = 1; i <= n; i++) a[i] = 0;
        for (int i = 1; i <= 9; i++) v[i] = 0;
    }
    return 0;
}

Bananas in a Microwave

思路

直接模拟复杂度会爆掉,于是考虑 dp,设 fi 表示最早出现的操作,每次操作都用 gi 转移,设 gi 表示在第 k 次操作时,能通过几次到 i,这样就完美地解决了限制 y 的问题。那么 gi 的转移方程就为 gi+x=min(gi+x,gi+1)gi×x=min(gi×x,gi+1),最后用 g 更新 f 即可。

代码

#include<iostream>
#include<cmath>
#define int long long
#define INF 0x3f3f3f3f

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1e5 + 10;
int n, m;
int f[N], g[N];

signed main(){
    n = read(), m = read();
    for (int i = 1; i <= m; i++) f[i] = INF;
    for (int i = 1; i <= n; i++){
        int t = read(), x = read(), y = read();
        double x0 = 1.0 * x / 100000;
        for (int j = 1; j <= m; j++)
            if (f[j] != INF) g[j] = 0;
            else g[j] = INF;
        if (t == 1){
            for (int j = 0; j <= m; j++){
                int k = ceil(j + x0);
                if (k <= m && g[j] != INF)
                    g[k] = min(g[k], g[j] + 1);
            }
        }else{
            for (int j = 1; j <= m; j++){
                int k = ceil(1.0 * j * 1.0 * x / 100000);
                if (k <= m && g[j] != INF)
                    g[k] = min(g[k], g[j] + 1);
            }
        }
        for (int j = 1; j <= m; j++)
            if (f[j] == INF && g[j] <= y) f[j] = i;
    }
    for (int i = 1; i <= m; i++)
        if (f[i] == INF) cout << -1 << ' ';
        else cout << f[i] << ' ';
    return 0;
}

[ABC174F] Range Set Query

思路

莫队板子。莫队就是一个将询问离线下来的操作,然后用左右端点扩展或缩小区间。

代码

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

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 5e5 + 10;
int n, m, block, ans[N];
int a[N], b[N];
struct node{
    int l, r, id;
    bool operator < (const node &w) const{
        return b[l] ^ b[w.l] ? b[l] < b[w.l] : (b[l] & 1 ? r < w.r : r > w.r);
    }
}q[N];
int L = 1, R, cnt[N];

int main(){
    n = read(), m = read(), block = sqrt(n);
    for (int i = 1; i <= n; i++) a[i] = read(), b[i] = (i - 1) / block + 1;
    for (int i = 1; i <= m; i++) q[i].l = read(), q[i].r = read(), q[i].id = i;
    sort(q + 1, q + m + 1);
    int res = 0;
    for (int i = 1; i <= m; i++){
        while (L > q[i].l) res += !cnt[a[--L]]++;
        while (R < q[i].r) res += !cnt[a[++R]]++;
        while (L < q[i].l) res -= !--cnt[a[L++]];
        while (R > q[i].r) res -= !--cnt[a[R--]];
        ans[q[i].id] = res;
    }
    for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
    return 0;
}

P4462 [CQOI2018] 异或序列

思路

由于异或有可逆性,那么对 a 数组做一个前缀异或和,原数组 lr 的异或和就变为了前缀异或和的 suml1 异或上 sumr

注意到查询是区间,选择用莫队去做,莫队维护当前加进来的数与 k 异或后的值的个数,统计答案即可。

代码

#include<iostream>
#include<cmath>
#include<algorithm>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1e5 + 10;
int n, m, k, block, ans[N];
int a[N], b[N];
struct node{
    int l, r, id;
    bool operator < (const node &x) const{
        return b[l] ^ b[x.l] ? b[l] < b[x.l] : (b[l] & 1 ? r < x.r : r > x.r);
    }
}q[N];
int L, R = -1, cnt[N], res;
void add(int x){
    res += cnt[a[x] ^ k];
    cnt[a[x]]++;
}
void del(int x){
    cnt[a[x]]--;
    res -= cnt[a[x] ^ k];
}

signed main(){
    n = read(), m = read(), k = read(), block = sqrt(n);
    for (int i = 1; i <= n; i++) a[i] = read(), a[i] ^= a[i - 1], b[i] = (i - 1) / block + 1;
    for (int i = 1; i <= m; i++) q[i].l = read() - 1, q[i].r = read(), q[i].id = i;
    sort(q + 1, q + m + 1);
    for (int i = 1; i <= m; i++){
        while (L > q[i].l) add(--L);
        while (R < q[i].r) add(++R);
        while (L < q[i].l) del(L++);
        while (R > q[i].r) del(R--);
        ans[q[i].id] = res;
    }
    for (int i = 1; i <= m; i++) cout << ans[i] << '\n';
    return 0;
}

P9108 [PA2020] Malowanie płotu

思路

40pts

dpi,j,k 表示第 i 个左端点为 j,右端点为 k 的方案数,转移很好想,暴力枚举合法的 l,r 转移,由于空间开不下,选择用滚动数组优化一维,时间复杂度 O(nm4)

70pts

考虑在什么条件下才能够转移,很明显转移的 lkrmax(l,j),由于在 r<l 时的 dp0,所以 dpi,j,k=1lk,jrmdpi1,lr,这一坨可以用前缀和优化,时间复杂度 O(nm2)

100pts

发现这样枚举左右端点是无法继续优化下去了,于是考虑将左右端点拆开进行动态规划,设 fi,j 为第 i 个右端点为 j 的方案数,gi,j 为第 i 个左端点为 j 的方案数。

思考转移方程,需要用到容斥,fi,j=j×k=1jg(i1,k)r=1jk=1r1f(i1,k),意思为所有左端点在 j 左侧的区间的方案数减去这些满足上一条件的右端点小于当前区间左端点的区间的方案数,gi,j 同理。集中注意力,发现所有的求和都可以用前缀和优化,顺便还可以直接去掉第一维,减少空间,于是时间复杂度为 O(nm)

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){
    register int x = 0, f = 1;
    register char c = getchar();
    while (c < '0' || c > '9'){
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9'){
        x = (x << 1) + (x << 3) + (c ^ 48);
        c = getchar();
    }
    return x * f;
}

const int N = 1e7 + 10;
int n, m, mod, ans;
int f[N], g[N], sf[N], sumf[N], sg[N], sumg[N];

signed main(){
    n = read(), m = read(), mod = read();
    for (int i = 1; i <= m; i++) f[i] = i, g[i] = m - i + 1;
    for (int i = 2; i <= n; i++){
        for (int j = 1; j <= m; j++) sf[j] = (sf[j - 1] + f[j]) % mod, sumf[j] = (sumf[j - 1] + sf[j]) % mod;
        for (int j = 1; j <= m; j++) sg[j] = (sg[j - 1] + g[j]) % mod;
        for (int j = m; j >= 1; j--) sumg[j] = (sumg[j + 1] + (sg[m] - sg[j - 1] + mod) % mod) % mod;
        for (int j = 1; j <= m; j++) f[j] = (j * sg[j] % mod - sumf[j - 1] + mod) % mod;
        for (int j = 1; j <= m; j++) g[j] = ((m - j + 1) * ((sf[m] - sf[j - 1] + mod) % mod) % mod - sumg[j + 1] + mod) % mod;
    }
    for (int i = 1; i <= m; i++) ans = (ans + f[i]) % mod;
    cout << ans;
    return 0;
}

P4768 [NOI2018] 归程

思路

前置芝士:kruskal 重构树

由 kruskal 重构树的性质得知,跑一遍最大生成树,建成的 kruskal 重构树,两个点的 lca 的权值为两个点之间的所有简单路径上最小边权的最大值。

题目显然要使汽车开一段路,再走最小的路程,那么,生成一个上述的 kruskal 重构树,就可以得知起始点与其他点之间简单路径的最小边权的最大值,如果它们两个点之间的 lca 的权值大于 p,就可以走。

于是先求一遍最短路,再建一遍 kruskal 重构树,dfs 一遍求所有子树中最小的最短路,因为只要子树的根节点大于等于 p,里面任何的点都能互相到达,最后倍增求满足条件的最大子树的根节点。

代码

#include<iostream>
#include<algorithm>
#include<queue>
#include<climits>
#include<cstring>
#define INF INT_MAX

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 4e5 + 10, M = 4e5 + 10;
int T, n, m, Q, K, S, lastans;
struct edge{
    int v, w, nxt;
}e[M << 1];
int head_edge[N], cnt_edge;
struct node{
    int u, v, w;
    bool operator < (const node &b) const{
        return w > b.w;
    }
}f[M];
void add_edge(int u, int v, int w){
    e[++cnt_edge] = (edge){v, w, head_edge[u]};
    head_edge[u] = cnt_edge;
}
void add_node(int u, int v, int w, int now){
    f[now] = (node){u, v, w};
}
struct dij{
    int u, d;
    bool operator < (const dij &b) const{
        return d > b.d;
    }
};
priority_queue<dij> q;
int dis[N];
void dijkstra(int s){
    for (int i = 1; i <= n; i++) dis[i] = INF;
    dis[s] = 0;
    q.push((dij){s, 0});
    while (!q.empty()){
        dij t = q.top();q.pop();
        int u = t.u, d = t.d;
        if (d != dis[u]) continue;
        for (int i = head_edge[u]; i; i = e[i].nxt){
            int v = e[i].v;
            if (dis[v] > dis[u] + e[i].w){
                dis[v] = dis[u] + e[i].w;
                q.push((dij){v, dis[v]});
            }
        }
    }
}
int fi[N];
int find(int x){
    return (x == fi[x] ? fi[x] : fi[x] = find(fi[x]));
}
struct krus{
    int v, nxt;
}kru[M << 1];
int head_kru[N], cnt_kru, val[N];
void add_kruskal(int u, int v){
    kru[++cnt_kru] = (krus){v, head_kru[u]};
    head_kru[u] = cnt_kru;
}
void kruskal(){
    sort(f + 1, f + m + 1);
    for (int i = 1; i <= (n << 1) - 1; i++) fi[i] = i;
    int tot = 0;
    for (int i = 1; i <= m; i++){
        int eu = find(f[i].u), ev = find(f[i].v);
        if (eu == ev) continue;
        tot++;
        fi[eu] = fi[ev] = n + tot;
        add_kruskal(eu, n + tot), add_kruskal(n + tot, eu);
        add_kruskal(ev, n + tot), add_kruskal(n + tot, ev);
        val[n + tot] = f[i].w;
        if (tot == n - 1) break;
    }
}
int dp[N], fath[N][21];
void dfs(int u, int fa){
    fath[u][0] = fa;
    for (int i = head_kru[u]; i; i = kru[i].nxt){
        int v = kru[i].v;
        if (v == fa) continue;
        dfs(v, u);
        dp[u] = min(dp[u], dp[v]);
    }
    if (u <= n) dp[u] = dis[u];
}
void init(){
    for (int i = 1; i <= 19; i++)
        for (int j = 1; j <= (n << 1) - 1; j++) fath[j][i] = fath[fath[j][i - 1]][i - 1];
}
int query(int v, int p){
    for (int i = 19; i >= 0; i--)
        if (val[fath[v][i]] > p) v = fath[v][i];
    return dp[v];
}
void solve(){
    n = read(), m = read();
    for (int i = 1; i <= m; i++){
        int u = read(), v = read(), w = read(), l = read();
        add_edge(u, v, w), add_edge(v, u, w), add_node(u, v, l, i);
    }
    dijkstra(1);
    kruskal();
    for (int i = 1; i <= (n << 1) - 1; i++) dp[i] = INF;
    dfs(find(1), 0);
    init();
    Q = read(), K = read(), S = read();
    while (Q--){
        int v0 = read(), p0 = read(), v = (v0 + K * lastans - 1) % n + 1, p = (p0 + K * lastans) % (S + 1);
        cout << (lastans = query(v, p)) << '\n';
    }
    lastans = 0, cnt_edge = 0, cnt_kru = 0;
    memset(e, 0, sizeof e);
    memset(head_edge, 0, sizeof head_edge);
    memset(f, 0, sizeof f);
    memset(dis, 0, sizeof dis);
    memset(fi, 0, sizeof fi);
    memset(kru, 0, sizeof kru);
    memset(head_kru, 0, sizeof head_kru);
    memset(val, 0, sizeof val);
    memset(dp, 0, sizeof dp);
    memset(fath, 0, sizeof fath);
}

int main(){
    T = read();
    while (T--) solve();
    return 0;
}

Omkar and Tours

思路

考虑第一个问题非常好做,直接对所有询问离线下来,排个序,并查集维护连通块。

如何做第二个问题?对 t 求一个 kruskal 重构树,由于是棵树,所以 lca 权值即为两点之间最大费用,由于 lca 越浅费用越大,排完序后用并查集维护当前这个连通块中的最大点权和最大点权的点之间的 lca,最后用这个 lca 与要求的 x 取一个 lca 即可。

代码

#include<iostream>
#include<algorithm>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 4e5 + 10;
int n, q, ans1[N], ans2[N];
int c[N];
struct node{
    int u, v, w;
    bool operator < (const node &b) const{
        return w < b.w;
    }
}f[N];
struct edge{
    int u, v, w;
    bool operator < (const edge &b) const{
        return w > b.w;
    }
}e[N];
int fi[N], val[N];
int find(int x){
    return (x == fi[x] ? fi[x] : fi[x] = find(fi[x]));
}
struct kru{
    int v, nxt;
}k[N << 1];
int head_kru[N], cnt_kru;
void add_kru(int u, int v){
    k[++cnt_kru] = (kru){v, head_kru[u]};
    head_kru[u] = cnt_kru;
}
void kruskal(){
    sort(f + 1, f + n);
    for (int i = 1; i < (n << 1); i++) fi[i] = i;
    int tot = 0;
    for (int i = 1; i < n; i++){
        int eu = find(f[i].u), ev = find(f[i].v);
        if (eu == ev) continue;
        tot++;
        fi[eu] = fi[ev] = n + tot;
        add_kru(eu, n + tot), add_kru(n + tot, eu);
        add_kru(ev, n + tot), add_kru(n + tot, ev);
        val[n + tot] = f[i].w;
    }
}
int fa[N][21], dep[N];
void dfs(int u, int fu){
    fa[u][0] = fu;
    dep[u] = dep[fu] + 1;
    for (int i = head_kru[u]; i; i = k[i].nxt){
        int v = k[i].v;
        if (v == fu) continue;
        dfs(v, u);
    }
}
void init(){
    for (int i = 1; i <= 19; i++)
        for (int j = 1; j < (n << 1); j++) fa[j][i] = fa[fa[j][i - 1]][i - 1];
}
int lca(int x, int y){
    if (dep[x] < dep[y]) swap(x, y);
    for (int i = 19; i >= 0; i--)
        if (dep[fa[x][i]] >= dep[y]) x = fa[x][i];
    if (x == y) return x;
    for (int i = 19; i >= 0; i--)
        if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
    return fa[x][0];
}
struct Query{
    int c, x, id;
    bool operator < (const Query &b) const{
        return c > b.c;
    }
}Q[N];
struct dsu{
    int fa, mx, pos;
}d[N];
int fd(int x){
    return (x == d[x].fa ? d[x].fa : d[x].fa = fd(d[x].fa));
}
void merge(int u, int v){
    int eu = fd(u), ev = fd(v);
    d[ev].fa = eu;
    if (d[ev].mx > d[eu].mx){
        d[eu].mx = d[ev].mx;
        d[eu].pos = d[ev].pos;
    }else if (d[eu].mx == d[ev].mx){
        d[eu].pos = lca(d[eu].pos, d[ev].pos);
    }
}

int main(){
    n = read(), q = read();
    for (int i = 1; i <= n; i++) c[i] = read();
    for (int i = 1; i < n; i++){
        int u = read(), v = read(), w = read(), t = read();
        e[i] = (edge){u, v, w};
        f[i] = (node){u, v, t};
    }
    kruskal();
    dfs((n << 1) - 1, 0);
    init();
    for (int i = 1; i <= q; i++) Q[i] = (Query){read(), read(), i};
    sort(e + 1, e + n);
    sort(Q + 1, Q + q + 1);
    for (int i = 1; i <= n; i++) d[i].fa = d[i].pos = i, d[i].mx = c[i];
    for (int i = 1, j = 0; i <= q; i++){
        while (e[j + 1].w >= Q[i].c && j < n){
            j++;
            merge(e[j].u, e[j].v);
        }
        int y = fd(Q[i].x);
        ans1[Q[i].id] = d[y].mx;
        ans2[Q[i].id] = val[lca(Q[i].x, d[y].pos)];
    }
    for (int i = 1; i <= q; i++) cout << ans1[i] << ' ' << ans2[i] << '\n';
    return 0;
}

P5684 [CSP-J2019 江西] 非回文串

思路

考虑要求非回文串的个数,即为总串的个数减去回文串的个数,对于每个字符统计一遍出现次数,特判是否有回文串,然后枚举字符,求每个在前 n2x 个位置放 num2 的方案数,乘起来,num 表示这个字符的出现次数,x 表示目前放了多少个位置,显然组合数学求即可。

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e3 + 10, mod = 1e9 + 7;
int n, ans = 1;
int cnt[28], ji, ou, mul[N << 1], inv[N << 1];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void init(){
    mul[0] = 1, inv[0] = 1;
    for (int i = 1; i <= (n << 1); i++)
        mul[i] = mul[i - 1] * i % mod, inv[i] = qpow(mul[i], mod - 2);
}
int C(int m, int n){
    if (m > n) return 0;
    return mul[n] * inv[n - m] % mod * inv[m] % mod;
}

signed main(){
    n = read();
    for (int i = 1; i <= n; i++){
        char c;cin >> c;
        cnt[c - 'a']++;
    }
    for (int i = 0; i < 26; i++)
        if (cnt[i] & 1) ji++;
        else ou++;
    init();
    if (n & 1 && ji != 1){
        cout << mul[n] % mod;
        return 0;
    }
    if (!(n & 1) && ji){
        cout << mul[n] % mod;
        return 0;
    }
    int res = 0;
    for (int i = 0; i < 26; i++){
        if (!cnt[i]) continue;
        ans = ans * C(cnt[i] / 2, n / 2 - res) % mod * mul[cnt[i]] % mod;
        res += cnt[i] / 2;
    }
    cout << (mul[n] - ans + mod) % mod;
    return 0;
}

SUM and REPLACE

思路

有一个经典的套路就是,这种修改成约数的次数不会有几次,于是维护每个区间和与区间内有多少个 12,因为它们的约数等于本身,所以没必要修改,直接上线段树,暴力修改即可。

代码

#include<iostream>
#include<cmath>
#define ls now << 1
#define rs now << 1 | 1

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 3e5 + 10;
int n, m;
int a[N];
int d(int x){
    int res = 0;
    for (int i = 1; i < sqrt(x); i++)
        if (x % i == 0) res += 2;
    int q = sqrt(x);
    if (q * q == x) res++;
    return res;
}
struct tree{
    long long v;
    int s;
}t[N << 2];
void pushup(int now){
    t[now].v = t[ls].v + t[rs].v;
    t[now].s = t[ls].s + t[rs].s;
}
void build(int now, int l, int r){
    if (l == r){
        t[now] = (tree){a[l], (a[l] == 1 || a[l] == 2)};
        return;
    }
    int mid = (l + r) >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    pushup(now);
}
void modify(int now, int l, int r, int x, int y){
    if (l == r){
        t[now].v = d(t[now].v);
        t[now].s = (t[now].v == 1 || t[now].v == 2);
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid && t[ls].s != mid - l + 1) modify(ls, l, mid, x, y);
    if (mid + 1 <= y && t[rs].s != r - mid) modify(rs, mid + 1, r, x, y);
    pushup(now);
}
long long query(int now, int l, int r, int x, int y){
    if (x <= l && r <= y){
        return t[now].v;
    }
    int mid = (l + r) >> 1;
    long long res = 0;
    if (x <= mid) res += query(ls, l, mid, x, y);
    if (mid + 1 <= y) res += query(rs, mid + 1, r, x, y);
    return res;
}

int main(){
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    build(1, 1, n);
    for (int i = 1; i <= m; i++){
        int opt = read(), l = read(), r = read();
        if (opt == 1){
            modify(1, 1, n, l, r);
        }else{
            cout << query(1, 1, n, l, r) << '\n';
        }
    }
    return 0;
}

[AGC029C] Lexicographic constraints

思路

首先二分答案,将二分得到的答案抽象成一个 k 进制,当 ai1<ai 时,直接在后面加一位最小的数即可,当 ai1ai 时,在当前这一位上 +1 或者进位,如果第一位进位了,那么这个答案就不合法。考虑如何实现,开一个栈记录加一的数的位置,然后每次操作找到加数的位置,加一,进位就通过递归的方式实现即可。

代码

#include<iostream>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int n, sum, l = 2, r, ans;
int a[N], stk[N], num[N], top;
void add(int pos, int k){
    while (stk[top] > pos) top--;
    if (stk[top] == pos) num[top]++;
    else stk[++top] = pos, num[top] = 1;
    if (top > 1 && num[top] == k) top--, add(pos - 1, k);
}
bool check(int x){
    top = 0;
    stk[++top] = 0, num[top] = 0;
    for (int i = 2; i <= n; i++)
        if (a[i] <= a[i - 1]) add(a[i], x);
    return (num[1] == 0);
}

int main(){
    n = read();
    for (int i = 1; i <= n; i++) a[i] = read(), r = max(r, a[i]), sum += (a[i] > a[i - 1]);
    if (sum == n){
        cout << 1;
        return 0;
    }
    r = max(r, n);
    while (l <= r){
        int mid = (l + r) >> 1;
        if (check(mid)) r = mid - 1, ans = mid;
        else l = mid + 1;
    }
    cout << ans;
    return 0;
}

The Child and Sequence

思路

维护区间最大值,如果小于 x 就不修改,否则暴力修改,修改操作很少。

代码

#include<iostream>
#define int long long
#define ls now << 1
#define rs now << 1 | 1
using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1e5 + 10;
int n, m;
int a[N];
struct tree{
    int sum, max;
}t[N << 2];
void pushup(int now){
    t[now].sum = t[ls].sum + t[rs].sum;
    t[now].max = max(t[ls].max, t[rs].max);
}
void build(int now, int l, int r){
    if (l == r){
        t[now].sum = t[now].max = a[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(ls, l, mid);
    build(rs, mid + 1, r);
    pushup(now);
}
void modify_mod(int now, int l, int r, int x, int y, int k){
    if (l == r){
        t[now].sum %= k;
        t[now].max = t[now].sum;
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid && t[ls].max >= k) modify_mod(ls, l, mid, x, y, k);
    if (mid + 1 <= y && t[rs].max >= k) modify_mod(rs, mid + 1, r, x, y, k);
    pushup(now);
}
void modify(int now, int l, int r, int x, int k){
    if (l == r){
        t[now].sum = t[now].max = k;
        return;
    }
    int mid = (l + r) >> 1;
    if (x <= mid) modify(ls, l, mid, x, k);
    else modify(rs, mid + 1, r, x, k);
    pushup(now);
}
int query(int now, int l, int r, int x, int y){
    if (x <= l && r <= y){
        return t[now].sum;
    }
    int mid = (l + r) >> 1, res = 0;
    if (x <= mid) res += query(ls, l, mid, x, y);
    if (mid + 1 <= y) res += query(rs, mid + 1, r, x, y);
    return res;
}

signed main(){
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    build(1, 1, n);
    for (int i = 1; i <= m; i++){
        int opt = read();
        if (opt == 1){
            int l = read(), r = read();
            cout << query(1, 1, n, l, r) << '\n';
        }else if (opt == 2){
            int l = read(), r = read(), x = read();
            modify_mod(1, 1, n, l, r, x);
        }else{
            int x = read(), k = read();
            modify(1, 1, n, x, k);
        }
    }
    return 0;
}

[ABC203D] Pond

思路

二分中位数 x,如果当前这个数大于 x,标记为 1,否则标记为 0,如果有一个 k×k 的矩阵内 1 的个数小于等于 k×k2,那么这个比这个小的数会成为最小的中位数,否则比这个大的数会最小的中位数。

代码

#include<iostream>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 810;
int n, k, ans;
int a[N][N], b[N][N];
bool check(int x){
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++)
            b[i][j] = b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1] + (a[i][j] > x);
    for (int i = k; i <= n; i++)
        for (int j = k; j <= n; j++)
            if (b[i][j] - b[i - k][j] - b[i][j - k] + b[i - k][j - k] <= k * k / 2) return 1;
    return 0;
}

int main(){
    n = read(), k = read();
    int l = 0, r = 0;
    for (int i = 1; i <= n; i++)
        for (int j = 1; j <= n; j++) a[i][j] = read(), r = max(r, a[i][j]);
    while (l <= r){
        int mid = (l + r) >> 1;
        if (check(mid)){
            r = mid - 1;
            ans = mid;
        }else l = mid + 1;
    }
    cout << ans;
    return 0;
}

Another MEX Problem

思路

显然有一个 dp,设 dpi,j 表示前 i 个数能否凑 j,于是 n2 枚举区间转移。

考虑优化这个 dp,我们发现,一个 [l,r]mex[l1,r]mex[l,r1]mex 相同,那么 [l1,r][l,r1] 的转移都能被 [l,r] 代替,而这种能够代替转移的区间的个数只有 2n 个,证明见题解(本人太弱了),于是只需预处理出这种区间的个数,再暴力转移即可。

代码

#include<iostream>
#include<vector>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 5e3 + 10, M = 8.2e3 + 10;
int T, n, ans;
int a[N], mex[N][N];
bool vis[M], dp[N][M];
vector<int> v[N];
void solve(){
    n = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    for (int i = 1; i <= n; i++){
        int val = 0;
        for (int j = i; j <= n; j++){
            vis[a[j]] = 1;
            while (vis[val]) val++;
            mex[i][j] = val;
        }
        for (int j = 0; j <= M - 10; j++) vis[j] = 0;
    }   
    for (int i = 1; i <= n; i++){
        for (int j = i; j <= n; j++){
            if (mex[i][j] != mex[i + 1][j] && mex[i][j] != mex[i][j - 1]) v[j].emplace_back(i);
        }
    }
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++){
        for (int j = 0; j <= M - 10; j++){
            dp[i][j] |= dp[i - 1][j];
            for (auto l : v[i]) if ((j ^ mex[l][i]) <= M - 10) dp[i][j] |= dp[l - 1][j ^ mex[l][i]];
        }
    }
    for (int i = 1; i <= n; i++) v[i].clear();
    for (int i = M - 10; i >= 0; i--){
        if (dp[n][i]){
            ans = i;
            break;
        }
    }
    cout << ans << '\n';
    ans = 0;
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= M - 10; j++) dp[i][j] = 0;
}

int main(){
    T = read();
    while (T--){
        solve();
    }
    return 0;
}

[ABC292Ex] Rating Estimator

思路

题目要求找到第一个 1ki=1kpiBk,化一下式子得到 i=1k(piB)0,于是将每个值减去 B,求一遍前缀和,线段树上维护前缀和的 max,修改时做区间修改,然后线段树二分找到最小的 k

代码

#include<iostream>
#include<iomanip>
#include<algorithm>
#define pii make_pair
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 5e5 + 10;
int n, p, m;
int a[N], sum[N];
struct tree{
    int max, tag;
}t[N << 2];
void pushup(int now){
    t[now].max = max(t[now << 1].max, t[now << 1 | 1].max);
}
void pushdown(int now){
    t[now << 1].max += + t[now].tag;
    t[now << 1 | 1].max += t[now].tag;
    t[now << 1].tag += t[now].tag;
    t[now << 1 | 1].tag += t[now].tag;
    t[now].tag = 0;
}
void build(int now, int l, int r){
    if (l == r){
        t[now].max = sum[l];
        return;
    }
    int mid = (l + r) >> 1;
    build(now << 1, l, mid);
    build(now << 1 | 1, mid + 1, r);
    pushup(now);
}
void modify(int now, int l, int r, int x, int y, int k){
    if (x <= l && r <= y){
        t[now].max += k;
        t[now].tag += k;
        return;
    }
    pushdown(now);
    int mid = (l + r) >> 1;
    if (x <= mid) modify(now << 1, l, mid, x, y, k);
    if (mid + 1 <= y) modify(now << 1 | 1, mid + 1, r, x, y, k);
    pushup(now);
}
pair<int, int> query(int now, int l, int r, int x, int y){
    if (l == r){
        return pii(t[now].max, l);
    }
    pushdown(now);
    int mid = (l + r) >> 1;
    if (t[now << 1].max >= 0) return query(now << 1, l, mid, x, y);
    else return query(now << 1 | 1, mid + 1, r, x, y);
}

signed main(){
    n = read(), p = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read() - p, sum[i] = sum[i - 1] + a[i];
    build(1, 1, n);
    for (int i = 1; i <= m; i++){
        int x = read(), k = read();
        modify(1, 1, n, x, n, -a[x]), modify(1, 1, n, x, n, k - p), a[x] = k - p;
        pair<int, int> ans = query(1, 1, n, 1, n);
        int pos = ans.second;
        cout << fixed << setprecision(15) << (long double)ans.first / (long double)pos + p << '\n';
    }
    return 0;
}

Common Divisor Graph

思路

集中注意力发现,最多创造两个新的节点就能使 st,因为 as(as+1)at(at+1) 都为偶数。

当答案为 0 时,对每个点求其质因数,并查集维护连通块,如果 st 在同一个连通块内答案就为 0
当答案为 1 时,即不在同一个连通块,对于每一个点求 ai+1 的质因数,将 ai 所在的连通块的代表点与 ai+1 的每一个质因数所在的连通块用邻接矩阵连一条边,表示能够到达,再将 ai+1 的每一个质因数所在的连通块之间两两连边,表示这些连通块能够通过 ai+1 这个点互相到达,判断 st 所在的连通块有否连边。

当答案为 2 时,即为无连边的情况。

代码

#include<iostream>
#include<vector>
#include<map>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1.5e5 + 10, M = 1e6 + 10;
int n, m, maxn;
int a[N], prime[M], cnt, fa[M];
bool p[M];
vector<int> v[M];
void init(){
    for (int i = 2; i <= maxn; i++){
        if (!p[i]) prime[++cnt] = i;
        for (int j = 1; j <= cnt && i * prime[j] <= maxn; j++){
            p[i * prime[j]] = 1;
            if (i % prime[j] == 0) break;
        }
    }
    for (int i = 1; i <= maxn; i++) fa[i] = i;
}
int find(int x){
    return (x == fa[x] ? fa[x] : fa[x] = find(fa[x]));
}
void merge(int x, int y){
    int fx = find(x), fy = find(y);
    fa[fx] = fy;
}
map<int, map<int, bool> > vis;

int main(){
    n = read(), m = read();
    for (int i = 1; i <= n; i++) a[i] = read(), maxn = max(maxn, a[i] + 1);
    init();
    for (int i = 1; i <= cnt; i++)
        for (int j = prime[i]; j <= maxn; j += prime[i]) v[j].emplace_back(prime[i]);
    for (int i = 1; i <= n; i++)
        for (auto j : v[a[i]]) merge(a[i], j);
    for (int i = 1; i <= n; i++){
        for (int j = 0; j < v[a[i]].size(); j++)
            for (int k = j; k < v[a[i] + 1].size(); k++){
                int fx = find(v[a[i]][j]), fy = find(v[a[i] + 1][k]);
                if (fx == fy) continue;
                vis[fx][fy] = 1;
            }
        for (int j = 0; j < v[a[i] + 1].size(); j++){
            for (int k = j; k < v[a[i] + 1].size(); k++){
                int fx = find(v[a[i] + 1][j]), fy = find(v[a[i] + 1][k]);
                if (fx == fy) continue;
                vis[fx][fy] = 1;
            }
        }
    }
    for (int i = 1; i <= m; i++){
        int s = find(a[read()]), t = find(a[read()]);
        if (s == t) cout << 0 << '\n';
        else if (vis[s][t] || vis[t][s]) cout << 1 << '\n';
        else cout << 2 << '\n';
    }
    return 0;
}

[ABC376E] Max × Sum

思路

a 为关键字排个序,假设现在考虑到第 i 位,这样就保证了 a 最大值为 ai,然后对 b 开一个堆,记录 i 之前 k 小的数,统计答案即可。

代码

#include<iostream>
#include<algorithm>
#include<queue>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 2e5 + 10;
int T, n, k, ans;
struct node{
    int a, b;
    bool operator < (const node &y) const{
        return a < y.a;
    }
}x[N];
priority_queue<int> q;
void solve(){
    n = read(), k = read();
    for (int i = 1; i <= n; i++) x[i].a = read();
    for (int i = 1; i <= n; i++) x[i].b = read();
    sort(x + 1, x + n + 1);
    int s = 0;
    for (int i = 1; i <= k; i++) q.push(x[i].b), s += x[i].b;
    ans = s * x[k].a;
    for (int i = k + 1; i <= n; i++){
        if (x[i].b < q.top()){
            s -= q.top();
            s += x[i].b;
            q.pop();
            q.push(x[i].b);
            ans = min(ans, s * x[i].a);
        }
    }
    cout << ans << '\n';
    while (!q.empty()) q.pop();
}

signed main(){
    T = read();
    while (T--) solve();
    return 0;
}

[ABC377G] Edit to Match

思路

考虑建一颗 trie 树,每次插入一个字符串,求其末尾到其他字符串末尾的最小长度,设 fi 表示 trie 树上第 i 个节点,到其最近的字符串结尾的节点的路径长度,于是,对于每个字符,从上到下查询一遍并更新路径上的节点 fi=min(fi,ffai+1),再从下到上更新一遍 fi=min(fi,fsoni+1)

代码

#include<iostream>
#include<cstring>
#include<cmath>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 2e5 + 10, M = 27;
int n, tot;
char c[N];
int trie[N][M], f[N * M], res[N * M];
int insert(char c[]){
    int m = strlen(c), now = 0, p = 0, ans;
    for (int i = 0; i < m; i++){
        int &to = trie[now][c[i] - 'a'];
        if (!to) to = ++tot, f[tot] = 0x7fffffff;
        f[to] = min(f[to], f[now] + 1);
        now = to;
        res[++p] = now;
    }
    ans = f[now];
    f[now] = 0;
    for (int i = p - 1; i >= 1; i--) f[res[i]] = min(f[res[i]], f[res[i + 1]] + 1);
    return ans;
}

int main(){
    n = read();
    for (int i = 1; i <= n; i++){
        cin >> c;
        cout << insert(c) << '\n';
    }
    return 0;
}

[ABC377E] Permute K times 2

思路

对于当前 i 位置上一个数变换一次变为 ppippi 上的数变为 pppi,变换两次,i 上的数就变为了 ppppipppi 上的数变为 ppppi,以此类推,发现一个数会在自己的循环中变换 2k1 次,于是,找出这个循环,找到从当前这个数开始往后第 2k1 个数。

代码

#include<iostream>
#include<vector>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 2e5 + 10;
int n, k;
int a[N], vis[N], pos[N];
vector<int> g[N];
void dfs(int u, int top){
    if (vis[u]) return;
    vis[u] = top;
    g[top].emplace_back(u);
    pos[u] = g[top].size();
    dfs(a[u], top);
}
int qpow(int a, int b, int mod){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

signed main(){
    n = read(), k = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    for (int i = 1; i <= n; i++)
        if (!vis[a[i]]) dfs(a[i], a[i]);
    for (int i = 1; i <= n; i++){
        int mod = g[vis[a[i]]].size(), p = ((pos[a[i]] + qpow(2, k, mod)) % mod - 1 + mod) % mod;
        p = (p == 0 ? mod : p);
        cout << g[vis[a[i]]][p - 1] << ' ';
    }
    return 0;
}

P7981 [JRKSJ R3] system

思路

这个题目和 ABC377E 有些不同,但也有相同的思路,考虑对 ipi 连边,发现建出来的图是一个基环树,我们先不考虑基环树怎么做,先考虑只给一个排列,建边后只有环。

对于当前 i 位置上一个数变换一次变为 ppippi 上的数变为 pppi,变换两次,i 上的数就变为了 ppppipppi 上的数变为 ppppi,以此类推,发现一个数会在自己的所在的环中变换 2k1 次,于是,找出这个环,找到从当前这个数开始往后第 2k1 个数。

接下来考虑基环树,我们发现这是一个内向基环树,按照上面的结论,从自己一直走 2k1 边,考虑这个怎么做,用倍增去维护,先倍增跳到一个环上,再判断在环上要走多少边,对环的大小取模,再在环上跳到正确的点。

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 5e5 + 10, M = 21;
int n, k;
int a[N], fa[N][M], vis[N], cir[N];
void dfs(int u, int id){
    if (vis[u] == id){
        if (cir[u]) return;
        int x = u, num = 1;
        while (a[x] != u){
            x = a[x];
            if (cir[x]) return;
            num++;
        }
        x = u, cir[x] = num;
        while (a[x] != u){
            x = a[x];
            if (cir[x]) return;
            cir[x] = num;
        }
        return;
    }
    if (vis[u]) return;
    vis[u] = id;
    fa[u][0] = a[u];
    dfs(a[u], id);
}
void init(){
    for (int i = 1; i <= 19; i++)
        for (int j = 1; j <= n; j++) fa[j][i] = fa[fa[j][i - 1]][i - 1];
}
int qpow(int a, int b, int mod){
    int ans = 1;
    while (b){
        if (b & 1){
            ans = ans * a % mod;
        }
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

signed main(){
    n = read(), k = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    for (int i = 1; i <= n; i++)
        if (!vis[a[i]]) dfs(a[i], i);
    init();
    for (int i = 1; i <= n; i++){
        int p = a[i], j;
        for (j = 0; j <= 19 && !cir[p]; j++) p = fa[p][j];
        int mod = cir[p], turn = ((((qpow(2, k, mod) - 1 + mod) % mod - qpow(2, j, mod) + mod) + mod) % mod + 1) % mod;
        for (j = 19; j >= 0; j--){
            if (turn >= (1 << j)) turn -= (1 << j), p = fa[p][j];
        }
        cout << p << ' ';
    }
    return 0;
}

[ABC310E] NAND repeatedly

思路

由于没有交换律,所以从前往后扫,计算每个前缀的贡献,若遇到 0,所有前缀都变为了 1,否则前面前缀等于 0 的与它合并都会变为 1,即贡献为 0 的数量。

代码

#include<iostream>
#define int long long

using namespace std;

const int N = 1e6 + 10;
int n, ans;
char c[N];

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n >> (c + 1);
    int cnt = 0;
    for (int i = 1; i <= n; i++){
        if (c[i] == '1') cnt = i - cnt;
        else cnt = i - 1;
        ans += cnt;
    }
    cout << ans;
    return 0;
}

[ABC323E] Playlist

思路

概率 dp,设 dpi 表示播到 i 时间结束时的概率,于是 dpi=dpi+dpitj,由于上一首播完的时间在 xti+1x 之间,那么第一首歌曲才有可能放到,最后统计答案为 ans=ans+fi×1n

代码

#include<iostream>
#include<algorithm>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 1e3 + 10, M = 1e4 + 10, mod = 998244353;
int n, k, ans;
int t[N], f[M];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}

signed main(){
    n = read(), k = read();
    for (int i = 1; i <= n; i++) t[i] = read();
    int inv = qpow(n, mod - 2);
    f[0] = 1;
    for (int i = 0; i <= k; i++)
        for (int j = 1; j <= n; j++)
            if (i >= t[j]) f[i] = (f[i] + f[i - t[j]] * inv % mod) % mod;
    for (int i = max(k - t[1] + 1, 0ll); i <= k; i++) ans = (ans + f[i] * inv % mod) % mod;
    cout << ans;
    return 0;
}

[ARC061F] 3人でカードゲーム

思路

将题目要求的东西转化一下,变为有这样的取牌序列,满足第一个牌堆先被拿完,求这样的取牌序列的个数。

设一个刚好拿完牌堆 1,且第一个拿完的取牌序列长度为 x,由于还可能有牌没有拿完,所以这个序列还对应 3n+m+kx 种方案。

枚举拿出了 p 张不是 1 的牌,对于当前 p,方案数就为 (p+n1p)i=0p(pi)[im][pik]

前面的组合数表示选出 p 个不为 1 的牌的方案数,后面表示 p 张牌从后面的 2 牌或 3 牌而来的总方案数。

这样做是 O(n2) 的,由于后面一部分时间复杂度太劣了,所以考虑递推求。

S(p)=pkimp(pi)

=pkimp(p1i)+(p1i1)

=pkimp(p1i)+p1kim1p1(p1i)

=2S(p1)(p1m)(p1p1k)

会有不符合条件的组合数,它们的贡献为 0

那么答案:

ans=p=0m+k3m+kp(p+n1p)S(p)

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}
inline void write(int x){if (x < 0) putchar('-'), x = -x;if (x > 9) write(x / 10);putchar(x % 10 + '0');}

const int N = 1e6 + 10, mod = 1e9 + 7;
int n, m, k, ans;
int mul[N], inv[N], s[N];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void init(){
    mul[0] = inv[0] = 1;
    for (int i = 1; i <= n + m + k; i++) mul[i] = mul[i - 1] * i % mod, inv[i] = qpow(mul[i], mod - 2);
}
int C(int m, int n){
    if (m > n || m < 0 || n < 0) return 0;
    return mul[n] * inv[n - m] % mod * inv[m] % mod;
}

signed main(){
    n = read(), m = read(), k = read();
    init();
    s[0] = 1;
    for (int i = 1; i <= m + k; i++) s[i] = ((s[i - 1] * 2 % mod - C(i - 1 - k, i - 1) + mod) % mod - C(m, i - 1) + mod) % mod;
    for (int i = 0; i <= m + k; i++){
        ans = (ans + qpow(3, m + k - i) * C(i, n + i - 1) % mod * s[i] % mod) % mod;
    }
    cout << ans;
    return 0;
}

Girl Permutation

思路

观察题目性质,前后肯定是分开计算的,如果前缀最大值的位置第一个不为 1,后缀最大值的位置的第一个不为 n,前缀最大值的最后一个位置与后缀最大值第一个位置不同,那么直接输出 0

先考虑前面的,前缀最大值最后一个位置上绝对是最大的数,从这个位置开始,有 (n1pm11) 种划分方案,再在下一个前缀最大值所在的位置上,左边的数又有 (npm11pm111) 种划分方式,右边的数可以任意排列,有 (pm1pm111)! 种方案,于是,将这些答案乘起来,一直计算下去,后缀同理。

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 2e5 + 10, K = 4e5, mod = 1e9 + 7;
int T, n, m1, m2;
int mul[N << 1], inv[N << 1];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void init(){
    mul[0] = inv[0] = 1;
    for (int i = 1; i <= K; i++) mul[i] = mul[i - 1] * i % mod, inv[i] = qpow(mul[i], mod - 2);
}
int C(int m, int n){
    return mul[n] * inv[n - m] % mod * inv[m] % mod;
}
int q[N], h[N];
void solve(){
    n = read(), m1 = read(), m2 = read();
    for (int i = 1; i <= m1; i++) q[i] = read();
    for (int i = 1; i <= m2; i++) h[i] = read();
    if (q[1] != 1 || h[m2] != n || q[m1] != h[1]){
        cout << 0 << '\n';
        return;
    }
    int ans = C(q[m1] - 1, n - 1);
    for (int i = m1 - 1; i >= 1; i--){
        ans = ans * C(q[i] - 1, q[i + 1] - 2) % mod;
        ans = ans * mul[q[i + 1] - q[i] - 1] % mod;
    }
    for (int i = 2; i <= m2; i++){
        ans = ans * C(n - h[i], n - h[i - 1] - 1) % mod;
        ans = ans * mul[h[i] - h[i - 1] - 1] % mod;
    }
    cout << ans << '\n';
}

signed main(){
    init();
    T = read();
    while (T--) solve();
    return 0;
}

[ABC318E] Sandwiches

思路

正着做不好动态维护,于是考虑反着做。考虑加入了一个数,在它的的后面有很多与它相同的数,于是思考如何从上一个相同的数的答案转移过来,首先答案要加上上一个的答案,因为你可以看成着一个数就相当于上一个数,前提是忽略这个数和上一个数之间不同的数,如果有不同的数,这个数可以与中间不同的数和后面任意一个相同的数匹配,于是乘法原理计算即可。

代码

#include<iostream>
#include<vector>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 3e5 + 10;
int n, ans;
int a[N];
vector<int> g[N];
int res[N];

signed main(){
    n = read();
    for (int i = 1; i <= n; i++) a[i] = read();
    for (int i = n; i >= 1; i--){
        if (!g[a[i]].empty()) res[a[i]] += g[a[i]].size() * (g[a[i]].back() - i - 1);
        g[a[i]].emplace_back(i);
        ans += res[a[i]];
    }
    cout << ans;
    return 0;
}

P1282 多米诺骨牌

思路

非常有意思的背包题目。

如果多米诺牌上面的数的和确定了下面数的和也就确定了,差值也能求出来,于是直接背包凑出上面的数需要最小的代价,最后扫一遍统计答案即可。

代码

#include<iostream>
#include<cmath>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 1e3 + 10;
int n, sum, res = 0x7fffffff, p;
int a[N], b[N], dp[N][N * 6];

signed main(){
    n = read();
    for (int i = 1; i <= n; i++) a[i] = read(), b[i] = read(), sum += a[i] + b[i];
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= 6 * n; j++) dp[i][j] = 0x7fffffff;
    dp[0][0] = 0;
    for (int i = 1; i <= n; i++){
        for (int j = 0; j <= 6 * n; j++){
            if (j - a[i] >= 0) dp[i][j] = min(dp[i][j], dp[i - 1][j - a[i]]);
            if (j - b[i] >= 0) dp[i][j] = min(dp[i][j], dp[i - 1][j - b[i]] + 1);
        }
    }
    for (int i = n; i <= 6 * n; i++)
        if (dp[n][i] != 0x3f3f3f3f){
            if (res >= abs(i - (sum - i))){
                res = abs(i - (sum - i));
                p = i;
            }
        }
    cout << dp[n][p];
    return 0;
}

[ABC369G] As far as possible

思路

仔细想一想发现并不是博弈论,第一个人每次需要选择离上次选取的所有点行走最远的点,第二个人需要走最小路径,考虑这个最小路径怎么来的,发现就是每条走过的边的边权乘以 2,那么这个很好求,只要确定了 k 个点就能确定这个答案。接下来考虑如何找到下一个点,根据上面的性质加上每次选点都是叶子节点,不难想到其实就是选出 k 条无重边的链,终点都在叶子节点上,将它们的权值加起来乘以二即为答案,而第一个人要使答案尽可能大,就要选取权值最大的 k 条链,找链直接上长链剖分即可。

代码

#include<iostream>
#include<algorithm>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 2e5 + 10;
int n, ans;
struct edge{
    int v, w, nxt;
}e[N << 1];
int head[N], cnt;
void add(int u, int v, int w){
    e[++cnt] = (edge){v, w, head[u]};
    head[u] = cnt;
}
int sz[N], son[N], val[N], f[N];
void dfs1(int u, int fa, int w){
    sz[u] = w;
    f[u] = fa;
    for (int i = head[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if (v == fa) continue;
        dfs1(v, u, val[v] = e[i].w);
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
    sz[u] += sz[son[u]];
}
int p[N];
void dfs2(int u, int t){
    p[t] += val[u];
    if (son[u]) dfs2(son[u], t);
    for (int i = head[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if (v == f[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}

signed main(){
    n = read();
    for (int i = 1; i < n; i++){
        int u = read(), v = read(), w = read();
        add(u, v, w), add(v, u, w);
    }
    dfs1(1, 0, 0);
    dfs2(1, 1);
    sort(p + 1, p + n + 1, greater<int>());
    for (int i = 1; i <= n; i++){
        ans += p[i];
        cout << (ans << 1) << '\n';
    }
    return 0;
}

[ARC183C] Not Argmax

思路

注意到 N 很小,考虑区间 dp。

dpl,r 表示在 [l,r] 中放入 rl+1 个不同的数的方案数,那么考虑枚举最大值所在的位置进行转移,设 k 是最大值的位置,若 k 合法,那么有如下的递推式:

dpl,r=k=lr(rlkl)×dpl,k1×dpk+1,r

下面来解释一下这个递推式,等号左边是枚举 k,中间的组合数表示除了最大值确定以外将剩下 rl 个数分 kl 个到左边的方案数,由于左边的数确定了,右边的数也就确定了,那么对于每种分法左边有 dpl,k1 种排列方式,也就是将 kl 个数放入 [l,k1] 中的方案数,右边同理也要乘起来。

递推式解决了,还要知道那些 k 合法才能通过它转移,若此是被转移的区间是 [l,r],有一个区间 [L,R] 里的 k 不能是这个区间的最大值,那么如果 [L,R][l,r] 那么肯定最大值不能为 k,无法转移,如果不是包含的关系而是有交集呢?k 还是能够转移给 [l,r],因为在 [l,r] 区间内 k 可以为最大值,接下来它会不断转移给其他区间,k 能够成为某些区间的最大值,但如果有区间出现了上面的情况就不会再通过 k 转移了,于是这样转移肯定是不重不漏的。

如何处理一个位置 k 能否转移给 [l,r] 呢?当输入 L,R,x 时,那么 x[l,r](l[1,L],r[R,n]) 中都不能转移,于是直接左右拓展标记不合法的区间即可。

代码

#include<iostream>
#include<bitset>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 510, M = 1e5 + 10, mod = 998244353;
int n, m;
bitset<N> g[N][N];
int dp[N][N];
int mul[N], inv[N];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void init(){
    for (int len = 2; len <= n; len++)
        for (int l = 1; l + len - 1 <= n; l++){
            int r = l + len - 1;
            g[l][r] |= g[l + 1][r] | g[l][r - 1];
        }
    mul[0] = inv[0] = 1;
    for (int i = 1; i <= n; i++) mul[i] = mul[i - 1] * i % mod, inv[i] = qpow(mul[i], mod - 2);
}
int C(int n, int m){
    return mul[n] * inv[n - m] % mod * inv[m] % mod;
}

signed main(){
    n = read(), m = read();
    for (int i = 1; i <= m; i++){
        int l = read(), r = read(), x = read();
        if (l == r){
            cout << '0';
            return 0;
        }
        g[l][r][x] = 1;
    }
    init();
    for (int i = 0; i <= n; i++) dp[i][i] = dp[i + 1][i] = 1;
    for (int len = 2; len <= n; len++){
        for (int l = 1; l + len - 1 <= n; l++){
            int r = l + len - 1;
            for (int k = l; k <= r; k++){
                if (g[l][r][k]) continue;
                dp[l][r] = (dp[l][r] + dp[l][k - 1] * dp[k + 1][r] % mod * C(r - l, k - l) % mod) % mod;
            }
        }
    }
    cout << dp[1][n];
    return 0;
}

LCM Sum (hard version)

思路

正着做不好做,考虑用总数减去不好的三元组的数量,即 lcm(i,j,k)<i+j+k 的数量。

由于 i<j<k,所以 lcm(i,j,k)<3k 才有可能成为不好的三元组,那么 lcm(i,j,k)=klcm(i,j,k)=2k 但在等于 2k 时要满足 i+j<k

如何统计答案?首先总的三元组的数量为 (rl+13),手玩一下发现当 lcm(i,j,k)=2k 且不好的三元组只有可能是 (3x,4x,6x)(6x,10x,15x),由于 l3x<6xr,所以第一种情况的数量为 r6l13,第二种情况的数量同理就为 r15l16,将答案减去这些即可完成等于 2k 的讨论。

接下来考虑如何统计等于 k 的三元组数量,那么 ij 都为 k 的约数,我们发现一个这样的三元组与 j 无关,只与一个二元组 (i,k),我们只需要知道 ik 值,统计有多少个 j 满足条件即为一个二元组 (i,k) 的权值,这部分可以预处理得到,于是只需枚举二元组将每个二元组的权值加起来即为等于 k 的三元组的数量。

考虑暴力枚举二元组不优秀,因为 T105,但由于没有强制在线,可以用二维数点将其离线下来去做,将一个二元组 (i,k) 看成平面上的点 (x,y),每个点有权重,那么询问区间等于 k 的三元组数量即为右上角为 (r,r),左下角为 (l,l) 的矩形中的点的权重之和。

代码

#include<iostream>
#include<vector>
#include<algorithm>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 2e5 + 10, M = 1e7 + 10, K = 2e5;
int T;
vector<int> g[N];
void init(){
    for (int i = 2; i <= K; i++){
        for (int j = 1; j * j <= i; j++){
            if (i % j == 0){
                g[i].emplace_back(j);
                if (j * j != i && j != 1) g[i].emplace_back(i / j);
            }
        }
    }
    for (int i = 2; i <= K; i++) sort(g[i].begin(), g[i].end());
}
struct node{
    int l, r, id, res;
}q[N];
struct Query{
    int x, y, cnt, id, opt;
    bool operator < (const Query &b) const{
        return (x == b.x ? opt < b.opt : x < b.x);
    }
}p[M];
int cnt;
void get(){
    for (int i = 2; i <= K; i++){
        int h = g[i].size();
        for (int j = 0; j < h; j++){
            p[++cnt] = (Query){g[i][j], i, h - j - 1, 0, 0};
        }
    }
}
int t[N];
int lowbit(int x){return x & -x;}
void add(int x, int k){
    for (int i = x; i <= K; i += lowbit(i)) t[i] += k;
}
int query(int x){
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += t[i];
    return res;
}

signed main(){
    T = read();
    init();
    for (int i = 1; i <= T; i++){
        int l = read(), r = read();
        q[i].l = l, q[i].r = r, q[i].id = i;
        q[i].res = (r - l + 1) * (r - l) * (r - l - 1) / 6;
        q[i].res -= max(r / 6 - (l - 1) / 3, 0ll);
        q[i].res -= max(r / 15 - (l - 1) / 6, 0ll);
    }
    get();
    for (int i = 1; i <= T; i++){
        p[++cnt] = (Query){q[i].r, q[i].r, 0, q[i].id, 1};
        p[++cnt] = (Query){q[i].r, q[i].l - 1, 0, q[i].id, 2};
        p[++cnt] = (Query){q[i].l - 1, q[i].r, 0, q[i].id, 2};
        p[++cnt] = (Query){q[i].l - 1, q[i].l - 1, 0, q[i].id, 1};
    }
    sort(p + 1, p + cnt + 1);
    for (int i = 1; i <= cnt; i++){
        if (p[i].opt == 0) add(p[i].y, p[i].cnt);
        else if (p[i].opt == 1) q[p[i].id].res -= query(p[i].y);
        else q[p[i].id].res += query(p[i].y);
    }
    for (int i = 1; i <= T; i++) cout << q[i].res << '\n';
    return 0;
}

P6105 [Ynoi2010] y-fast trie

思路

分情况讨论,我们把加入集合的数 aimodC,如果 i+jC,那么 ij 都是集合中最大的数,如果 i+j<C,那么 ij 肯定能互相匹配,得到尽可能大的不超过 C 的数,且这种匹配是唯一的,那么考虑加入一个数 x,如果 x 的最优匹配是 y,且未加入 xy 的最优匹配是 z,如果 x>zyx 匹配最优,去除之前的 yz 的匹配,加入 xy 的匹配,删除操作就是先把 xx 的最优匹配一起删去,再加入 y

代码

#include<iostream>
#include<set>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

int n, mod, lastans;
multiset<int> s, k;
int find(int x, bool f){
    if (x == -1) return -1;
    multiset<int>::iterator it = s.upper_bound(mod - 1 - x);
    if (it == s.begin()) return -1;
    it--;
    if (f && x == *it && s.count(x) == 1){
        if (it == s.begin()) return -1;
        return *--it;
    }
    return *it;
}
void insert(int x){
    if (s.empty()){
        s.insert(x);
        return;
    }
    int y = find(x, 0), z = find(y, 1), p = find(z, 1);
    if (y != -1 && x > z){
        if (z != -1 && y == p) k.erase(k.find(y + z));
        k.insert(x + y);
    }
    s.insert(x);
}
void del(int x){
    s.erase(s.find(x));
    if (s.empty()) return;
    int y = find(x, 0), z = find(y, 1), p = find(z, 1);
    if (y != -1 && x > z){
        if (z != -1 && y == p) k.insert(y + z);
        k.erase(k.find(x + y));
    }
}
int query(){
    multiset<int>::iterator it = --s.end();
    int res;
    if (s.count(*it) >= 2) res = 2 * (*it) % mod;
    else res = (*it + *--it) % mod;
    if (!k.empty()) res = max(res, *--k.end());
    return res;
}

int main(){
    n = read(), mod = read();
    for (int i = 1; i <= n; i++){
        int opt = read(), x = (read() ^ lastans) % mod;
        if (opt == 1) insert(x);
        else del(x);
        if (s.size() <= 1) cout << "EE\n", lastans = 0;
        else cout << (lastans = query()) << '\n';
    }
    return 0;
}

P7091 数上的树

思路

dpi 表示 n 的第 i 大的约数 di 为根,答案的最小值,显然这具有最优子结构性质,可以用动态规划去做。

gi 表示以 gi 为根时,树中节点的个树,显然,若 di=dj×dk,那么 gi=gj+gk+1,且 gi 是个定值,那么 dp 数组的转移方程式就为 dpi=dpj+dpk+(gj×gk+gi)di,前提是 di=dj×dk

这样做的时间复杂度为 O(k3)kn 的约数个数,我们发现,在枚举 j 的时候,k 具有单调性,于是 k 直接变为指针去做时间复杂度就变为 O(k2)

代码

#include<iostream>
#include<map>
#include<cmath>
#include<algorithm>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 1e5 + 10;
int n, m;
int d[N], cnt, dp[N], g[N];
map<int, bool> mp;
void init(){
    for (int i = 1; i * i < n; i++)
        if (n % i == 0)
            d[++cnt] = i, d[++cnt] = n / i;
    int x = sqrt(n);
    if (x * x == n) d[++cnt] = x;
}

signed main(){
    n = read(), m = read();
    init();
    for (int i = 1; i <= m; i++) mp[read()] = 1;
    sort(d + 1, d + cnt + 1);
    for (int i = 1; i <= cnt; i++){
        if (mp[d[i]]) dp[i] = -1;
        else dp[i] = -114514;
    }
    for (int i = 1; i <= cnt; i++){
        if (dp[i] == -1) continue;
        bool f = 0;
        for (int j = 2; j < i; j++)
            if (d[i] % d[j] == 0) f = 1;
        if (!f) g[i] = 1, dp[i] = d[i];
    }
    for (int i = 2; i <= cnt; i++){
        for (int j = 2, k = i - 1; j < i; j++){
            while (k >= j && d[j] * d[k] > d[i]) k--;
            if (k < j) break;
            if (d[i] == d[j] * d[k] && dp[i] != -1 && dp[j] != -1 && dp[k] != -1 && dp[j] != -114514 && dp[k] != -114514){
                g[i] = g[j] + g[k] + 1;
                if (dp[i] == -114514) dp[i] = dp[j] + dp[k] + (g[j] * g[k] + g[i]) * d[i];
                else dp[i] = min(dp[i], dp[j] + dp[k] + (g[j] * g[k] + g[i]) * d[i]);
            }
        }
    }
    cout << (dp[cnt] == -114514 ? -1 : dp[cnt]);
    return 0;
}

Card Bag

思路

考虑概率 dp,设 dpi,j 表示抽出来了 j 张牌,最后一张是 i,设 pi 表示牌 i 出现的次数,于是 dpi,j=pinj+1×k=0i1dpk,j,每次答案加上 dpi,j×pi1nj,发现 k=0i1dpk,j 可以用前缀和优化,时间复杂度 O(n2)

代码

#include<iostream>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 5e3 + 10, mod = 998244353;
int n, ans;
int p[N], dp[N][N], sum[N][N], inv[N];
int qpow(int a, int b){
    int ans = 1;
    while (b){
        if (b & 1) ans = ans * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return ans;
}
void init(){
    inv[0] = 1;
    for (int i = 1; i <= n; i++) inv[i] = qpow(i, mod - 2);
}

signed main(){
    n = read();
    for (int i = 1; i <= n; i++) p[read()]++;
    init();
    for (int i = 0; i <= n; i++) sum[i][0] = 1;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= i; j++){
            dp[i][j] = sum[i - 1][j - 1] * p[i] % mod * inv[n - j + 1] % mod;
            ans = (ans + dp[i][j] * ((p[i] % mod - 1 + mod) % mod) % mod * inv[n - j] % mod) % mod;
            sum[i][j] = (sum[i - 1][j] + dp[i][j]) % mod;
        }
    }
    cout << ans;
    return 0;
}

Blood Cousins

思路

为此特意去学了 dsu,结果发现是 dsu 板子。

求出每个询问的 k 级祖先,只有深度相同的才能成为同一个 k 级表亲,于是上树上启发式合并,先处理轻儿子及其子树,然后清空轻儿子的答案,再处理重儿子的答案,保留答案,再暴力跑一边轻儿子合并答案。

代码

#include<iostream>
#include<vector>

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 1e5 + 10;
int n, m;
int f[N][23];
struct edge{
    int v, nxt;
}e[N];
int head[N], cnt;
void add(int u, int v){
    e[++cnt] = (edge){v, head[u]};
    head[u] = cnt;
}
void init(){
    for (int i = 1; i <= 20; i++)
        for (int j = 1; j <= n; j++) f[j][i] = f[f[j][i - 1]][i - 1];
}
int dep[N], sz[N], son[N];
void dfs(int u, int fa){
    dep[u] = dep[fa] + 1, sz[u] = 1;
    for (int i = head[u]; i; i = e[i].nxt){
        int v = e[i].v;
        dfs(v, u);
        sz[u] += sz[v];
        if (sz[v] > sz[son[u]]) son[u] = v;
    }
}
int find(int u, int k){
    int d = dep[u];
    for (int i = 20; i >= 0; i--)
        if (d - dep[f[u][i]] < k) u = f[u][i];
    return (u = f[u][0]);
}
struct query{
    int x, res;
}ans[N];
vector<int> g[N];
int res[N];
void calc(int u, int s, int k){
    res[dep[u]] += k;
    for (int i = head[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if (v == s) continue;
        calc(v, s, k);
    }
}
void dsu(int u, bool p){
    for (int i = head[u]; i; i = e[i].nxt){
        int v = e[i].v;
        if (v == son[u]) continue;
        dsu(v, 0);
    }
    if (son[u]) dsu(son[u], 1);
    calc(u, son[u], 1);
    for (int i = 0; i < g[u].size(); i++)
        ans[g[u][i]].res = res[dep[ans[g[u][i]].x]] - 1;
    if (!p) calc(u, 0, -1);
}

int main(){
    n = read();
    for (int i = 1; i <= n; i++){
        f[i][0] = read();
        if (f[i][0]) add(f[i][0], i);
    }
    init();
    for (int i = 1; i <= n; i++)
        if (!f[i][0]) dfs(i, 0);
    m = read();
    for (int i = 1; i <= m; i++){
        int u = read(), p = read();
        ans[i] = (query){u, 0};
        g[find(u, p)].emplace_back(i);
    }
    for (int i = 1; i <= n; i++)
        if (!f[i][0]) dsu(i, 0);
    for (int i = 1; i <= m; i++) cout << ans[i].res << ' ';
    return 0;
}

P3586 [POI2015] LOG

思路

如果大于等于 s 的数有 cnt 个,那么这 cnt 个数每个会被减去 s,小于 s 的数,每次要选 ccnt 个数减去 1,考虑将剩下的数分布到 s×(ccnt) 的矩阵中,将一个数 x 拆分成 x1,可以占 x 个位置,由于矩阵每一行中不能存在相同的数,于是考虑一列一列地将 x1 填到 (i,j)(i+x,j) 中,其中 i+x 大于 s 的部分就移到 j+1 列,由于 x<s,所以永远填不出一行内有两个相同的数,所以剩下的数的总和要大于等于 (ccnt)×s,于是用树状数组维护小于 s 的数的总和,还有大于等于 s 的数的个数。

代码

#include<iostream>
#include<algorithm>
#define int long long

using namespace std;

inline int read(){register int x = 0, f = 1;register char c = getchar();while (c < '0' || c > '9'){if (c == '-') f = -1;c = getchar();}while (c >= '0' && c <= '9'){x = (x << 1) + (x << 3) + (c ^ 48);c = getchar();}return x * f;}

const int N = 1e6 + 10;
int n, m;
int a[N], t1[N], t2[N], l[N], cnt, len, p[N];
struct node{
    char opt;
    int x, y;
}q[N];
int lowbit(int x){return x & -x;}
void add(int t[], int x, int k){
    for (int i = x; i <= len; i += lowbit(i)) t[i] += k;
}
int query(int t[], int x){
    int res = 0;
    for (int i = x; i; i -= lowbit(i)) res += t[i];
    return res;
}

signed main(){
    n = read(), m = read();
    l[++cnt] = 0;
    for (int i = 1; i <= m; i++){
        cin >> q[i].opt; q[i].x = read(), q[i].y = read();
        l[++cnt] = q[i].y;
    }
    sort(l + 1, l + cnt + 1);
    len = unique(l + 1, l + cnt + 1) - l - 1;
    for (int i = 1; i <= n; i++) a[i] = 1, add(t1, 1, 1);
    for (int i = 1; i <= m; i++){
        int x = lower_bound(l + 1, l + len + 1, q[i].y) - l;
        p[x] = q[i].y;
        q[i].y = x;
    }
    for (int i = 1; i <= m; i++){
        if (q[i].opt == 'U'){
            add(t1, a[q[i].x], -1), add(t1, q[i].y, 1);
            add(t2, a[q[i].x], -p[a[q[i].x]]), add(t2, q[i].y, p[q[i].y]);
            a[q[i].x] = q[i].y;
        }else{
            int cnt = query(t1, len) - query(t1, q[i].y - 1), sum = query(t2, q[i].y - 1);
            if (sum + cnt * p[q[i].y] >= q[i].x * p[q[i].y]) cout << "TAK\n";
            else cout << "NIE\n";
        }
    }
    return 0;
}

本文作者:bryce蒟蒻的小窝

本文链接:https://www.cnblogs.com/bryceyyds/p/18460870

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   bryce_yyds  阅读(22)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起