练习选讲(2023.9)

9 月

9.1

P5546 [POI2000] 公共串:二分,哈希,SA(紫)

二分长度 len,用一个 unordered_map 存储对于每一个字符串,当前长度的哈希值是否出现过。最后再枚举第一个字符串中出现的长度为 len 子串即可。

时间复杂度 O(nlogm),其中 m 为字符串长度。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <unordered_map>

using namespace std;

typedef unsigned long long ull;

const ull P = 19260817;

int n, minlen = 2000; ull p[2010], h[10][2010];
char s[10][2010];

bool check(int len) {
    unordered_map<ull, bool> nums[10];
    for (int i = 2; i <= n; ++i) {
        for (int j = len; j <= strlen(s[i]+1); ++j) {
            ull num = h[i][j] - h[i][j-len] * p[len];
            nums[i][num] = 1;
        }
    }

    for (int i = len; i <= strlen(s[1]+1); ++i) {
        ull num = h[1][i] - h[1][i-len] * p[len];
        bool check = 1;
        for (int j = 2; j <= n; ++j) check &= nums[j][num];
        if (check) return 1;
    }
    return 0;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> s[i]+1;
    if (n == 1) return printf("%d\n", strlen(s[1]+1)), 0;

    p[0] = 1;
    for (int i = 1; i <= 2000; ++i) p[i] = p[i-1] * P;
    for (int i = 1; i <= n; ++i) {
        minlen = min(minlen, (int)strlen(s[i]+1));
        for (int j = 1; j <= strlen(s[i]+1); ++j)
            h[i][j] = h[i][j-1] * P + (s[i][j]-'a');
    }

    int l = -1, r = minlen+1;
    while (l+1 != r) {
        int mid = l + r >> 1;
        if (check(mid)) l = mid;
        else r = mid;
    }
    printf("%d\n", l);
    return 0;
}

P8818 [CSP-S 2022] 策略游戏:st 表(绿)

显然我们应该考虑后手的情况:

1 后手无负数

1.1 先手有正数:此时先手会取区间内最大值,后手只能取区间内最小值。

1.2 先手无正数:此时先手只能取区间内最大值,后手会取区间内最大值。

2 后手无正数

2.1 先手有负数:此时先手会取区间内最小值(负负得正,相乘后变为最大值),后手会取区间内最大值。

2.2 先手无负数:此时先手只能取区间内最小值,后手会取区间内最小值。

3 后手既有正数又有负数

3.1 先手有 0:此时先手只能选 0,否则后手一定有策略使得结果为负。

3.2 先手无正数:此时先手只能取区间内最大值,后手会取区间内最大值。

3.3 先手无负数:此时先手只能取区间内最小值,后手会取区间内最小值。

3.4 先手既有正数又有负数:先手可以在 3.2 和 3.3 两种情况中取最大值,即 max(先手区间内最大负数×后手区间内最大值,先手区间内最小正数×后手区间内最小值)

观察上面这 8 种情况,我们可以发现,我们需要维护:

  • 区间内最大/小值;
  • 区间内最大非正数,区间内最小非负数。

4 个 st 表维护即可。时间复杂度 O(nlogn+q)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long ll;

const int N = 1e5+10, INF = 2e9;

int n, m, q;
int st[3][5][N][20];
// 1: 区间内最大值, 2: 区间内最小值, 3: 区间内最大非正数, 4: 区间内最小非负数

int query(int p, int op, int l, int r) {
    int k = log2(r-l+1);
    if (op & 1) return max(st[p][op][l][k], st[p][op][r-(1<<k)+1][k]);
    else return min(st[p][op][l][k], st[p][op][r-(1<<k)+1][k]);
}

int main() {
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= n; ++i) {
        int x; scanf("%d", &x);
        st[1][1][i][0] = x, st[1][2][i][0] = x;
        st[1][3][i][0] = (x > 0) ? -INF : x, st[1][4][i][0] = (x < 0) ? INF : x;
    }
    for (int i = 1; i <= m; ++i) {
        int x; scanf("%d", &x);
        st[2][1][i][0] = x, st[2][2][i][0] = x;
        st[2][3][i][0] = (x > 0) ? -INF : x, st[2][4][i][0] = (x < 0) ? INF : x;
    }

    for (int i = 1; (1<<i) <= n; ++i) {
        for (int j = 1; j+(1<<i)-1 <= n; ++j)   
            st[1][1][j][i] = max(st[1][1][j][i-1], st[1][1][j+(1<<i-1)][i-1]),
            st[1][2][j][i] = min(st[1][2][j][i-1], st[1][2][j+(1<<i-1)][i-1]),
            st[1][3][j][i] = max(st[1][3][j][i-1], st[1][3][j+(1<<i-1)][i-1]),
            st[1][4][j][i] = min(st[1][4][j][i-1], st[1][4][j+(1<<i-1)][i-1]);
    }
    for (int i = 1; (1<<i) <= m; ++i) {
        for (int j = 1; j+(1<<i)-1 <= m; ++j)   
            st[2][1][j][i] = max(st[2][1][j][i-1], st[2][1][j+(1<<i-1)][i-1]),
            st[2][2][j][i] = min(st[2][2][j][i-1], st[2][2][j+(1<<i-1)][i-1]),
            st[2][3][j][i] = max(st[2][3][j][i-1], st[2][3][j+(1<<i-1)][i-1]),
            st[2][4][j][i] = min(st[2][4][j][i-1], st[2][4][j+(1<<i-1)][i-1]);
    }

    int l1, r1, l2, r2;
    while (q -- ) {
        scanf("%d%d%d%d", &l1, &r1, &l2, &r2);
        int maxa = query(1, 1, l1, r1), mina = query(1, 2, l1, r1), maxb = query(2, 1, l2, r2), minb = query(2, 2, l2, r2);
        if (minb >= 0) {
            if (maxa > 0) printf("%lld\n", (ll)maxa*minb);
            else printf("%lld\n", (ll)maxa*maxb);
        } else if (maxb <= 0) {
            if (mina < 0) printf("%lld\n", (ll)mina*maxb);
            else printf("%lld\n", (ll)mina*minb);
        } else {
            int maxxa = query(1, 3, l1, r1), minna = query(1, 4, l1, r1);
            if (maxxa == 0) puts("0");
            else if (maxa < 0) printf("%lld\n", (ll)maxa*maxb);
            else if (mina > 0) printf("%lld\n", (ll)mina*minb);
            else printf("%lld\n", max((ll)maxxa*maxb, (ll)minna*minb));
        }
    }
    return 0;
}

补之前的 ABC 317:

ABC317A. Potions:枚举(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int n, h, x, a[110];

int main() {
	scanf("%d%d%d", &n, &h, &x);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]);
		if (h+a[i] >= x) printf("%d\n", i), exit(0);
	}
}

ABC317B. MissingNo:枚举(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int n, minl = 1001, nums[1100];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		int x; scanf("%d", &x);
		minl = min(minl, x), nums[x] ++;
	}
	
	for (int i = minl; i <= minl+n; ++i) {
		if (!nums[i])
			printf("%d\n", i), exit(0); 
	}
}

ABC317C. Remembering the Days:枚举,dfs(橙)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

int n, m, ans, a[15], g[15][15];

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v, w; scanf("%d%d%d", &u, &v, &w);
		g[u][v] = g[v][u] = w;
	}
	
	for (int i = 1; i <= n; ++i) a[i] = i;
	while (true) {
		int tot = 0;
		for (int i = 1; i < n; ++i) {
			if (g[a[i]][a[i+1]]) tot += g[a[i]][a[i+1]];
			else break;
		}
		ans = max(ans, tot);
		if (!next_permutation(a+1, a+n+1)) break;
	}
	printf("%d\n", ans);
	return 0;
}

ABC317D. President:dp(黄)

fi,j 表示前 i 个选取中共获得 j 张票的最小花费。初始时若 x1>y1f1,z1=0;否则 f1,z1=y1x1+y12

同样的,当 xi>yi 时,状态转移方程为:

fi,j=fi1,jzi

否则,

fi,j=min{fi1,j,fi1,jzi+yixi+yi2}

答案为 mintot/2jtotfn,j

时间复杂度 O(n(xi+yi))

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

#define int long long

const int N = 110, M = 1e5+10;

int n, ans, tot, x[N], y[N], z[N], f[N][M];

signed main() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; ++i) scanf("%lld%lld%lld", &x[i], &y[i], &z[i]), tot += z[i];
	
	for (int i = 1; i <= tot; ++i) f[1][i] = 1e18;
	f[1][z[1]] = (x[1] > y[1]) ? 0 : y[1]-(x[1]+y[1])/2;
	for (int i = 2; i <= n; ++i) {
		for (int j = 0; j <= tot; ++j) {
			f[i][j] = (int)1e18;
			if (x[i] > y[i] && j >= z[i]) f[i][j] = min(f[i][j], f[i-1][j-z[i]]);
			if (x[i] < y[i]) f[i][j] = min(f[i-1][j], (j>=z[i])?(y[i]-(x[i]+y[i])/2+f[i-1][j-z[i]]):(int)1e18);
		}
	}
	
	ans = 1e18;
	for (int i = (tot+1)/2; i <= tot; ++i) ans = min(ans, f[n][i]);
	printf("%lld\n", ans);
	return 0;
}

ABC317E. Avoid Eye Contact:bfs(黄)

实际上直接暴力处理出地图就好了,但我赛时脑抽了写了个奇丑的二分……

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

const int dx[] = {-1, 0, 1, 0}, dy[] = {0, 1, 0, -1};

typedef pair<int, int> pii; 

int n, m, dist[2010][2010]; pii S, T; 
vector<pii> row[2010], col[2010];
char c[2010][2010];

bool check(int x, int y) {
	if (!(x && y && (x <= n) && (y <= m) && (c[x][y] == '.' || c[x][y] == 'G'))) return 0;
	int l = upper_bound(row[x].begin(), row[x].end(), make_pair(y, 0)) - row[x].begin() - 1;
	int r = upper_bound(row[x].begin(), row[x].end(), make_pair(y, 0)) - row[x].begin();
	int u = upper_bound(col[y].begin(), col[y].end(), make_pair(x, 0)) - col[y].begin() - 1;
	int d = upper_bound(col[y].begin(), col[y].end(), make_pair(x, 0)) - col[y].begin();
	return ((l == -1 || row[x][l].second > -1) && (r == row[x].size() || row[x][r].second < 1) &&
		    (u == -1 || col[y][u].second < 1) && (d == col[y].size() || col[y][d].second > -1));
}

void bfs() {
	queue<pii> q; q.push(S), dist[S.first][S.second] = 0;
	while (q.size()) {
		auto t = q.front(); q.pop();
		int x = t.first, y = t.second;
		for (int i = 0; i < 4; ++i) {
			int x_ = x+dx[i], y_ = y+dy[i];
			if (dist[x_][y_] > dist[x][y]+1 && check(x_, y_)) dist[x_][y_] = dist[x][y]+1, q.push({x_, y_});
		}
	}
}

int main() {
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) cin >> c[i]+1;
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j)
			dist[i][j] = 1e9;
	}
	
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			if (c[i][j] == '.') continue;
			else if (c[i][j] == '#') row[i].push_back({j, 0}), col[j].push_back({i, 0});
			else if (c[i][j] == 'S') S = {i, j};
			else if (c[i][j] == 'G') T = {i, j};
			else if (c[i][j] == '>') row[i].push_back({j, -1}), col[j].push_back({i, 0});
			else if (c[i][j] == '<') row[i].push_back({j, 1}), col[j].push_back({i, 0});
			else if (c[i][j] == '^') col[j].push_back({i, -1}), row[i].push_back({j, 0});
			else if (c[i][j] == 'v') col[j].push_back({i, 1}), row[i].push_back({j, 0});
		}
	}
	
	bfs();
	if (dist[T.first][T.second] == 1e9) puts("-1");
	else printf("%d\n", dist[T.first][T.second]);
	return 0;
}

ABC317F. Nim:数位 dp(蓝)

注意到 X,Y,Z 都很小但 N 很大,考虑数位 dp。令 fu,a,b,c,l1,l2,l3,z1,z2,z3 表示计算到 N 的二进制第 u 位,当前选的数模 X,Y,Z 的余数分别是 a,b,c,且三个数是否到当前位上界,是否为 0 的方案数,记忆化搜索实现。

时间复杂度 O(26X3logN)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int P = 998244353;

long long n; 
int x, y, z, r[65], f[65][15][15][15][2][2][2][2][2][2];

int dfs(int u, int a, int b, int c, bool l1, bool l2, bool l3, bool z1, bool z2, bool z3) {
    if (u < 0) return ((!a) && (!b) && (!c) && (!z1) && (!z2) && (!z3));
    if (f[u][a][b][c][l1][l2][l3][z1][z2][z3] != -1) return f[u][a][b][c][l1][l2][l3][z1][z2][z3];
    
    int res = 0;
    for (int i = 0; i <= (l1 ? r[u] : 1); ++i) {
        for (int j = 0; j <= (l2 ? r[u] : 1); ++j) {
            for (int k = 0; k <= (l3 ? r[u] : 1); ++k) {
                if (i ^ j ^ k) continue;
                res += dfs(u-1, (a+(1ll<<u)*i)%x, (b+(1ll<<u)*j)%y, (c+(1ll<<u)*k)%z, (l1&&(i==r[u])), (l2&&(j==r[u])), (l3&&(k==r[u])), (z1&&(!i)), (z2&&(!j)), (z3&&(!k)));
                res %= P;
            }
        }
    }
    return f[u][a][b][c][l1][l2][l3][z1][z2][z3] = res;
}

int main() {
    memset(f, -1, sizeof f);
    scanf("%lld%d%d%d", &n, &x, &y, &z);
    for (int i = 62; i >= 0; --i) r[i] = ((n >> i) & 1);
    printf("%d\n", dfs(62, 0, 0, 0, 1, 1, 1, 1, 1, 1));
    return 0;
}

9.2

ABC317G. Rearranging:二分图,最大流(紫)

将这个问题转化为一个图论问题,若 ai,j=x,我们就从表示第 i 行的点向表示数 x 的点之间连一条边。显然最后会形成一个二分图,若该二分图恰好有 m 个完美匹配则存在答案。

Hall 定理:二分图有完美匹配的充要条件是,对于左部点集的任意一个子集 S,它的邻居集合(可重集)N(S) 均满足 |S||N(S)|

实际上,Hall 定理可以推广到有 m 个完美匹配的形式,此时需要满足 m|S||N(S)|

我们先给出做法:不断寻找完美匹配,找到后把该完美匹配内的所有边删除,重复 m 次。若有一次找不到完美匹配则无解。那么,若原图满足上述条件,删除一组完美匹配后仍然满足上述条件;否则无解。

通过 Dinic 算法求解二分图的完美匹配,时间复杂度 O(n1.5m2)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int N = 410, M = 50010, INF = 2e9;

int n, m, S, T, a[N][N], ans[N][N];
int idx = 1, cnt, e[M], ne[M], h[N], c[M];
int d[N], cur[N];

void add(int u, int v, int w) {
    e[++ idx] = v, ne[idx] = h[u], c[idx] = w, h[u] = idx;
}

bool bfs() {
    memset(d, 0, sizeof d);
    queue<int> q; q.push(S), d[S] = 1, cur[S] = h[S];
    while (!q.empty()) {
        int u = q.front(); q.pop();
        for (int i = h[u]; i != -1; i = ne[i]) {
            int v = e[i];
            if (!d[v] && c[i] > 0) {
                d[v] = d[u]+1, q.push(v), cur[v] = h[v];
                if (v == T) return 1;
            }
        }
    }
    return 0;
}

int dinic(int u, int mf) {
    if (u == T) return mf;
    int sum = 0;
    for (int i = cur[u]; i != -1; i = ne[i]) {
        cur[u] = i;
        int v = e[i];
        if (d[v] == d[u]+1 && c[i] > 0) {
            int f = dinic(v, min(mf, c[i]));
            c[i] -= f, c[i^1] += f, sum += f, mf -= f;
            if (!mf) break;
        }
    }
    if (!sum) d[u] = 0;
    return sum;
}

int MaxFlow() {
    int flow = 0;
    while (bfs()) flow += dinic(S, INF);
    return flow;
}

int main() {
    S = 0, T = 400;
    memset(h, -1, sizeof h);

    scanf("%d%d", &n, &m); 
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j) {
            scanf("%d", &a[i][j]);
            add(i, a[i][j]+n, 1), add(a[i][j]+n, i, 0);
        }
    }
    cnt = idx;
    for (int i = 1; i <= n; ++i) add(S, i, 1), add(i, S, 0), add(i+n, T, 1), add(T, i+n, 0);

    for (int j = 1; j <= m; ++j) {
        if (MaxFlow() != n) return puts("No"), 0;
        for (int i = 3; i <= cnt; i += 2) {
            if (!c[i]) continue;
            int u = e[i], v = e[i^1];
            ans[u][j] = v-n;
            c[i] = c[i^1] = 0;
        }
        for (int i = cnt+2; i <= idx; i += 2) {
            if (!c[i]) continue;
            c[i^1] = 1, c[i] = 0;
        }
    }

    puts("Yes");
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= m; ++j)
            printf("%d ", ans[i][j]);
        puts("");
    }
    return 0;
}

CF540E. Infinite Inversions:树状数组(紫)

(双倍经验:P2448 无尽的生命

答案可以分为两种情况:

  • 两个调换过位置的数构成的逆序对:

​ 这种情况直接用树状数组维护即可。

  • 一个调换过位置的数和一个没有调换过位置的数构成的逆序对:

​ 令 x 表示调换过位置的数,其调换后位置为 yxy 之间还有 k 个调换过位置的数,那么其对答案的贡献为 |xyk|

显然,两个没有调换过位置的数不可能构成逆序对。

时间复杂度 O(nlogn)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> pii;

const int N = 2e5+10;

int n, a[N], c[N]; long long ans; pii oper[N];
vector<int> pos;

int find(int x) {
    return lower_bound(pos.begin(), pos.end(), x) - pos.begin() + 1;
}

int lowbit(int x) {
    return x & -x;
}

void add(int x, int k) {
    for (int i = x; i < N; i += lowbit(i)) c[i] += k;
}

int query(int x) {
    int sum = 0;
    for (int i = x; i > 0; i -= lowbit(i)) sum += c[i];
    return sum;
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        int x, y; scanf("%d%d", &x, &y);
        oper[i] = {x, y}; pos.push_back(x), pos.push_back(y);
    }

    sort(pos.begin(), pos.end());
    pos.erase(unique(pos.begin(), pos.end()), pos.end());
    for (int i = 1; i <= pos.size(); ++i) a[i] = pos[i-1];

    for (int i = 1; i <= n; ++i) swap(a[find(oper[i].first)], a[find(oper[i].second)]);
    for (int i = 1; i <= pos.size(); ++i) {
        int x = query(find(a[i]));
        ans += (find(a[i])-1 - x) + abs(a[i]-pos[i-1]-(find(a[i])-find(pos[i-1])));
        add(find(a[i]), 1);
    }
    printf("%lld\n", ans);
    return 0;
}

P5854 【模板】笛卡尔树:单调栈,笛卡尔树(蓝)

笛卡尔树是一种特殊的二叉搜索树,其一个节点上有两个信息 (xi,yi),如果只考虑 xi,它是一棵二叉搜索树;如果只考虑 yi,它是一个小根堆。

在保证 xi 递增的情况下,我们可以在 O(n) 的时间复杂度内构建笛卡尔树。

每次插入一个新节点时,为了确保二叉搜索树的性质得到满足,我们需要将新节点插入到尽可能靠右的位置。也就是说,我们需要维护从根节点一直到右儿子形成的链。设当前要插入的点为 u,则我们需要从下往上在这条链上找到第一个点 v 满足 yv<yu,那么将 v 的右儿子设置为 u(若不存在这样的 v,则将 u 设为根节点),若 v 已经有右子树了,则将 v 原来的右儿子设为 u 的左儿子。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 1e7+10;

int n, top, p[N], L[N], R[N], stk[N]; ll ans1, ans2;

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<23],*p1=buf,*p2=buf;
template <typename T>
void read(T &x) {
    x = 0;
    register int flag = 1;
    static char c = getchar();
    while (!isdigit(c)) {
        if (c == '-') flag = -1;
        c = getchar();
    }
    while (isdigit(c)) {
        x = x * 10 + c - '0';
        c = getchar();
    }
    x *= flag;
}

void build() {
    for (int i = 1; i <= n; ++i) {
        while (top && p[stk[top]] > p[i]) top --;
        if (!top) L[i] = stk[top+1];
        else L[i] = R[stk[top]], R[stk[top]] = i;
        stk[++ top] = i;
    }
}

int main() {
    read(n);
    for (int i = 1; i <= n; ++i) read(p[i]);

    build();

    for (int i = 1; i <= n; ++i) ans1 ^= 1ll * i * (L[i]+1), ans2 ^= 1ll * i * (R[i]+1);
    printf("%lld %lld\n", ans1, ans2);
    return 0;
}

9.3

P3538 [POI2012] OKR-A Horrible Poem:字符串哈希,线性筛(蓝)

本题需要用到循环节的几个关键性质:

  • s[lrlen]=s[l+lenr],则 lens[lr] 的循环节长度。

  • 循环节 × 循环次数 = 总长度,即总长度的质因子可分为循环节和循环次数。

  • 最小循环节的倍数也是循环节。

知道这些之后,我们可以将区间长度 len 分解质因数,即不断将 len 除以其最小质因子,并判断能否成为循环节。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef unsigned long long ull;

const int N = 5e5+10;
const ull P = 998244353;

int n, m; char s[N];
ull h[N], power[N];
int cnt, prime[N], p[N]; bool st[N];
// p[i]表示i的最小质因子

void Euler() {
    st[0] = st[1] = 1;
    for (int i = 2; i <= n; ++i) {
        if (!st[i]) prime[++ cnt] = i, p[i] = i;
        for (int j = 1; j <= cnt && prime[j]*i <= n; ++j) {
            st[i*prime[j]] = 1, p[i*prime[j]] = prime[j];
            if (i % prime[j] == 0) break;
        }
    }
}

ull get_hash(int l, int r) {
    return h[r] - h[l-1] * power[r-l+1];
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);

    cin >> n >> s+1 >> m;
    Euler();
    power[0] = 1; for (int i = 1; i <= n; ++i) power[i] = power[i-1] * P;
    for (int i = 1; i <= n; ++i) h[i] = h[i-1] * P + (s[i]-'a'); 

    int l, r, ans, len;
    while (m -- ) {
        cin >> l >> r;
        ans = len = r-l+1;
        while (len > 1) {
            int newlen = ans / p[len];
            if (get_hash(l, r-newlen) == get_hash(l+newlen, r)) ans /= p[len];
            len /= p[len];
        }
        cout << ans << '\n';
    }
    return 0;
}

P1377 [TJOI2011] 树的序:笛卡尔树(蓝)

这题与板子题的区别在于,权值符合二叉搜索树的性质,编号符合小根堆的性质(因为始终先插入父亲,再插入儿子)。那么只需要在读入时调换一下位置就好。

如何求出字典序最小的插入顺序?由于权值是二叉搜索树,所以先序遍历输出答案即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1e5+10;

int n, p[N], L[N], R[N];
int top, stk[N];

void build() {
    for (int i = 1; i <= n; ++i) {
        while (top && p[stk[top]] > p[i]) top --;
        if (!top) L[i] = stk[top+1];
        else L[i] = R[stk[top]], R[stk[top]] = i;
        stk[++ top] = i;
    }
}

void dfs(int u) {
    if (!u) return ;
    printf("%d ", u), dfs(L[u]), dfs(R[u]);
}

int main() {
    scanf("%d", &n);
    for (int i = 1, x; i <= n; ++i) scanf("%d", &x), p[x] = i;

    build(), dfs(stk[1]);
    return 0;
}

P8773 [蓝桥杯 2022 省 A] 选数异或:st 表(绿)

对于每一个 ai,我们求出一个最大的 lasti=j,使得 ajai=xji。我们用 st 表维护区间内最大的 lasti,显然,若 lmaxlirlastir,则区间内存在两个数异或结果为 x;否则不存在。

时间复杂度 O(nlogn)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 1e5+10, M = 1 << 21;

int n, m, x, a[N], L[N], last[M], st[N][20];

int query(int l, int r) {
    int k = log2(r-l+1);
    return max(st[l][k], st[r-(1<<k)+1][k]);
}

int main() {
    scanf("%d%d%d", &n, &m, &x);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);

    for (int i = 1; i <= n; ++i) last[a[i]] = i, st[i][0] = L[i] = last[x^a[i]];
    for (int j = 1; (1<<j) <= n; ++j) {
        for (int i = 1; i+(1<<j)-1 <= n; ++i)
            st[i][j] = max(st[i][j-1], st[i+(1<<j-1)][j-1]);
    }

    int l, r;
    while (m -- ) {
        scanf("%d%d", &l, &r);
        int pos = query(l, r);
        if (l <= pos && pos <= r) puts("yes");
        else puts("no");
    }
    return 0;
}

CF618F. Double Knapsack:构造(紫)

可以猜测,一定有解且为两个连续子段。

sumai 表示 a 的前缀和,sumbi 表示 b 的前缀和。不妨设 sumansumbn

对于每一个 i,我们求出一个最大的 ci=j 使得 sumaisumbj。那么有 sumai<sumbci+1,即 sumai<sumbci+bci+1。移项得 sumaisumbci<bci+1。由于 1bci+1n,所以 0sumaisumbci<n,有 n 种可能。而 0in,有 n+1 种可能。那么至少有两个 sumaisumbci 是相等的。

假设 sumai1sumbci1=sumai2sumbci2,且 i1<i2,那么 sumai2sumai1=sumbci2sumbci1。即 [i1+1,i2][ci1+1,ci2] 就是符合条件的两个子段。

由于 ci 都是单调不降的,用指针计算即可。

时间复杂度 O(n)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

typedef long long ll;

const int N = 1e6+10;

bool check = 1;
int n, a[N], b[N], c[N], num[N]; ll sa[N], sb[N];

int main() {
    scanf("%d", &n); memset(num, -1, sizeof num);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), sa[i] = (ll)sa[i-1]+a[i];
    for (int i = 1; i <= n; ++i) scanf("%d", &b[i]), sb[i] = (ll)sb[i-1]+b[i];
    if (sa[n] > sb[n]) for (int i = 1; i <= n; ++i) swap(sa[i], sb[i]), check = 0;

    int now = 0;
    for (int i = 0; i <= n; ++i) {
        while (now < n && sa[i] >= sb[now+1]) now ++;
        c[i] = now;
        if (num[sa[i]-sb[c[i]]] == -1) num[sa[i]-sb[c[i]]] = i;
        else {
            int last = num[sa[i]-sb[c[i]]];
            if (check) {
                printf("%d\n", i-last);
                for (int j = last+1; j <= i; ++j) printf("%d ", j); puts("");
                printf("%d\n", c[i]-c[last]);
                for (int j = c[last]+1; j <= c[i]; ++j) printf("%d ", j); puts("");
            } else {
                printf("%d\n", c[i]-c[last]);
                for (int j = c[last]+1; j <= c[i]; ++j) printf("%d ", j); puts("");
                printf("%d\n", i-last);
                for (int j = last+1; j <= i; ++j) printf("%d ", j); puts("");
            
            }
            return 0;
        }
    }
}

P4398 [JSOI2008] Blue Mary的战役地图:哈希(蓝)

哈希乱搞一下就过了。时间复杂度 O(n4)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>

using namespace std;

typedef unsigned long long ull;

const int N = 55;
const ull P1 = 19260817, P2 = 19491001; // 行和列选取不同的进制

int n, a[2][N][N]; ull h[2][N][N], power[N];

ull get_hash(int x, int y, int k, int op) {
    ull res = 0;
    for (int i = x; i <= x+k-1; ++i) res = res*P2 + h[op][i][y+k-1]-h[op][i][y-1]*power[k]; 
}

int main() {
    scanf("%d", &n);
    for (int k = 0; k <= 1; ++k) {
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j)
                scanf("%d", &a[k][i][j]);
        }
    }

    power[0] = 1;
    for (int i = 1; i <= n; ++i) power[i] = power[i-1]*P1;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j)
            h[0][i][j] = h[0][i][j-1]*P1+a[0][i][j], h[1][i][j] = h[1][i][j-1]*P2+a[1][i][j];
    }

    for (int k = n; k >= 1; --k) {
        unordered_map<ull, int> nums;
        for (int i = 1; i <= n-k+1; ++i) {
            for (int j = 1; j <= n-k+1; ++j)
                nums[get_hash(i, j, k, 0)] = 1;
        }
        for (int i = 1; i <= n-k+1; ++i) {
            for (int j = 1; j <= n-k+1; ++j)
                if (nums[get_hash(i, j, k, 1)])
                    return printf("%d\n", k), 0;
        }
    } 
    return puts("0"), 0;
}

CF1092F. Tree with Maximum Cost:换根 dp(绿)

板子题。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;

const int N = 2e5+10;

int n, a[N], dist[N]; ll sum, ans, siz[N], f[N]; 
vector<int> e[N];

void dfs(int u, int fa) {
    siz[u] = a[u], f[1] += (ll)dist[u]*a[u];
    for (auto v : e[u]) {
        if (v == fa) continue;
        dist[v] = dist[u]+1, dfs(v, u), siz[u] += siz[v];
    }
}

void dp(int u, int fa) {
    ans = max(ans, f[u]);
    for (auto v : e[u]) {
        if (v == fa) continue;
        f[v] = f[u] - siz[v] + (sum-siz[v]), dp(v, u);
    }
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), sum += a[i];
    for (int i = 1; i < n; ++i) {
        int u, v; scanf("%d%d", &u, &v);
        e[u].push_back(v), e[v].push_back(u);
    }

    dfs(1, 1), dp(1, 1);
    printf("%lld\n", ans);
    return 0;
}

9.4

ABC259D. Circumferences:并查集(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 3010;

int n, sx, sy, tx, ty, p[N];

struct Circ {
	int x, y, r;
} c[N];

int find(int x) {return p[x] = (p[x] == x) ? p[x] : find(p[x]);}

ll get_dist(int x1, int y1, int x2, int y2) {
	return (ll)(x1-x2)*(x1-x2)+(ll)(y1-y2)*(y1-y2);
}

int main() {
	scanf("%d%d%d%d%d", &n, &sx, &sy, &tx, &ty);
	p[0] = 0, p[n+1] = n+1;
	for (int i = 1; i <= n; ++i) scanf("%d%d%d", &c[i].x, &c[i].y, &c[i].r), p[i] = i;
	
	for (int i = 1; i <= n; ++i) {
		if (get_dist(sx, sy, c[i].x, c[i].y) == (ll)c[i].r*c[i].r) p[0] = i;
		if (get_dist(tx, ty, c[i].x, c[i].y) == (ll)c[i].r*c[i].r) p[n+1] = i;
	}
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) {
			if (find(i) == find(j)) continue;
			if (get_dist(c[i].x, c[i].y, c[j].x, c[j].y) <= (ll)(c[i].r+c[j].r)*(c[i].r+c[j].r) && 
				get_dist(c[i].x, c[i].y, c[j].x, c[j].y) >= (ll)abs(c[i].r-c[j].r)*abs(c[i].r-c[j].r))
				p[find(i)] = find(j);
		}
	}
	
	(find(0) == find(n+1)) ? puts("Yes") : puts("No");
	return 0;
}

ABC259E. LCM on Whiteboard:数学(绿)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <unordered_map>

using namespace std;

typedef pair<int, int> pii;

const int N = 2e5+10;

int n, ans, idx; bool Check; vector<pii> e[N];
unordered_map<int, int> nums; pii maxl[N];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		int m; scanf("%d", &m), e[i].push_back({m, 0});
		for (int j = 1; j <= m; ++j) {
			int p, r; scanf("%d%d", &p, &r);
			if (!nums[p]) nums[p] = ++ idx; p = nums[p];
			e[i].push_back({p, r}); 
			if (maxl[p].first < r) maxl[p] = {r, 1};
			else if (maxl[p].first == r) maxl[p].second ++;
		}
	}
	
	for (int i = 1; i <= n; ++i) {
		int m = e[i][0].first;
		bool check = 0;
		for (int j = 1; j <= m; ++j) {
			int p = e[i][j].first, r = e[i][j].second;
			if (maxl[p].first == r && maxl[p].second == 1) {check = 1; break;}
		}
		if (check) ans ++;
		else if (!Check) ans ++, Check = 1; 
	}
	printf("%d\n", ans);
	return 0;
}

ABC259F. Select Edges:树形 dp(蓝)

fu,0/1 表示考虑以 u 为根的子树,且是否向上连边的最大价值。转移时直接全部丢进 vector 里排序就好了。

时间复杂度 O(nlogn)。实现注意细节。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 3e5+10;

int n, d[N]; ll f[N][2];
vector<pii> e[N];

void dfs(int u, int fa) {
    for (auto edge : e[u]) {
        int v = edge.first, w = edge.second;
        if (v == fa) {
            if (d[u]) f[u][1] += w;
            continue ;
        }
        dfs(v, u);
    }

    vector<ll> nums;
    for (auto edge : e[u]) {
        int v = edge.first, w = edge.second;
        if (v == fa) continue;
        f[u][0] += f[v][0], f[u][1] += f[v][0];
        nums.push_back((d[v]?f[v][1]:0)-f[v][0]);
    }
    
    sort(nums.begin(), nums.end(), greater<ll>());
    int tot = 0;
    for (auto num : nums) {
        if (num <= 0) break;
        tot ++;
        if (d[u]-tot >= 0) f[u][0] += num;
        if (d[u]-tot >= 1) f[u][1] += num;
    }
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &d[i]);
	for (int i = 1; i < n; ++i) {
		int u, v, w; scanf("%d%d%d", &u, &v, &w);
		e[u].push_back({v, w}), e[v].push_back({u, w});
	}
	
	dfs(1, 0);
	printf("%lld\n", max(f[1][0], f[1][1]));
	return 0;
}

9.5

ABC258D. Trophy:枚举(橙)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <climits>

using namespace std;

#define int long long
typedef pair<int, int> pii;

const int N = 2e5+10;

int n, x, ans = LLONG_MAX, a[N], b[N];

signed main() {
	scanf("%lld%lld", &n, &x);
	for (int i = 1; i <= n; ++i) scanf("%lld%lld", &a[i], &b[i]);
	
	int tot = 0, minb = 2e9;
	for (int i = 1; i <= min(n, x); ++i) {
		tot += a[i]+b[i], minb = min(minb, b[i]);
		ans = min(ans, tot+minb*(x-i));
	}
	printf("%lld\n", ans);
	return 0;
}

ABC258E. Packing Potatoes:枚举,二分,前缀和(绿)

注意到我们确定一个段的起点时,终点也随之确定,所以最多有 n 个不同的起点,那么就可以暴力枚举循环节了。

时间复杂度 O(nlogn)。实现挺恶心。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>

using namespace std;

#define int long long

const int N = 2e5+10;

int n, q, x, w[N], c[N], s[N];
unordered_map<int, int> circ;

int find(int pos, int tot) {
	int l = pos, r = n;
	while (l < r) {
		int mid = l + r >> 1;
		if (s[mid]-s[pos-1] >= tot) r = mid;
		else l = mid+1;
	}
	return l;
}

int calc(int &pos) {
	int now = pos, sum = s[n]-s[now-1], num = 0;
	if (sum >= x) return pos = find(now, x), pos-now+1;
	num += (x-sum) / s[n];
	sum = x - sum - num * s[n];
	return pos = find(1, sum), num*n+pos+n-now+1;
}

signed main() {
	scanf("%lld%lld%lld", &n, &q, &x);
	for (int i = 1; i <= n; ++i) scanf("%lld", &w[i]), s[i] = s[i-1]+w[i];
	
	int pos = 1, idx = 1;
	c[1] = calc(pos), circ[1] = 1;
	while (true) {
		pos = pos % n + 1;
		if (circ[pos]) break;
		idx ++, circ[pos] = idx;
		c[idx] = calc(pos);
	}
	
	while (q -- ) {
		int k; scanf("%lld", &k);
		if (k <= idx) printf("%lld\n", c[k]);
		else k -= circ[pos]-1, printf("%lld\n", (k%(idx-circ[pos]+1))?c[k%(idx-circ[pos]+1)+circ[pos]-1]:c[idx]);
	}
	return 0;
}

ABC258G. Triangle:枚举(绿)

枚举 i,j,bitset 优化暴力。时间复杂度 O(n3w)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <bitset>

using namespace std;

typedef long long ll;

const int N = 3010;

int n; ll ans;
bitset<N> a[N];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) {
			int x; scanf("%1d", &x);
			if (j <= i) a[i][j] = 0;
			else a[i][j] = x;
		}
	}

	for (int i = 1; i <= n; ++i) {
		for (int j = i+1; j <= n; ++j) 
			if (a[i][j]) ans += (a[i]&a[j]).count();
	}
	printf("%lld\n", ans);
	return 0;
}

9.6

ABC257D. Jumping Takahashi 2:二分,bfs(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;

#define int long long

const int N = 210;

struct Dot {
	int x, y, p;
} dot[N];

int n; bool st[N];

bool bfs(int u, int x) {
	int cnt = 1; queue<int> q; memset(st, 0, sizeof st);
	q.push(u), st[u] = 1;
	while (q.size()) {
		int u = q.front(); q.pop();
		for (int i = 1; i <= n; ++i) {
			if (st[i]) continue;
			if (dot[u].p * x >= abs(dot[u].x-dot[i].x)+abs(dot[u].y-dot[i].y)) q.push(i), st[i] = 1, cnt ++;
		}
	}
	return (cnt == n);
}

bool check(int x) {
	for (int i = 1; i <= n; ++i) if (bfs(i, x)) return 1;
	return 0;
}

signed main() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; ++i) scanf("%lld%lld%lld", &dot[i].x, &dot[i].y, &dot[i].p);
	
	int l = 1, r = 8e9;
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid+1;
	}
	printf("%lld\n", l);
	return 0;
}

ABC257E. Addition and Multiplication 2:贪心(绿)

可以想到先选价值最小的让位数最大,再从最高位开始调整。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int n, num[15];

int main() {
	scanf("%d", &n);
	int minl = 1e9;
	for (int i = 1; i <= 9; ++i) scanf("%d", &num[i]), minl = min(minl, num[i]);
	
	int len = n/minl; n -= minl*len;
	for (int i = 1; i <= len; ++i) {
		int j;
		for (j = 9; num[j] != minl; --j) if (num[j]-minl <= n) break;
		n -= num[j]-minl, printf("%d", j);
	}
	return 0;
}

ABC257F. Teleporter Setting:最短路(绿)

直接按照题意建边,将 0 号点视作超级源点。分别从 1,n 号点出发跑一次最短路,则 ans=min(dist(1,n),dist(1,0)+dist(n,i),dist(1,i)+dist(n,0))

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>

using namespace std;

typedef pair<int, int> pii;

const int N = 3e5+10, inf = 2e8;

int n, m, dist[2][N]; bool st[N];
vector<int> dots, e[N];

void dijkstra(int op, int s) {
	for (int i = 0; i <= n; ++i) dist[op][i] = inf, st[i] = 0;
	priority_queue<pii, vector<pii>, greater<pii>> q;
	q.push({0, s}), dist[op][s] = 0;
	while (q.size()) {
		auto t = q.top(); q.pop();
		int u = t.second, dis = t.first;
		if (st[u]) continue;
		st[u] = 1; 
		for (auto v : e[u]) if (dist[op][v] >= dis+1) dist[op][v] = dis+1, q.push({dist[op][v], v});
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		e[u].push_back(v), e[v].push_back(u);
	}
	
	dijkstra(0, 1), dijkstra(1, n);
	
	for (int i = 1; i <= n; ++i) {
		int ans = min(dist[0][n], min(dist[0][i]+dist[1][0], dist[1][i]+dist[0][0]));
		printf("%d ", (ans>=inf)?-1:ans);
	}
	return 0;
}

ABC257G. Prefix Concatenation:KMP,贪心(蓝)

fi 表示 Ti 对应 S 中最长前缀匹配长度,若存在 fi=0 则无解,显然这个可以用 KMP 计算。然后我们令 i=|T|,每次贪心地取当前能匹配的最长前缀。(正确性?)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 5e5+10;

int n, m, ans, ne[N], f[N]; 
char S[N], T[N];

void get_next() {
	ne[1] = 0;
	for (int i = 2, j = 0; i <= n; ++i) {
		while (S[j+1] != S[i] && j) j = ne[j];
		if (S[j+1] == S[i]) ne[i] = ++ j;
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> S+1 >> T+1;
	n = strlen(S+1), m = strlen(T+1);
	
	get_next();
	
	for (int i = 1, j = 0; i <= m; ++i) {
		while (S[j+1] != T[i] && j) j = ne[j];
		if (S[j+1] == T[i]) ++ j;
		f[i] = j; if (!f[i]) return puts("-1"), 0;
		if (j == n) j = ne[j]; 
	}
	
	for (int i = m; i >= 1; i -= f[i]) ans ++;
	printf("%d\n", ans);
	return 0;
}

9.7

ABC256D. Union of Interval:排序(橙)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef pair<int, int> pii;

int n; vector<pii> range;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		int l, r; scanf("%d%d", &l, &r);
		range.push_back({l, r});
	}
	
	sort(range.begin(), range.end());
	int L = range[0].first, R = range[0].second;
	for (int i = 1; i < n; ++i) {
		if (range[i].first <= R) R = max(R, range[i].second);
		else printf("%d %d\n", L, R), L = range[i].first, R = range[i].second;
	}
	printf("%d %d\n", L, R);
	return 0;
}

ABC256E. Takahashi's Anguish:dfs(黄)

考虑将这个问题转化为一个图论问题,我们从 ixi 连一条边,若一个连通块中不存在环,我们可以从入度为 0 的点出发遍历得到一个可行解;否则,该连通块中至少会有一个条件不能被满足。那么答案就是图中所有环的最小边权之和。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>

using namespace std;

typedef pair<int, int> pii;
typedef long long ll;

const int N = 2e5+10;

int n, x[N], c[N]; ll ans; bool st[N], inc[N], check[N];
vector<pii> e[N];

void dfs(int u) {
	st[u] = 1, check[u] = 1;
	for (auto edge : e[u]) {
		int v = edge.first;
		if (st[v]) {inc[v] = 1; continue;}
		if (check[v]) continue;
		dfs(v);
	}
	st[u] = 0;
}

void calc(int u, int &minl) {
	st[u] = 1;
	for (auto edge : e[u]) {
		int v = edge.first, w = edge.second;
		minl = min(minl, w);
		if (st[v]) continue;
		calc(v, minl);
	}
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &x[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &c[i]);
	
	for (int i = 1; i <= n; ++i) e[i].push_back({x[i], c[i]});
	for (int i = 1; i <= n; ++i) if (!check[i]) dfs(i);

	for (int i = 1; i <= n; ++i) {
		if (!inc[i] || st[i]) continue;
		int now = 1e9; calc(i, now);
		ans += now;
	}
	printf("%lld\n", ans);
	return 0;
}

ABC256F. Cumulative Cumulative Cumulative Sum:数学,树状数组,线段树(蓝)

纯粹的推式子。

bi=j=1iaici=j=1ibi=j=1ik=1jak=j=1i(ij+1)aj=(i+1)j=1iajj=1ijajdi=j=1ici=j=1i[(j+1)k=1jakk=1jkak]=j=1i(i+j2)(ij+1)2ajj=1i(ij+1)jaj=12j=1i(i22ij+j2+3i3j+2)aj=12[(i2+3i+2)j=1iaj(2i+3)j=1ijaj+j=1ij2aj]

树状数组维护这三个东西即可。

注意 12499122177(mod998244353)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

#define int long long

const int N = 2e5+10, P = 998244353, inv = 499122177;

int n, m, a[N], c[5][N];

int lowbit(int x) {
	return x & -x;
}

void add(int op, int x, int k) {
	for (int i = x; i <= n; i += lowbit(i)) c[op][i] = (c[op][i] + k) % P;
}

int query(int op, int x) {
	int sum = 0;
	for (int i = x; i >= 1; i -= lowbit(i)) sum = (sum + c[op][i]) % P;
	return sum;
}

void modify(int x, int v) {
	int t = ((-a[x]+v) % P + P) % P; 
	add(1, x, t%P), add(2, x, t*x%P), add(3, x, t*x%P*x%P), a[x] = v;
}

int get_sum(int x) {
	int sum = (((__int128)(x*x+3*x+2)*query(1, x)%P - (2*x+3)*query(2, x)%P + query(3, x)) * inv % P + P) % P;
	return sum;
}

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]), add(1, i, a[i]%P), add(2, i, a[i]*i%P), add(3, i, a[i]*i%P*i%P);
	
	int op, x, v;
	while (m -- ) {
		scanf("%lld%lld", &op, &x);
		if (op == 1) scanf("%lld", &v), modify(x, v);
		else printf("%lld\n", get_sum(x));
	}
	return 0;
}

ABC256Ex. I like Query Problem:珂朵莉树,分块,势能线段树(紫)

看到区间推平立马想到珂朵莉树,但是区间整除和区间求和会爆炸,考虑用分块优化珂朵莉树,即对每一个块建一个珂朵莉树并记录快内元素和,在修改时也统计块内元素和。

时间复杂度 O((n+q)nlogwB)。挺好写的。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <set>

using namespace std;

typedef long long ll;

const int N = 5e5+10;

int n, m, p, a[N], block[N], L[N], R[N];

struct Node {
	int l, r;
	mutable int v;
	
	Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {};
	
	bool operator < (const Node &T) const {
		return l < T.l;
	}
};

typedef set<Node>::iterator iter;

struct Block {
	int l, r; ll sum; set<Node> s;
	iter split(int pos) {
		iter it = s.lower_bound(Node(pos));
		if (it != s.end() && it->l == pos) return it;
		
		it --;
		if (it->r < pos) return s.end();
		int l = it->l, r = it->r, v = it->v;
		s.erase(it), s.insert(Node(l, pos-1, v));
		return s.insert(Node(pos, r, v)).first;
	}
		
	void assign(int l, int r, int x) {
		iter itr = split(r+1), itl = split(l);
		for (iter it = itl; it != itr; ++it) sum -= (it->r-it->l+1) * it->v;
		s.erase(itl, itr), s.insert(Node(l, r, x));
		sum += (r-l+1) * x;
	}
	
	void div(int l, int r, int x) {
		if (!sum) return ;
		iter itr = split(r+1), itl = split(l);
		for (iter it = itl; it != itr; ++it) sum -= (it->r-it->l+1)*(it->v-it->v/x), it->v /= x;
	}
	
	ll query(int l, int r) {
		ll tot = 0; iter itr = split(r+1), itl = split(l);
		for (iter it = itl; it != itr; ++it) tot += (it->r-it->l+1) * it->v;
		return tot;
	}
} B[N];

void div(int l, int r, int x) {
	int p = block[l], q = block[r];
	if (p == q) {
		B[p].div(l, r, x);
	} else {
		B[p].div(l, R[l], x), B[q].div(L[r], r, x);
		for (int i = p+1; i <= q-1; ++i) B[i].div(B[i].l, B[i].r, x);
	}
}

void assign(int l, int r, int x) {
	int p = block[l], q = block[r];
	if (p == q) {
		B[p].assign(l, r, x);
	} else {
		B[p].assign(l, R[l], x), B[q].assign(L[r], r, x);
		for (int i = p+1; i <= q-1; ++i) B[i].assign(B[i].l, B[i].r, x);
	}
}

ll query(int l, int r) {
	int p = block[l], q = block[r]; ll sum = 0;
	if (p == q) {
		return B[p].query(l, r);
	} else {
		sum += B[p].query(l, R[l]) + B[q].query(L[r], r);
		for (int i = p+1; i <= q-1; ++i) sum += B[i].sum;
		return sum;
	}
}

int main() {
	scanf("%d%d", &n, &m); p = sqrt(n);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]);
		block[i] = (i+p-1) / p, L[i] = (block[i]-1)*p+1, R[i] = min(block[i]*p, n);
		B[block[i]].l = L[i], B[block[i]].r = R[i];
		B[block[i]].s.insert(Node(i, i, a[i])), B[block[i]].sum += a[i];
	}
	
	int op, l, r, x;
	while (m -- ) {
		scanf("%d%d%d", &op, &l, &r);
		if (op == 1) scanf("%d", &x), div(l, r, x);
		else if (op == 2) scanf("%d", &x), assign(l, r, x);
		else printf("%lld\n", query(l, r));
	}
	return 0;
}

9.8

补一些之前没做的题。

ABC259Ex. Yet Another Path Counting:根号分治(紫)

显然有两种做法:

  • 枚举起始点 (x,y) 和结束点 (z,w),那么有 (zx+wyzx) 种路径。时间复杂度 O(k2),其中 k 为这种颜色的格子数量。
  • 枚举起始点 (x,y),动态规划计算这个点到这个点右下方所有点的方案数。时间复杂度 O(n2)

根号分治,当 kn 时,第一种做法时间复杂度不超过 O(n3);当 k>n 时,最多只有 nk 个这样的颜色,第二种做法时间复杂度也不超过 O(n3)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

const int N = 410, P = 998244353;

int n, ans, a[N][N], f[N][N], fac[N<<1], infac[N<<1];
vector<pii> col[N*N];

int power(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = (ll)res * a % P;
        a = (ll)a * a % P;
        b >>= 1;
    }
    return res;
}

int C(int a, int b) {
    return (ll)fac[a] * infac[b] % P * infac[a-b] % P;
}

void solve1(int c) {
    for (int i = 0; i < col[c].size(); ++i) {
        for (int j = i; j < col[c].size(); ++j) {
            int x1 = col[c][i].first, y1 = col[c][i].second, x2 = col[c][j].first, y2 = col[c][j].second;
            (ans += C(x2-x1+y2-y1, x2-x1)) %= P;
        }
    }
}

void solve2(int c) {
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            f[i][j] = ((a[i][j] == c) + f[i-1][j] + f[i][j-1]) % P;
            if (a[i][j] == c) (ans += f[i][j]) %= P;
        }
    }
}

int main() {
    fac[0] = 1, infac[0] = 1;
    for (int i = 1; i <= 800; ++i) fac[i] = (ll)fac[i-1]*i%P, infac[i] = power(fac[i], P-2);

    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j)
            scanf("%d", &a[i][j]), col[a[i][j]].push_back({i, j});
    }

    for (int i = 1; i <= n*n; ++i) {
        int k = col[i].size(); if (!k) continue;
        if (k <= n) solve1(i); else solve2(i);
    }
    printf("%d\n", ans);
    return 0;
}

ABC256G. Black and White Stones:计数 dp(蓝)

fi,j,0/1,0/1 表示处理了前 i 条边,每条边有 j 个白石子,第 1 条边左端点颜色为 0/1,第 i 条边右端点为 0/1 的数量。注意到转移过程中 j 不会改变,我们可以通过先枚举 j 来消掉一维,即状态表示为 fi,0/1,0/1。边界即为 f0,0,0=f0,1,1=1,答案即为 fn,0,0+fn,1,1

当我们从 i1 转移到 i 时,我们可以获取的第 i 条边的左端点的颜色(即第 i1 条边的右端点的颜色),考虑状态转移:

fi,0,0=(d1j)fi1,0,0+(d1j1)fi1,0,1fi,0,1=(d1j1)fi1,0,0+(d1j2)fi1,0,1fi,1,0=(d1j)fi1,1,0+(d1j1)fi1,1,0fi,1,1=(d1j1)fi1,1,0+(d1j2)fi1,1,1

直接 dp 的时间复杂度是 O(n) 的,观察到 fi,0/1,0/1 全部由 fi1,0/1,0/1 乘上相应的系数转移过来,考虑矩阵快速幂优化,那么我们有:

[fi,0,0fi,0,1fi,1,0fi,1,1]=[fi1,0,0fi1,0,1fi1,1,0fi1,1,1]×[(d1j)(d1j1)(d1j1)(d1j2)]

时间复杂度 O(dlogp+logn)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

#define int long long

const int N = 1e4+10, P = 998244353;

int n, d, ans, fac[N], infac[N];

struct Matrix {
    int a[3][3];

    Matrix() {
        memset(a, 0, sizeof a);
    }
};

Matrix operator*(const Matrix &x, const Matrix &y) {
    Matrix z;
    for (int k = 1; k <= 2; ++k) {
        for (int i = 1; i <= 2; ++i) {
            for (int j = 1; j <= 2; ++j)
                (z.a[i][j] += x.a[i][k]*y.a[k][j]) %= P;
        }
    }
    return z;
}

void set_base(Matrix &a) {
    for (int i = 1; i <= 2; ++i) a.a[i][i] = 1;
}

int C(int a, int b) {
    if (b < 0) return 0;
    return fac[a] * infac[b] % P * infac[a-b] % P;
}

int power(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) (res *= a) %= P;
        (a *= a) %= P;
        b >>= 1;
    }
    return res;
}

Matrix power(Matrix a, int b) {
    Matrix res; set_base(res);
    while (b) {
        if (b & 1) res = res * a;
        a = a * a;
        b >>= 1;
    }
    return res;
}

signed main() {
    fac[0] = 1, infac[0] = 1;
    for (int i = 1; i <= 10000; ++i) fac[i] = fac[i-1]*i%P, infac[i] = power(fac[i], P-2);

    scanf("%lld%lld", &n, &d);
    for (int j = 0; j <= d+1; ++j) {
        Matrix base, trans; set_base(base);
        trans.a[1][1] = C(d-1, j), trans.a[1][2] = C(d-1, j-1), trans.a[2][1] = C(d-1, j-1), trans.a[2][2] = C(d-1, j-2);
        trans = power(trans, n), base = base * trans;
        (ans += base.a[1][1]+base.a[2][2]) %= P;
    }
    printf("%lld\n", ans);
    return 0;
}

9.9

ABC255D. ±1 Operation 2:排序,二分(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

#define int long long 

const int N = 2e5+10;

int n, m, a[N], s[N];

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
	
	sort(a+1, a+n+1);
	for (int i = 1; i <= n; ++i) s[i] = s[i-1] + a[i];
	
	while (m -- ) {
		int x; scanf("%lld", &x);
		int pos = upper_bound(a+1, a+n+1, x) - a - 1;
		printf("%lld\n", x*pos-s[pos]+s[n]-s[pos]-(n-pos)*x);
	}
	return 0;
}

ABC255E. Lucky Numbers:枚举,桶(绿)

显然,当 a1 确定时,整个 a 数组也随之确定,满足 ai+1=j=1i(1)ijSj+(1)ia1。可以枚举 aix1,x2,,xma1 的值并用一个桶记录,那么取使得答案最大的 a1 即可。

时间复杂度 O(nm)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>

using namespace std;

#define int long long

const int N = 1e5+10;

int n, m, s[N], x[15];
int now, ans;
unordered_map<int, int> nums; 

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 2; i <= n; ++i) scanf("%lld", &s[i]);
	for (int i = 1; i <= m; ++i) scanf("%lld", &x[i]);
	
	for (int i = 1; i <= n; ++i) {
		now = s[i] - now;
		for (int j = 1; j <= m; ++j) {
			nums[(i%2)?x[j]-now:now-x[j]] ++;
			ans = max(ans, nums[(i%2)?x[j]-now:now-x[j]]);
		}
	}
	printf("%lld\n", ans);
	return 0;
}

ABC255F. Pre-order and In-order:dfs(绿)

具体看代码。时间复杂度 O(n)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 2e5+10;

int n, a[N], b[N], pos[N], L[N], R[N];

int dfs(int al, int ar, int bl, int br) {
	if (al > ar || ar < 0 || al < 0) return 0;
	int now = pos[a[al]];
	if (now < bl || now > br || ar-al != br-bl) puts("-1"), exit(0);
	if (now != bl) L[a[al]] = dfs(al+1, now-bl+al, bl, now-1);
	if (now != br) R[a[al]] = dfs(now-bl+al+1, ar, now+1, br);
	return a[al];
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &b[i]), pos[b[i]] = i;
	if (a[1] != 1) puts("-1"), exit(0);
	
	dfs(1, n, 1, n);
	
	for (int i = 1; i <= n; ++i) printf("%d %d\n", L[i], R[i]);
	return 0;
}

ABC255Ex. Range Harvest Query:珂朵莉树,权值线段树,离散化(紫)

珂朵莉树维护每个段最后一次收获的时间即可。对于一个段 [l,r],假设其上一次收获的时间为 d0,则这一段对答案的贡献为 (dd0)(l+r)(rl+1)2

时间复杂度通过颜色段均摊证明,为 O(qlogn)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>

using namespace std;

#define int long long

const int P = 998244353, inv = 499122177;

int n, q;

struct Node {
	int l, r; mutable int v;
	
	Node (int l, int r = 0, int v = 0) : l(l), r(r), v(v) {};
	
	bool operator < (const Node &T) const {
		return l < T.l;
	}
};

set<Node> s;

auto split(int pos) {
	auto it = s.lower_bound(Node(pos));
	if (it != s.end() && it->l == pos) return it;
	it --; if (it->r < pos) return s.end();
	int l = it->l, r = it->r, v = it->v;
	s.erase(it), s.insert(Node(l, pos-1, v));
	return s.insert(Node(pos, r, v)).first; 
}

void assign(int l, int r, int x) {
	auto itr = split(r+1), itl = split(l);
	s.erase(itl, itr), s.insert(Node(l, r, x));
}

int query(int l, int r, int d) {
	int sum = 0; auto itr = split(r+1), itl = split(l);
	for (auto it = itl; it != itr; ++it) (sum += (__int128)(d-it->v)%P * ((it->r+it->l)%P) % P * ((it->r-it->l+1)%P) % P * inv % P) %= P;
	return sum; 
}

signed main() {
	scanf("%lld%lld", &n, &q); s.insert(Node(1, n, 0));
	int d, l, r;
	while (q -- ) {
		scanf("%lld%lld%lld", &d, &l, &r);
		printf("%lld\n", query(l, r, d)), assign(l, r, d);
	}
	return 0;
}

ABC254D. Together Square:线性筛,数学(黄)

对于每一个数 i,我们通过分解质因数计算出一个最小的 j,满足 i×j=k2(kN+)。那么 j×12,j×22, 都是符合条件的数,这样的数共有 n/j 个。

时间复杂度 O(nlogn)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 2e5+10; 

int n, ans;
int idx, prime[N]; bool st[N];

void get_prime() {
	st[0] = st[1] = 1;
	for (int i = 2; i <= n; ++i) {
		if (!st[i]) prime[++ idx] = i;
		for (int j = 1; j <= idx && i*prime[j] <= n; ++j) {
			st[i*prime[j]] = 1;
			if (i % prime[j] == 0) break;
		}
	}
}

int main() {
	scanf("%d", &n); get_prime(); 
	for (int i = 1; i <= n; ++i) {
		int now = 1;
		if (!st[i] || i == 1) now = i;
		else {
			int t = i;
			for (int j = 1; j <= idx && t > 1; ++j) {
				int cnt = 0;
				while (t % prime[j] == 0) t /= prime[j], cnt ++;
				if (cnt & 1) now *= prime[j];
			} 
		}
		ans += sqrt(n/now); 
	}
	printf("%d\n", ans);
	return 0;
}

ABC254E. Small d and k:dfs/bfs(黄)

由于保证了每个点的度数 3,所以可以直接暴搜。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>

using namespace std;

const int N = 2e5+10;

int n, m, q; bool st[N];
vector<int> e[N];

int dfs(int u, int k) {
	if (!k) {
		if (st[u]) return 0;
		return st[u] = 1, u;
	}
	int tot = (st[u]) ? 0 : u; st[u] = 1;
	for (auto v : e[u]) tot += dfs(v, k-1);
	return tot;
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		e[u].push_back(v), e[v].push_back(u);
	}
	
	scanf("%d", &q);
	while (q -- ) {
		memset(st, 0, sizeof st);
		int x, k; scanf("%d%d", &x, &k);
		printf("%d\n", dfs(x, k));
	}
	return 0;
}

ABC254F. Rectangle GCD:数学,st 表(绿)

先考虑同一行的情况。我们有:

gcd(ci,j,ci,j+1,,ci,k)=gcd(ci,j,ci,j+1ci,j,,ci,kci,k1)=gcd(ci,j,bj+1bj,,bkbk1)

同样地,对于同一行的情况,我们也有类似的结论。那么整个子矩阵的 gcd 即为:

gcd(ah1+bw1,gcd(bw1+1bw1,,bw2bw21),gcd(ah1+1ah1,,ah2ah21))

我们只需要用 st 表维护差分数组的区间 gcd 即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 2e5+10;

int n, m, a[N][3], c[N][3], st[N][30][3];

int query(int l, int r, int op) {
    int k = log2(r-l+1);
    return __gcd(st[l][k][op], st[r-(1<<k)+1][k][op]);
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= 2; ++i) 
        for (int j = 1; j <= n; ++j) 
            scanf("%d", &a[j][i]), c[j][i] = abs(a[j][i]-a[j-1][i]), st[j][0][i] = c[j][i];

    for (int i = 1; i <= 2; ++i) {
        for (int k = 1; (1<<k) <= n; ++k) 
            for (int j = 1; j+(1<<k)-1 <= n; ++j)
                st[j][k][i] = __gcd(st[j][k-1][i], st[j+(1<<k-1)][k-1][i]);
    }

    while (m -- ) {
        int x1, y1, x2, y2; scanf("%d%d%d%d", &x1, &x2, &y1, &y2);
        int ans = a[x1][1]+a[y1][2];
        if (x1+1 <= x2) ans = __gcd(ans, query(x1+1, x2, 1));
        if (y1+1 <= y2) ans = __gcd(ans, query(y1+1, y2, 2));
        printf("%d\n", ans);
    }
    return 0;
}

9.10

补昨晚 ABC 319:

ABC319A. Legendary Players:枚举(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>
#include <unordered_map>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

unordered_map<string, int> S; 

int main() {
	S["tourist"] = 3858, S["ksun48"] = 3679, S["Benq"] = 3658, S["Um_nik"] = 3648, S["apiad"] = 3638, S["Stonefeang"] = 3630, S["ecnerwala"] = 3613, S["mnbvmar"] = 3555, S["newbiedmy"] = 3516, S["semiexp"] = 3481;
	string s; cin >> s; cout << S[s] << '\n';
	return 0;
}

ABC319B. Measure:枚举(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

int n;

int main() {
	cin >> n;
	for (int i = 0; i <= n; ++i) {
		int j;
		for (j = 1; j <= 9; ++j) if (n % j == 0 && (i % (n/j) == 0)) break;
		cout << (char)((j == 10) ? '-' : j+'0');
	}
	return 0;
}

ABC319C. False Hope:枚举(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>

using namespace std;

typedef long long ll;
typedef pair<int, int> pii;

int a[5][5], nums[10], tot, sum;

int main() {	
	for (int i = 1; i <= 3; ++i) {
		for (int j = 1; j <= 3; ++j)
			scanf("%d", &a[i][j]);
	}
	
	for (int i = 1; i <= 9; ++i) nums[i] = i;
	do {
		sum ++; bool check = 1;
		vector<pii> line[10];
		for (int i = 1; i <= 9; ++i) {
			int x = (nums[i]-1)/3+1, y = (nums[i]%3)?nums[i]%3:3;
			line[x].push_back({x, y}), line[y+3].push_back({x, y});
			if (x == y) line[7].push_back({x, y});
			if (x+y == 4) line[8].push_back({x, y});
		}
		for (int i = 1; i <= 8; ++i) {
			int x1 = line[i][0].first, y1 = line[i][0].second, x2 = line[i][1].first, y2 = line[i][1].second;
			if (a[x1][y1] == a[x2][y2]) {check = 0; break;}
		}
		if (check) tot ++;
	} while (next_permutation(nums+1, nums+10));
	printf("%.10f\n", (double)tot/sum);
	return 0;
}

ABC319D. Minimum Width:二分(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cmath>

using namespace std;

#define int long long

const int N = 2e5+10;

int n, m, maxl = 0, a[N];

bool check(int x) {
	int cnt = 1, now = 0;
	for (int i = 1; i <= n; ++i) {
		if (now+a[i] <= x) now += a[i]+1;
		else now = a[i]+1, cnt ++;
	}
	return cnt <= m;
}

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]), maxl = max(maxl, a[i]);

	int l = maxl, r = 1e17;
	while (l < r) {
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid+1;
	}
	printf("%lld\n", l);
	return 0;
}

ABC319E. Bus Stops:数学,枚举(绿)

题目中保证了 1Pi8,那么所有车站最多经过 M=lcm(1,2,3,,8)=840 单位时间就会有一次循环。预处理出前 840 个答案,则出发时间为 T 时,答案为 ansTmodM+MTM

时间复杂度 O(NM+Q)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

const int N = 1e5+10, M = 840;

#define int long long

int n, m, x, y, p[N], t[N], ans[M];

signed main() {
    scanf("%lld%lld%lld", &n, &x, &y);
    for (int i = 1; i < n; ++i) scanf("%lld%lld", &p[i], &t[i]);

    for (int i = 0; i < 840; ++i) {
        ans[i] += x+i;
        for (int j = 1; j < n; ++j) {
            if (ans[i] % p[j]) ans[i] += p[j] - ans[i]%p[j];
            ans[i] += t[j];
        }
        ans[i] += y;
    }

    scanf("%lld", &m);
    while (m -- ) {
        int q; scanf("%lld", &q);
        printf("%lld\n", ans[q%M]+q/M*M);
    }
    return 0;
}

9.11

ABC319G. Counting Shortest Paths:最短路,bfs,dp(蓝)

考虑对于每个点求出其与 1 号点之间的距离。这个过程可以用 setqueue 实现,其中 set 维护当前还没有走到的点。每次我们在 set 里找到可以扩展的点并将它们从 set 中删除并加入队列。

统计答案时,我们考虑 dp。令 fi 表示走到 i 号点的最短路径数量。显然,若 fu 能从 fv 转移而来,则一定有 distu=distv+1(u,v)GG 为删去的边构成的图)。也就是说,我们有:

fu=fv(u,v)Gfv

时间复杂度 O(nlogn+m)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <set>

using namespace std;

const int N = 2e5+10, P = 998244353;

int n, m, dist[N], f[N], s[N]; 
vector<int> e[N];
set<int> S, p[N];

void bfs() {
    queue<int> q; q.push(1); dist[1] = 1;
    while (q.size()) {
        int u = q.front(); q.pop(); set<int> SS = S;
        for (auto v : e[u]) {
            if (SS.find(v) != S.end())
                SS.erase(v);
        }
        for (auto v : SS) S.erase(v), dist[v] = dist[u]+1, q.push(v);
    }
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= m; ++i) {
        int u, v; scanf("%d%d", &u, &v);
        e[u].push_back(v), e[v].push_back(u);
    }

    for (int i = 2; i <= n; ++i) S.insert(i);
    bfs();
    
    if (!dist[n]) return puts("-1"), 0;

    f[1] = 1, s[1] = 1;
    for (int i = 2; i <= n; ++i) p[dist[i]].insert(i);
    for (int i = 2; i <= dist[n]; ++i) {
        for (auto u : p[i]) {
            f[u] = s[i-1];
            for (auto v : e[u]) {
                if (dist[v] == dist[u]-1)
                    (f[u] += P-f[v]) %= P;
            }
            (s[i] += f[u]) %= P;
        }
    }
    printf("%d\n", f[n]);
    return 0;
}

ABC254Ex. Multiply or Divide by 2:堆,贪心(紫)

我们记当前 A 中最大的元素为 aB 中最大的元素为 b。考虑两种情况:

  • a>b:那么我们需要将 a 除以 2
  • a<b:此时,若 b 为奇数,则不可能完成。否则,我们需要将 a 乘上 2,也就相当于将对应的 b 除以 2
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 1e5+10;

int n, ans, a[N], b[N]; 
priority_queue<int> A, B;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
	
	for (int i = 1; i <= n; ++i) A.push(a[i]), B.push(b[i]);
	
	while (A.size() && B.size()) {
		int x = A.top(), y = B.top(); A.pop(), B.pop();
		if (x > y) {
			x >>= 1; A.push(x), B.push(y);
			ans ++;
		} else if (x < y) {
			if (y & 1) return puts("-1"), 0;
			y >>= 1; A.push(x), B.push(y);
			ans ++;
		}
	}
	printf("%d\n", ans);
	return 0;
}

ABC253D. FizzBuzz Sum Hard:数学(橙)

显然答案为 n(n+1)2a×(n/a+1)(n/a)2b×(n/b+1)(n/b)2+c×(n/c+1)(n/c)2。其中,c=lcm(a,b)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

#define int long long

int n, a, b, c, ans;

signed main() {
	cin >> n >> a >> b; c = a*b / __gcd(a, b);
	ans = n*(n+1)/2 - a*(n/a+1)*(n/a)/2 - b*(n/b+1)*(n/b)/2 + c*(n/c+1)*(n/c)/2;
	cout << ans << '\n';
	return 0;
}

ABC253E. Distance Sequence:dp(黄)

fi,j 表示考虑前 i 个数,ai=j 时的方案数量。初始状态为 f1,j=1。状态转移是显然的:

fi,j=1tjkfi1,t+j+ktmfi1,t

前缀和优化即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 1010, M = 5010, P = 998244353;

int n, m, k, ans, f[N][M], s[N][M];

int main() {
	scanf("%d%d%d", &n, &m, &k);
	
	for (int i = 1; i <= m; ++i) f[1][i] = 1, s[1][i] = s[1][i-1]+f[1][i];
	for (int i = 2; i <= n; ++i) {
		for (int j = 1; j <= m; ++j) {
			(f[i][j] = ((long long)s[i-1][m] - (k?(s[i-1][min(m, j+k-1)]-s[i-1][max(0, j-k)]):0)) % P + P) %= P;
			(s[i][j] = s[i][j-1]+f[i][j]) %= P;
		}
	}
	
	printf("%d\n", s[n][m]);
	return 0;
}

9.12

ABC253F. Operations on a Matrix:树状数组,可持久化线段树(绿)

对于操作编号为 r 的一次查询 (x,y),我们设上一次对第 x 行进行修改的操作编号为 l。那么答案显然为第 [l,r] 这个操作序列对第 x 行的影响,也就是 [1,r] 的影响减去 [1,l1] 的影响。 那么这个东西显然可以可持久化,但也可以离线用树状数组维护每一列,并在每一次操作后加上或减去对之后的序列产生的影响。

代码有点抽象。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

#define int long long

const int N = 2e5+10;

int n, m, t, last[N], c[N];
int idx, ans[N];

struct Node {
	int op, a, b, c;
} q[N];
vector<Node> mf[N];

int lowbit(int x) {
	return x & -x;
}

void add(int x, int k) {
	for (int i = x; i <= m; i += lowbit(i)) c[i] += k;
}

int query(int x) {
	int res = 0;
	for (int i = x; i >= 1; i -= lowbit(i)) res += c[i];
	return res;
}

signed main() {
	scanf("%lld%lld%lld", &n, &m, &t);
	
	int op, a, b, c;
	for (int i = 1; i <= t; ++i) {
		scanf("%lld%lld%lld", &op, &a, &b);
		if (op == 1) scanf("%lld", &c);
		else if (op == 2) last[a] = i; // last[a]存储第i行上一次修改的操作编号
		else ans[++ idx] = q[last[a]].b, mf[last[a]].push_back({-1, b, idx}), mf[i].push_back({1, b, idx}); // 表示对于第idx次查询操作, 需要减去第last[a]次操作之前增加的量, 并加上第i次操作之前增加的量
		q[i] = {op, a, b, c};
	}
	
	for (int i = 1; i <= t; ++i) {
		op = q[i].op, a = q[i].a, b = q[i].b, c = q[i].c;
		if (op == 1) add(a, c), add(b+1, -c);
		for (auto p : mf[i]) {
			int f = p.op, c = p.a, pos = p.b;
			ans[pos] += f * query(c);
		}
	}
	
	for (int i = 1; i <= idx; ++i) printf("%lld\n", ans[i]);
	return 0;
}

ABC253G. Swap Many Times:分块,枚举(蓝)

考虑如果我们将操作 (i,i+1),(i,i+2),,(i,n) 都进行一遍会发生什么。可以发现,这相当于我们将 aian1 集体向右移一位,同时将 an 放在第 i 个位置。那么我们可以一次性处理出所有这样的整块。对于左右两端的散块,直接暴力交换即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 2e5+10;

#define int long long

int n, l, r, a[N], b[N], siz[N];

void modify(int l, int r) {
	for (int i = l; i <= r; ++i) {
		int x = lower_bound(siz+1, siz+n+1, i)-siz, y = i-(n*(x-1)-x*(x-1)/2)+x;
		swap(a[x], a[y]);
	}
}

signed main() {
	scanf("%lld%lld%lld", &n, &l, &r);
	for (int i = 1; i <= n; ++i) a[i] = i;
	
	for (int i = 1; i <= n; ++i) siz[i] = siz[i-1] + n-i;
	int p = lower_bound(siz+1, siz+n+1, l)-siz, q = lower_bound(siz+1, siz+n+1, r)-siz;
	
	if (q-p < 2) modify(l, r);
	else {
		modify(l, siz[p]);
		int idx = 0;
		for (int i = 1; i <= p; ++i) b[++ idx] = a[i];
		for (int i = n; i >= n-q+p+2; --i) b[++ idx] = a[i];
		for (int i = p+1; i < n-q+p+2; ++i) b[++ idx] = a[i];
		for (int i = 1; i <= n; ++i) a[i] = b[i];
		modify(siz[q-1]+1, r);
	}
	
	for (int i = 1; i <= n; ++i) printf("%lld ", a[i]);
	return 0;
}

ABC252D. Distinct Trio:枚举,桶(黄)

我们可以转换一下题意:将原数组排序后,满足 Ai<Aj<Ak,i<j<k 的三元组 (i,j,k) 数量。这个问题就是能够简单解决的了。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

typedef long long ll;

const int N = 2e5+10;

int n, cnt, a[N], c[N]; ll ans;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), c[a[i]] ++;
	
	sort(a+1, a+n+1);
	for (int i = 1; i <= n; ++i) {
		if (a[i] != a[i-1]) cnt = 1; else cnt ++;
		ans += (ll)(i-cnt) * (n-(i-cnt+c[a[i]]));
	}
	printf("%lld\n", ans);
	return 0;
} 

ABC252E. Road Reduction:最短路树(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;

#define int long long
typedef pair<int, int> pii;

const int N = 2e5+10;

struct Edge {
	int v, w, id;
};

int n, m, p[N], dist[N]; bool st[N];
vector<Edge> e[N];

void dijkstra() {
	memset(dist, 0x7f, sizeof dist);
	priority_queue<pii, vector<pii>, greater<pii>> q;
	q.push({0, 1}), dist[1] = 0;
	
	while (q.size()) {
		auto t = q.top(); q.pop();
		int u = t.second, dis = t.first;
		if (st[u]) continue;
		st[u] = 1;
		
		for (auto edge : e[u]) {
			int v = edge.v, w = edge.w, id = edge.id;
			if (dist[v] > dis+w) dist[v] = dis+w, p[v] = id, q.push({dist[v], v});
		}
	}
}

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v, w; scanf("%lld%lld%lld", &u, &v, &w);
		e[u].push_back({v, w, i}), e[v].push_back({u, w, i});
	}
	
	dijkstra();
	
	for (int i = 2; i <= n; ++i) printf("%lld ", p[i]);
	return 0;
} 

ABC252F. Bread:堆,贪心。

合并果子,注意若 L>ai,则我们还需要在堆中加入 Lai

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

#define int long long

int n, l, tot, ans;
priority_queue<int, vector<int>, greater<int>> q;

signed main() {
	scanf("%lld%lld", &n, &l);
	for (int i = 1; i <= n; ++i) {
		int x; scanf("%lld", &x);
		q.push(x); tot += x;
	}
	if (l > tot) q.push(l-tot);
	
	while (q.size() > 1) {
		int a = q.top(); q.pop();
		int b = q.top(); q.pop();
		ans += a+b, q.push(a+b);
	}
	printf("%lld\n", ans);
	return 0;
}

9.16

ABC320A. Leyland Number:(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <iomanip>
#include <cmath>

using namespace std;

int a, b;

int main() {
	cin >> a >> b;
	cout << fixed << setprecision(0) << pow(a, b)+pow(b, a) << '\n';
	return 0;
}

ABC320B. Longest Palindrome:枚举,manacher(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 3e7+10;

int n, idx, ans, d[N];
char S[N], T[N];

void build() { 
	S[++ idx] = '!', S[++ idx] = '#';
	for (int i = 1; i <= n; ++i) S[++ idx] = T[i], S[++ idx] = '#';
	S[++ idx] = '^';
}

void get_d() { 
	for (int i = 2, l = 0, r = 0; i < idx; ++i) {
		int j = l + r - i;
		if (i <= r) d[i] = min(d[j], r-i+1);
		while (S[i+d[i]] == S[i-d[i]]) d[i] ++;
		if (i+d[i] > r) r = i+d[i]-1, l = i-d[i]+1;
		ans = max(ans, d[i]-1);
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> T+1;
	n = strlen(T+1);
	
	build(), get_d();
	
	cout << ans << '\n';
	return 0;
}

ABC320C. Slot Strategy 2 (Easy):模拟,枚举(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>

using namespace std;

const int N = 110, M = 3e5+10;

int n = 3, m, ne[N][M], cnt[15]; char s[N][M];
bool st[N][15];

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> m;
	for (int i = 1; i <= n; ++i) cin >> s[i]+1;
	for (int i = 1; i <= n; ++i) {
		for (int j = m+1; j <= 3*m; ++j)
			s[i][j] = s[i][(j-1)%m+1];
	}
	m *= 3;
	for (int i = 0; i <= 9; ++i) cnt[i] = n;
	
	for (int i = 1; i <= n; ++i) {
		int now[15]; memset(now, 0, sizeof now);
		for (int j = m; j >= 1; --j) {
			int x = s[i][j] - '0';
			if (!now[x]) ne[i][j] = m+1;
			else ne[i][j] = now[x];
			now[x] = j;
		}
	}
	
	for (int i = 1; i <= m; ++i) {
		vector<int> nums[15];
		for (int j = 1; j <= n; ++j) {
			int x = s[j][i] - '0';
			if (!st[j][x]) nums[x].push_back(j);
		}
		for (int j = 0; j <= 9; ++j) {
			int maxpos = 0, maxr = 0;
			for (auto k : nums[j]) if (ne[k][i] > maxpos) maxpos = ne[k][i], maxr = k;
			if (!maxr) continue;
			st[maxr][j] = 1, cnt[j] --;
			if (!cnt[j]) return cout << i-1 << '\n', 0;
		}
	}
	return cout << -1 << '\n', 0;
}

ABC320D. Relative Position:dfs(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

#define int long long
typedef pair<int, int> pii;
#define x first
#define y second

const int N = 2e5+10;

int n, m; pii pos[N];
bool st[N], circ[N];

struct Edge {
	int v, x, y;
};
vector<Edge> e[N];

void dfs(int u) {
	st[u] = 1;
	for (auto edge : e[u]) {
		int v = edge.v, x = edge.x, y = edge.y;
		if (st[v]) {
			int X = pos[v].x, Y = pos[v].y;
			if (X != pos[u].x+x || Y != pos[u].y+y) circ[v] = 1;
			continue;
		}
		pos[v].x = pos[u].x+x, pos[v].y = pos[u].y+y;
		dfs(v);
	}
}

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int a, b, x, y; scanf("%lld%lld%lld%lld", &a, &b, &x, &y);
		e[a].push_back({b, x, y}), e[b].push_back({a, -x, -y});
	}
	
	pos[1] = {0, 0}; dfs(1);
	for (int i = 1; i <= n; ++i) {
		if (!st[i] || circ[i]) puts("undecidable");
		else printf("%lld %lld\n", pos[i].x, pos[i].y);
	}
	return 0;
}

ABC320E. Somen Nagashi:模拟,堆(黄)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

const int N = 2e5+10;

#define int long long
typedef pair<int, int> pii;

int n, m, ans[N];
priority_queue<int, vector<int>, greater<int>> Q;
priority_queue<pii, vector<pii>, greater<pii>> q;

signed main() {
	scanf("%lld%lld", &n, &m);
	for (int i = 1; i <= n; ++i) Q.push(i);
	for (int i = 1; i <= m; ++i) {
		int t, w, s; scanf("%lld%lld%lld", &t, &w, &s);
		while (q.size() && q.top().first <= t) Q.push(q.top().second), q.pop();
		if (!Q.size()) continue; 
		ans[Q.top()] += w, q.push({t+s, Q.top()}), Q.pop();
	} 
	for (int i = 1; i <= n; ++i) printf("%lld\n", ans[i]);
	return 0;
}

9.20

ABC251D. At Most 3 (Contestant ver.):思维(橙)

由于 w106,注意到除了 106 是七位数,其他的数的位数都不超过 6。也就是说,它们都可以表示为 x=abcdef=ab×104+cd×102+ef 的形式。那么,我们可以将数组分为 3 部分:

  • 第一组:1,2,3,100
  • 第二组:100,200,300,,10000
  • 第三组:10000,20000,30000,,1000000

这样就可以用不超过 3 个数的和表示出 [1,106] 中的所有数了。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

int w;

int main() {
	scanf("%d", &w);
	puts("300");
	for (int i = 1; i <= 100; ++i) printf("%d %d %d ", i, i*100, i*10000);
	return 0;
}

ABC251E. Takahashi and Animals:环形 dp(黄)

断环成链,做两次 dp 即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

#define int long long

const int N = 3e5+10, inf = 8e18;

int n, ans = inf, a[N<<1], f[N][2];

signed main() {
	scanf("%lld", &n);
	for (int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
	for (int i = n+1; i <= 2*n; ++i) a[i] = a[i-n];
	
	memset(f, 0x7f, sizeof f), f[1][1] = a[1];
	for (int i = 2; i <= n; ++i) f[i][0] = f[i-1][1], f[i][1] = min(f[i-1][0], f[i-1][1])+a[i];
	ans = min(ans, min(f[n][0], f[n][1]));
	memset(f, 0x7f, sizeof f), f[n][1] = a[n];
	for (int i = n+1; i <= 2*n-1; ++i) f[i][0] = f[i-1][1], f[i][1] = min(f[i-1][0], f[i-1][1])+a[i];
	ans = min(ans, min(f[2*n-1][0], f[2*n-1][1]));
	printf("%lld\n", ans);
	return 0;
}

ABC251F. Two Spanning Trees:dfs,bfs(绿)

先给出结论:T1 是原图的 dfs 树,T2 是原图的 bfs 树。

证明:

对于 T1,若存在 (u,v)G(u,v)T1,并且 u 不是 v 的祖先(反之亦然)。那么由于在先遍历 u 时一定会走到 v,与 dfs 的性质矛盾。故 T1 的构造符合题目要求。

对于 T2,若存在 (u,v)G(u,v)T2,并且 uv 的祖先(反之亦然)。由于在图 GT2 中,1 号点到任何点的最短路都是相等的,不妨设 1 号点到 u 的距离为 d(u),到 v 的距离为 d(v)。由于 uv 的祖先,我们有 d(u)+1d(v);又因为 (u,v)G,所以 d(u)+1d(v),那么只能有 d(u)+1=d(v),也就是说 uv 的父亲,(u,v)T2,矛盾。故 T2 的构造也符合题目要求。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <queue>

using namespace std;

const int N = 2e5+10;

int n, m, dist[N]; bool st[N];
vector<int> e[N], d[N];

void dfs(int u) {
	st[u] = 1;
	for (auto v : e[u]) {
		if (st[v]) continue;
		printf("%d %d\n", u, v), dfs(v);
	}
}

void bfs() {
	queue<int> q; q.push(1), dist[1] = 1, d[1].push_back(1);
	while (q.size()) {
		int u = q.front(); q.pop();
		for(auto v : e[u]) {
			if (dist[v]) continue;
			dist[v] = dist[u]+1, q.push(v), d[dist[v]].push_back(v);
		}
	}
}

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		e[u].push_back(v), e[v].push_back(u);
	} 
	
	dfs(1);
	
	bfs(); memset(st, 0, sizeof st);
	for (int i = 1; i <= n; ++i) {
		if (!d[i].size()) break;
		for (auto u : d[i]) {
			for (auto v : e[u]) 
				if (dist[v] == dist[u]+1 && !st[v]) 
					printf("%d %d\n", u, v), st[v] = 1;
		}
	}
	return 0;
} 

ABC250D. 250-like Number:数学(黄)

显然,q[2,n3] 之间的质数。令 numi 表示 [2,i] 之间的质数个数,则 q 对答案的贡献为 nummin(n/q3,q1)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

typedef long long ll;

const int N = 1e6+10;

ll n, ans;
int idx, prime[N], num[N]; bool st[N]; 

void get_prime(int n) {
	st[0] = st[1] = 1;
	for (int i = 2; i <= n; ++i) {
		num[i] += num[i-1];
		if (!st[i]) prime[++ idx] = i, num[i] ++;
		for (int j = 1; j <= idx && prime[j] <= n/i; ++j) {
			st[i*prime[j]] = 1;
			if (i % prime[j] == 0) break;
		} 
	}
} 

int main() {
	scanf("%lld", &n);
	get_prime(pow(n, 1.0/3));
	for (int i = 1; i <= idx; ++i) ans += num[min((ll)(n/pow(prime[i], 3)), (ll)prime[i]-1)];
	printf("%lld\n", ans);
	return 0;
}

ABC250E. Prefix Equality:哈希(绿)

由于左端点固定,还是比较好哈希的。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>

using namespace std;

typedef unsigned long long ull;

const int N = 2e5+10;  

int n, m, a[N], b[N];
map<int, int> numA, numB; ull ha[N], hb[N]; 

ull power(ull a, ull b) {
	ull res = 1;
	while (b) {
		if (b & 1) res *= a;
		a *= a, b >>= 1;
	}
	return res;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
	
	for (int i = 1; i <= n; ++i) {
		if (!numA[a[i]]) numA[a[i]] = 1, ha[i] = ha[i-1]+power(a[i], 3);
		else ha[i] = ha[i-1];
		if (!numB[b[i]]) numB[b[i]] = 1, hb[i] = hb[i-1]+power(b[i], 3);
		else hb[i] = hb[i-1];
	}
	
	scanf("%d", &m);
	while (m -- ) {
		int x, y; scanf("%d%d", &x, &y);
		(ha[x] == hb[y]) ? puts("Yes") : puts("No");
	}
	return 0;
} 

ABC250G. Stonks:反悔贪心(蓝)(双倍经验:CF865D Buy Low Sell High

考虑用一个堆存储前面每天的股票价值。每一天,如果堆不空,我们就找到之前价格最低的一天买入股票然后卖出。这个贪心很显然是假的。考虑怎么反悔。

如果我们在第 i 天买入,第 j 天卖出,我们赚到了 ajai 元。但如果我们反悔了,想在第 k 天卖出,那我们可以赚到 akai=(akaj)+(ajai) 元,也就是说,如果我们想要反悔,只需要在第 j 天将 aj 插入即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

typedef long long ll;

const int N = 3e5+10;

int n, a[N]; ll ans;
priority_queue<int, vector<int>, greater<int>> q;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &a[i]);
		if (q.size() && q.top() < a[i]) ans += (ll)a[i]-q.top(), q.pop(), q.push(a[i]);
		q.push(a[i]);
	}
	printf("%lld\n", ans);
	return 0;
}

9.21

ABC250Ex. Trespassing Takahashi:最短路,并查集(紫)

考虑初始时将所有房子都加入优先队列中,然后跑一遍 dijkstra,令 d(u) 表示 u 到最近的房子的距离,令 c(u,v)c(u,v)+d(u)+d(v)。对于每次询问,若 c(u,v)t 则将 u,v 加入并查集。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>

using namespace std;

#define int long long

typedef pair<int, int> pii;

const int N = 2e5+10;

int n, m, k, q; vector<pii> e[N];
int dist[N], p[N]; bool st[N];

struct Edge {
	int u, v, w;
	
	bool operator < (const Edge &T) const {
		return w < T.w;
	}
} edge[N];

void dijkstra() {
	memset(dist, 0x3f, sizeof dist);
	priority_queue<pii, vector<pii>, greater<pii>> q;
	for (int i = 1; i <= k; ++i) q.push({0, i}), dist[i] = 0;
	while (q.size()) {
		int u = q.top().second; q.pop();
		if (st[u]) continue;
		st[u] = 1;
		for (pii edg : e[u]) {
			int v = edg.first, w = edg.second;
			if (dist[v] > dist[u]+w) dist[v] = dist[u]+w, q.push({dist[v], v});
		}
	}
}

int find(int x) {
	return (p[x] == x) ? x : p[x] = find(p[x]);
}

void merge(int u, int v) {
	int fu = find(u), fv = find(v);
	if (fu == fv) return ;
	p[fu] = fv;
}

signed main() {
	scanf("%lld%lld%lld", &n, &m, &k);
	for (int i = 1; i <= m; ++i) {
		int u, v, w; scanf("%lld%lld%lld", &u, &v, &w);
		e[u].push_back({v, w}), e[v].push_back({u, w}), edge[i] = {u, v, w};
	}
	
	dijkstra();
	for (int i = 1; i <= n; ++i) p[i] = i;
	for (int i = 1; i <= m; ++i) edge[i].w += dist[edge[i].u] + dist[edge[i].v];
	sort(edge+1, edge+m+1);
	
	int now = 1; scanf("%lld", &q);
	while (q -- ) {
		int u, v, t; scanf("%lld%lld%lld", &u, &v, &t);
		while (edge[now].w <= t && now <= m) merge(edge[now].u, edge[now].v), now ++;
		if (find(u) == find(v)) puts("Yes");
		else puts("No");
	}
	return 0;
}

ABC249D. Index Trio:枚举,桶(黄)

我们用一个桶记录 ai 的出现次数。枚举 x[1,V],y[1,V/x],则 aj=x,ak=y 的贡献即为 numsxnumsynumsxy

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 2e5+10, M = 2e5;

int n, a[N], nums[N]; long long ans;

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), nums[a[i]] ++;
	
	for (int i = 1; i <= M; ++i) {
		for (int j = 1; j <= M/i; ++j)
			ans += (long long)nums[i] * nums[j] * nums[i*j];
	}
	printf("%lld\n", ans);
	return 0;
}

ABC249E. RLE:dp(蓝)

fi,j 表示考虑前 i 个字符,压缩后长度为 j 的方案数,那么我们有:

fi,j=25k=0i1fk,jlog10(ik)1

注意到 log10(ik) 很小,对于其值相同的可以放在一起计算。前缀和优化即可。

时间复杂度 O(n2log10n)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

#define int long long

const int N = 3010;

int n, P, ans, f[N][N], s[N][N], p[N];

int power(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = res * a % P;
		a = a * a % P, b >>= 1;
	}
	return res;
}

signed main() {
	scanf("%lld%lld", &n, &P);
	p[0] = 1; for (int i = 1; i <= 4; ++i) p[i] = p[i-1] * 10;
	
	f[0][0] = s[0][0] = 1;
	for (int i = 1; i <= n; ++i) {
		for (int j = 0; j < n; ++j) {
			for (int k = 1; k <= 4; ++k) {
				int l = i-p[k]+1, r = i-p[k-1], pos = j-k-1;
				if (l > r || r < 0 || pos < 0) continue;
				(f[i][j] += 25ll*(s[r][pos]-(l>0?s[l-1][pos]:0)+P)) %= P;
			}
			(s[i][j] = s[i-1][j] + f[i][j]) %= P;
		}
	}
	
	for (int j = 0; j < n; ++j) (ans += f[n][j]) %= P;
	ans = ans * power(25, P-2) % P * 26 % P;
	printf("%lld\n", ans);
	return 0;
}

9.25

ABC248D. Range Count Query:二分(黄)

值域很小,每个数用一个 vector 存即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector> 

using namespace std;

const int N = 2e5+10;

int n, m, a[N]; 
vector<int> nums[N];

int main() {
	scanf("%d", &n); 
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]), nums[a[i]].push_back(i);
	scanf("%d", &m);
	while (m -- ) {
		int l, r, x; scanf("%d%d%d", &l, &r, &x);
		int posl = lower_bound(nums[x].begin(), nums[x].end(), l) - nums[x].begin(), posr = upper_bound(nums[x].begin(), nums[x].end(), r) - nums[x].begin() - 1;
		printf("%d\n", max(0, posr-posl+1));
	}
	return 0;
}

ABC248E. K-colinear Line:数学,枚举(黄)

显然,t=1 时,答案为 Infinity

我们知道,两点确定一条直线。而如何判断第三个点是否在这条直线上呢?直接用解析式算会有误差,设三个点分别为 i,j,k,注意到它们在一条直线上当且仅当:

xixjyiyj=xjxkyjyk

交叉相乘即可。

去重可以取每条直线的起始点和终止点,用 set 去重。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <set>

using namespace std;

#define int long long 
typedef pair<int, int> pii;
#define x first
#define y second

const int N = 310;

int n, t; pii dots[N];
set<pii> s;

bool check(int i, int j, int k) {
	if (dots[i].x == dots[j].x && dots[j].x == dots[k].x) return 1;
	if (dots[i].y == dots[j].y && dots[j].y == dots[k].y) return 1;
	if ((dots[i].x-dots[j].x)*(dots[j].y-dots[k].y) == (dots[j].x-dots[k].x)*(dots[i].y-dots[j].y)) return 1;
	return 0;
}

signed main() {
	scanf("%lld%lld", &n, &t);
	for (int i = 1; i <= n; ++i) scanf("%lld%lld", &dots[i].x, &dots[i].y);
	if (t == 1) puts("Infinity"), exit(0);
	
	for (int i = 1; i <= n; ++i) {
		for (int j = 1; j <= n; ++j) {
			if (j == i) continue;
			int cnt = 2, begin = min(i, j), last = max(i, j);
			for (int k = 1; k <= n; ++k) {
				if (k != i && k != j && check(i, j, k))
					cnt ++, begin = min(begin, k), last = max(last, k);
			}
			if (cnt >= t) s.insert({begin, last});
		}
	}
	printf("%lld\n", (int)s.size());
	return 0;
}

ABC248F. Keep Connect:dp(绿)

fi,j,k 表示考虑前 i 列的点,删除 j 条边,前 i 列的点是否联通的方案数。手推 i 转移到 i+1 的所有情况即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 3010;

int n, p, f[N][N][2];

int main() {
	scanf("%d%d", &n, &p);
	f[1][0][1] = f[1][1][0] = 1;
	for (int i = 1; i < n; ++i) {
		for (int j = 0; j < n; ++j) {
			(f[i+1][j+2][0] += 2ll*f[i][j][1]%p) %= p;
			(f[i+1][j+1][1] += 3ll*f[i][j][1]%p) %= p;
			(f[i+1][j][1] += f[i][j][1]) %= p;
			(f[i+1][j+1][0] += f[i][j][0]) %= p;
			(f[i+1][j][1] += f[i][j][0]) %= p;
 		}
	}
	for (int j = 1; j < n; ++j) printf("%d ", f[n][j][1]); puts("");
	return 0;
}

ABC248G. GCD cost on the tree:树形 dp,数学(紫)

考虑枚举路径上所有点的 gcd 为 t,则其对答案的贡献为 ft

ft 不好直接计算。我们可以求出路径上所有点的 gcd 为 t 的倍数的方案数 gt,则 ft=gti>1fit。对于 gt,我们将所有满足 tai 的点提出来成为一个森林,在每棵树上做树形 dp:令 cntu 表示 u 的子树内,有一个端点为 u 的链的个数;lenu 表示 u 的子树内,有一个端点为 u 的长度之和;resu 表示 u 的子树内经过 u 的所有长度 >1 的链的长度之和。显然,gt=resu

对于边 (u,v),考虑如何转移:

  • cntucntu+cntv,这是显然的。
  • lenulenu+lenv+cntv,这是因为 v 中所有以 v 为一端点的链在接到 u 上时,长度都要增加 1,所以 要再加上 cntv
  • resuresu+cntu×lenv+cntv×lenu,这是因为之前以 u 为一端点的链有 cntu 条,将它们都接到 v 上就会产生 cntu×lenv 的贡献,后面的部分同理。

时间复杂度 O(VlogV+nV)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 100001, P = 998244353;

int n, m, a[N]; vector<int> e[N], g[N];
int cnt[N], len[N], res[N], ans[N]; bool vis[N];

void dfs(int u, int x) {
	cnt[u] = 1, len[u] = 1, res[u] = 0, vis[u] = 0;
	for (auto v : e[u]) {
		if (!vis[v]) continue; dfs(v, x);
		(res[u] += (1ll*cnt[u]*len[v]+1ll*cnt[v]*len[u])%P) %= P;
		(cnt[u] += cnt[v]) %= P;
		(len[u] += len[v]+cnt[v]) %= P;
	}
	(ans[x] += res[u]) %= P;
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i < n; ++i) {
		int u, v; scanf("%d%d", &u, &v);
		e[u].push_back(v), e[v].push_back(u);
	}
	
	for (int i = 1; i <= n; ++i) {
		int x = a[i];
		for (int j = 1; j <= x/j; ++j) {
			if (x % j) continue;
			g[j].push_back(i);
			if (j != x/j) g[x/j].push_back(i);
		}
	}
	
	for (int i = N-1; i > 0; --i) {
		if (!g[i].size()) continue;
		for (auto j : g[i]) vis[j] = 1;
		for (auto j : g[i]) if (vis[j]) dfs(j, i);
		for (int j = i+i; j < N; j += i) (ans[i] += P-ans[j]) %= P;
	}
	
	int sum = 0;
	for (int i = 1; i < N; ++i) (sum += 1ll*ans[i]*i%P) %= P;
	printf("%d\n", sum);
	return 0;
}

9.26

ABC248Ex. Beautiful Subsequences:分治,扫描线(黑)

用分治的方法,令 solve(L,R) 为区间 [L,R] 内符合条件的区间数量,令 mid=L+R2,我们可以递归地计算 solve(L,mid)solve(mid+1,R)。那么我们只需要考虑如何计算形如 [l,r](Llmid<rR) 的合法区间数量。

我们首先预处理出区间 [L,mid] 的后缀最大/小值,[mid,R] 的前缀最大/小值。考虑以下 2 种情况:

  • min,max 均在左侧:枚举 l[L,mid],k[0,K],则 r=maxmin+lk。若合法则可以对答案造成 1 的贡献。
  • min 在右侧,max 在左侧:同样枚举 l[L,mid],双指针统计右端点的范围,且 r+min=l+maxk。用一个桶统计即可。

其余两种情况是类似的。

时间复杂度 O(nklogn),当 k 增大时,将桶改为树状数组即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 2e5+10;

int n, K, a[N]; long long ans;
int mx[N], mn[N], cnt[N<<2];

void solve(int L, int R) {
	if (L == R) {ans ++; return ;}
	int mid = L + R >> 1; solve(L, mid), solve(mid+1, R);
	
	mx[mid] = mn[mid] = a[mid], mx[mid+1] = mn[mid+1] = a[mid+1];
	for (int i = mid-1; i >= L; --i) mx[i] = max(mx[i+1], a[i]), mn[i] = min(mn[i+1], a[i]); // suffix max/min of the left
	for (int i = mid+2; i <= R; ++i) mx[i] = max(mx[i-1], a[i]), mn[i] = min(mn[i-1], a[i]); // prefix max/min of the right
	
	// min: left, max: left
	for (int i = mid; i >= L; --i) {
		for (int k = 0; k <= K; ++k) {
			int r = mx[i] - mn[i] + i - k;
			if (r > mid && r <= R && mx[i] > mx[r] && mn[i] < mn[r]) ans ++;
		}
	}
	// min: right, max: left
	for (int i = mid, r = mid+1, rr = mid+1; i >= L; --i) {
		while (mx[r] < mx[i] && r <= R) cnt[r+mn[r]+n] ++, r ++;
		while (mn[rr] > mn[i] && rr < r) cnt[rr+mn[rr]+n] --, rr ++;
		for (int k = 0; k <= K; ++k) ans += cnt[i+mx[i]-k+n];
	}
	// min: left, max: right
	for (int i = mid+1; i <= R; ++i) cnt[i+mn[i]+n] = 0;
	for (int i = mid+1, l = mid, ll = mid; i <= R; ++i) {
		while (mx[l] < mx[i] && l >= L) cnt[l-mn[l]+n] ++, l --;
		while (mn[ll] > mn[i] && ll > l) cnt[ll-mn[ll]+n] --, ll --;
		for (int k = 0; k <= K; ++k) ans += cnt[i-mx[i]+k+n];
	}
	// min: right, max: right
	for (int i = L; i <= mid; ++i) cnt[i-mn[i]+n] = 0;
	for (int i = mid+1; i <= R; ++i) {
		for (int k = 0; k <= K; ++k) {
			int l = i + k - mx[i] + mn[i];
			if (l <= mid && l >= L && mx[l] < mx[i] && mn[l] > mn[i]) ans ++;
		}
	}
}

int main() {
	scanf("%d%d", &n, &K);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	solve(1, n);
	printf("%lld\n", ans);
	return 0;
}

ABC247D. Cylinder:模拟,队列(橙)

直接用 deque 模拟即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <deque>

using namespace std;

#define int long long
typedef pair<int, int> pii;

const int N = 2e5+10;

int q, idx, tot;
deque<pii> Q;

signed main() {
	scanf("%lld", &q);
	
	int op, c, x;
	while (q -- ) {
		scanf("%lld%lld", &op, &c);
		if (op == 1) scanf("%lld", &x), swap(x, c), Q.push_back({x, c});
		else {
			int tot = 0;
			while (c) {
				auto t = Q.front(); Q.pop_front(); int num = t.first, cnt = t.second;
				if (cnt <= c) tot += num*cnt, c -= cnt;
				else tot += num*c, Q.push_front({num, cnt-c}), c = 0;
			}
			printf("%lld\n", tot);
		}
	}
	return 0;
}

9.27

ABC247E. Max Min:容斥原理,双指针,枚举(黄)

我们令 f(y,x) 表示 liryaix 的区间个数。则答案为 f(y,x)f(y+1,x)f(y,x1)+f(y+1,x1)

f(y,x) 可通过双指针求出。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 2e5+10;

int n, x, y, a[N];

long long f(int y, int x) {
	long long cnt = 0;
	for (int r = 1, l = 1; r <= n; ++r) {
		if (a[r] < y || a[r] > x) l = r+1;
		cnt += r-l+1;
	}
	return cnt;
}

int main() {
	scanf("%d%d%d", &n, &x, &y);
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	printf("%lld\n", f(y, x)-f(y+1, x)-f(y, x-1)+f(y+1, x-1));
	return 0;
}

ABC247F. Cards:dp,并查集(绿)

我们从 piqi 连一条无相边,显然会构成若干个不相交的简单环。对于两个环之间,我们可以通过乘法原理求出答案。而对于一个环内的方案,我们要求每一条边至少要选择一个端点。令 fi 表示环中有 i 个点时的答案,可以发现 f1=1,f2=3,f3=4,,fn=fn1+fn2

可以用并查集维护每个环的大小。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 2e5+10, P = 998244353;

int n, ans = 1, a[N], b[N], p[N], siz[N], f[N];

int find(int x) {
	return p[x] = (p[x] == x) ? x : find(p[x]);
}

void merge(int u, int v) {
	int fu = find(u), fv = find(v);
	if (fu == fv) return ;
	p[fu] = fv, siz[fv] += siz[fu];
}

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) p[i] = i, siz[i] = 1;
	for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) scanf("%d", &b[i]);
	for (int i = 1; i <= n; ++i) merge(a[i], b[i]);
	
	f[1] = 1, f[2] = 3;
	for (int i = 3; i <= n; ++i) (f[i] += f[i-1]+f[i-2]) %= P;
	for (int i = 1; i <= n; ++i) {
		if (find(i) != i) continue;
		ans = (long long)ans * f[siz[i]] % P;
	}
	printf("%d\n", ans);
	return 0;
}

ABC246D. 2-variable Function:枚举,二分(黄)

枚举 a,二分取最小值即可。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

#define int long long

int n, ans = 1e18;

int get(int x) {
	int l = x, r = 1e8;
	while (l < r) {
		int mid = l + r >> 1;
		if ((x+mid)*((__int128)x*x+mid*mid) >= n) r = mid;
		else l = mid+1;
	}
	return (x+l)*(x*x+l*l);
}

signed main() {
	cin >> n; 
	for (int i = 0; i <= 1000000; ++i) ans = min(ans, get(i)); 
	cout << ans << '\n';
	return 0;
}

ABC246E. Bishop 2:bfs(绿)

注意实现。

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>

using namespace std;

typedef pair<int, int> pii;

const int N = 1510;
const int dx[] = {-1, -1, 1, 1}, dy[] = {-1, 1, -1, 1};

int n; pii S, T;
char s[N][N]; int dist[N][N];

void bfs() {
	queue<pii> q; q.push(S);
	for (int i = 1; i <= n; ++i) for (int j = 1; j <= n; ++j) dist[i][j] = 1000000000;
	dist[S.first][S.second] = 0;
	while (q.size()) {
		pii t = q.front(); q.pop(); 
		if (t == T) return ;
		int x = t.first, y = t.second;
		for (int i = 0; i < 4; ++i) {
			int X = x+dx[i], Y = y+dy[i];
			while (X && Y && X <= n && Y <= n && dist[X][Y] > dist[x][y] && s[X][Y] != '#') dist[X][Y] = dist[x][y]+1, q.push({X, Y}), X += dx[i], Y += dy[i];
		}
	}
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> n >> S.first >> S.second >> T.first >> T.second;
	for (int i = 1; i <= n; ++i) cin >> s[i]+1;
	bfs();
	if (dist[T.first][T.second] != 1000000000) printf("%d\n", dist[T.first][T.second]);
	else puts("-1");
	return 0;
}

ABC246F. typewriter:容斥原理,数学(绿)

在不考虑重复的情况下,一个字符集能组成的单词数显然为 ||L。注意到 n 很小,可以直接容斥计算。

bitset 优化计算并集,时间复杂度 O(2nn2w)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <bitset>

using namespace std;

typedef long long ll;

const int P = 998244353;

int n, l, ans; char s[30][30];
bitset<30> chr[30]; 

int power(int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (ll)res * a % P;
		a = (ll)a * a % P, b >>= 1;
	}
	return res; 
}

int main() {
	cin >> n >> l;
	for (int i = 1; i <= n; ++i) {
		cin >> s[i]+1;
		for (int j = 1; j <= strlen(s[i]+1); ++j) chr[i][s[i][j]-'a'+1] = 1;
	}
	
	for (int i = 1; i <= (1<<n)-1; ++i) {
		int cnt = 0; bitset<30> now;
		for (int j = 0; j < 30; ++j) now[j] = 1; 
		for (int j = 0; j < n; ++j) if ((i >> j) & 1) cnt ++, now &= chr[j+1];
		(ans += (cnt&1) ? power(now.count(), l) : P-power(now.count(), l)) %= P;
	}
	cout << ans << '\n';
	return 0;
} 

9.31

ABC 322 不好评价。

ABC322A. First ABC 2:枚举(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 110;

int n;
char s[N];

int main() {
	cin >> n >> s+1;
	for (int i = 3; i <= n; ++i) {
		if (s[i-2] == 'A' && s[i-1] == 'B' && s[i] == 'C')
			printf("%d\n", i-2), exit(0);
	}
	puts("-1"), exit(0);
} 

ABC322B. Prefix and Suffix:枚举(红)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 110;

int n, m;
char s[N], t[N];

int main() {
	cin >> n >> m >> s+1 >> t+1;
	bool pre = 1, suf = 1;
	for (int i = 1; i <= n; ++i) if (s[i] != t[i]) pre = 0;
	for (int i = 1; i <= n; ++i) if (s[n-i+1] != t[m-i+1]) suf = 0;
	if (pre && suf) puts("0");
	else if (pre) puts("1");
	else if (suf) puts("2");
	else puts("3");
}

ABC322C. Festival:二分(橙)

点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 2e5+10;

int n, m, a[N];

int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; ++i) scanf("%d", &a[i]);
	for (int i = 1; i <= n; ++i) printf("%d\n", a[lower_bound(a+1, a+m+1, i)-a]-i);
}

ABC322F. Vacation Query:线段树(蓝)

实际上是对一个 01 串维护两种操作:

  • 区间取反;
  • 求区间最大连续 1 的长度。
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>

using namespace std;

const int N = 5e5+10;

int n, m;
char s[N];

struct Node {
	int l, r, tag;
	int cnt0 = 0, cnt1 = 0, l0 = 0, l1 = 0, r0 = 0, r1 = 0, m0 = 0, m1 = 0;
} seg[N<<2];

void pushup(Node &U, Node &L, Node &R) {
	U.cnt0 = L.cnt0 + R.cnt0, U.cnt1 = L.cnt1 + R.cnt1;
	U.l0 = L.cnt1 ? L.l0 : L.cnt0+R.l0, U.l1 = L.cnt0 ? L.l1 : L.cnt1+R.l1;
	U.r0 = R.cnt1 ? R.r0 : R.cnt0+L.r0, U.r1 = R.cnt0 ? R.r1 : R.cnt1+L.r1;
	U.m0 = max(max(L.m0, R.m0), L.r0+R.l0), U.m1 = max(max(L.m1, R.m1), L.r1+R.l1);
}

void modify(int u) {
	seg[u].tag ^= 1;
	swap(seg[u].cnt0, seg[u].cnt1), swap(seg[u].l0, seg[u].l1), swap(seg[u].r0, seg[u].r1), swap(seg[u].m0, seg[u].m1);
}

void pushup(int u) {
	pushup(seg[u], seg[u<<1], seg[u<<1|1]);
}

void pushdown(int u) {
	if (!seg[u].tag) return ;
	modify(u<<1), modify(u<<1|1), seg[u].tag = 0;
}

void build(int u, int l, int r) {
	seg[u].l = l, seg[u].r = r;
	if (l == r) {
		if (s[l] == '0') seg[u].cnt0 = seg[u].l0 = seg[u].r0 = seg[u].m0 = 1;
		else seg[u].cnt1 = seg[u].l1 = seg[u].r1 = seg[u].m1 = 1;
		return ;
	}
	int mid = l + r >> 1;
	build(u<<1, l, mid), build(u<<1|1, mid+1, r);
	pushup(u);
}

void modify(int u, int l, int r) {
	if (seg[u].l >= l && seg[u].r <= r) {modify(u); return ;}
	pushdown(u);
	int mid = seg[u].l + seg[u].r >> 1;
	if (l <= mid) modify(u<<1, l, r);
	if (r > mid) modify(u<<1|1, l, r);
	pushup(u);
}

Node query(int u, int l, int r) {
	if (seg[u].l >= l && seg[u].r <= r) return seg[u];
	pushdown(u);
	int mid = seg[u].l + seg[u].r >> 1; Node res, L, R;
	if (l <= mid) L = query(u<<1, l, r);
	if (r > mid) R = query(u<<1|1, l, r);
	pushup(res, L, R);
	return res;
} 

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	cin >> n >> m >> s+1;
	build(1, 1, n);
	
	int op, l, r;
	while (m -- ) {
		cin >> op >> l >> r;
		if (op == 1) modify(1, l, r);
		else cout << query(1, l, r).m1 << '\n';
	}
	return 0;
}

本文作者:Jasper08

本文链接:https://www.cnblogs.com/Jasper08/p/17675757.html

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

posted @   Jasper08  阅读(32)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑