AtCoder Beginner Contest 221

读错题意导致下大分,😢

G 的转化过于神秘,锅了。

\(A\sim D\)

A、B、C 模拟,D 差分。

\(E\)

求首 $\leq $ 尾的子序列个数。

线段树维护全局 \(\times 2\) 和但单点 \(+1\) 即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N = 3e5 + 10;
const LL P = 998244353;
int n, a[N], p[N], t, c[N];
LL dat[N << 2], mul[N << 2];

int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

void Build(int p, int l, int r) {
	dat[p] = 0, mul[p] = 1;
	if(l == r) return;
	int mid = (l + r) >> 1;
	Build(p << 1, l, mid);
	Build(p << 1 | 1, mid + 1, r);
}

void Push_Up(int p) {
	dat[p] = (dat[p << 1] + dat[p << 1 | 1]) % P;
}

void Push_Down(int p) {
	if(mul[p] == 1) return;
	int l = p << 1, r = p << 1 | 1;
	dat[l] = dat[l] * mul[p] % P, mul[l] = mul[l] * mul[p] % P;
	dat[r] = dat[r] * mul[p] % P, mul[r] = mul[r] * mul[p] % P;
	mul[p] = 1;
}

void Add(int p, int l, int r, int k) {
	if(l == r) {dat[p] = (dat[p] + 1) % P; return;}
	Push_Down(p);
	int mid = (l + r) >> 1;
	if(k <= mid) Add(p << 1, l, mid, k);
	else Add(p << 1 | 1, mid + 1, r, k);
	Push_Up(p);
}

LL Ask(int p, int l, int r, int L, int R) {
	if(L <= l && r <= R) return dat[p];
	Push_Down(p);
	int mid = (l + r) >> 1; LL sum = 0;
	if(L <= mid) sum = (sum + Ask(p << 1, l, mid, L, R)) % P;
	if(R >  mid) sum = (sum + Ask(p << 1 | 1, mid + 1, r, L, R)) % P;
	return sum;
}

int main() {
	n = read();
	for(int i = 1; i <= n; i ++) a[i] = p[i] = read();
	sort(p + 1, p + n + 1);
	t = unique(p + 1, p + n + 1) - (p + 1);
	Build(1, 1, t);
	LL ans = 0;
	for(int i = 1; i <= n; i ++) {
		int x = lower_bound(p + 1, p + t + 1, a[i]) - p;
		ans = (ans + Ask(1, 1, t, 1, x)) % P;
		dat[1] = dat[1] * 2 % P;
		mul[1] = mul[1] * 2 % P;
		Add(1, 1, t, x);
	}
	printf("%lld\n", ans);
	return 0;
}

\(F\)

求树上好的点集个数,一个点集是好的,当且仅当任意两点间的距离均为直径。

根据直径长度奇偶性讨论,所有直径都经过某个点/某条边。

若有中点,那么处理出每个子树中在某个直径上的点数,简单统计即可。

若有中边,直接统计边上两个点,然后相乘即可。

不难证明不会在某个子树中存在统计不到的直径。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;

typedef long long LL;
const int N = 2e5 + 10;
const LL P = 998244353;
int n, dep[N], num[N], frm[N];
bool vis[N];
vector<int> G[N];

int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

void dfs1(int u, int fa) {
	dep[u] = dep[fa] + 1;
	frm[u] = fa;
	for(int v : G[u]) if(v != fa) dfs1(v, u);
}

void dfs2(int u, int fa, int lim) {
	vis[u] = true;
	dep[u] = dep[fa] + 1;
	if(dep[u] == lim) num[u] ++;
	for(int v : G[u]) if(!vis[v]) dfs2(v, u, lim), num[u] += num[v];
}

int main() {
	n = read();
	for(int i = 1; i < n; i ++) {
		int u = read(), v = read();
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs1(1, 0);
	int s = 0; for(int i = 1; i <= n; i ++) s = dep[s] < dep[i] ? i : s;
	for(int i = 1; i <= n; i ++) dep[i] = 0;
	dfs1(s, 0);
	int t = 0; for(int i = 1; i <= n; i ++) t = dep[t] < dep[i] ? i : t;
	if((dep[t] - 1) & 1) {
		int u, v;
		for(int i = t; i; i = frm[i])
			if(dep[i] == dep[t] / 2 + 1) u = i, v = frm[i];
		vis[u] = vis[v] = true;
		int rec = dep[t];
		for(int i = 1; i <= n; i ++) dep[i] = 0;
		dfs2(u, 0, (rec - 2) / 2 + 1);
		dfs2(v, 0, (rec - 2) / 2 + 1);
		printf("%lld\n", 1LL * num[u] * num[v] % P);
	}
	else {
		int u;
		for(int i = t; i; i = frm[i])
			if(dep[i] == dep[t] / 2 + 1) u = i;
		vis[u] = true;
		int rec = dep[t];
		for(int i = 1; i <= n; i ++) dep[i] = 0;
		dfs2(u, 0, (rec - 1) / 2 + 1);
		LL sum = 0, ans = 0;
		for(int v : G[u]) ans = (ans + 1LL * num[v] * (sum + ans) % P) % P, sum += num[v];
		printf("%lld\n", ans);
	}
	return 0;
}

\(H\)

对于 \(k=1\sim n\),统计长度为 \(k\),没有超过 \(m\) 个相同的数的,和为 \(n\) 的无序序列个数。

将序列升序排序,得到差分数组 \(A\),问题转化为求差分数组的方案数,满足:

  1. \(\sum A_i\times(k-i+1)=n\)
  2. \(A_1> 0\)
  3. 没有 \(\geq m\) 个连续的 \(0\)

\(A\) 翻转,得到 \(\sum A_i\times i=n,A_k>0\), 这样就和 \(k\) 无关了,那么就能够一起统计。

\(f(i,j)\) 表示 \(i\) 个数,和为 \(j\) 的方案数,有转移方程:

\[f(i,j)=\sum\limits_{k=1}^{n/i} \sum\limits_{l=i-m}^{i-1} f(l,j-k\times i) \]

复杂度螺旋升天。

但是仔细观察,\(f(i,j)\)\(f(i,j-i)\) 的方程仅一线之隔,所以可以改为:

\[f(i,j)=f(i,j-i)+\sum\limits_{l=i-m}^{i-1} f(l,j-i) \]

前缀和优化一下,就可以做到 \(O(n^2)\) 了。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;
const int N = 5010;
const LL P = 998244353;
int n, m; LL sum[N], f[N][N];

int read() {
	int x = 0, f = 1; char c = getchar();
	while(c < '0' || c > '9') f = (c == '-') ? -1 : 1, c = getchar();
	while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
	return x * f;
}

int main() {
	n = read(), m = read();
	f[0][0] = sum[0] = 1;
	for(int i = 1; i <= n; i ++) {
		for(int j = i; j <= n; j ++)
			f[i][j] = (sum[j - i] + f[i][j - i]) % P;
		for(int j = 0; j <= n; j ++) {
			sum[j] = (sum[j] + f[i][j]) % P;
			if(i >= m) sum[j] = (sum[j] + P - f[i - m][j]) % P;
		}
	}
	for(int i = 1; i <= n; i ++) printf("%d\n", f[i][n]);
	return 0;
}
posted @ 2021-10-03 11:12  LPF'sBlog  阅读(60)  评论(0编辑  收藏  举报