2024 信友队 noip 冲刺 10.11

T1

给定一张 n n n 个点 m m m 条边的有向图,求至少增加多少条有向边,使得对于图中任意 3 3 3 个不同的点 u , v , w u,v,w u,v,w,若 u u u v v v 有连边、 v v v w w w 有连边,那么 u u u w w w 有连边。

3 ≤ n ≤ 2000 3\le n\le 2000 3n2000 0 ≤ m ≤ 2000 0\le m\le 2000 0m2000

不妨考虑图中的一条链,可以发现这条链的起始点要向链上所有点连边。那么可以推出对于点 u u u,需要向 u u u 能走到的所有点进行连边。 n n n 次 dfs 即可。

// Problem: [ABC292E] Transitivity
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/AT_abc292_e
// Memory Limit: 1 MB
// Time Limit: 2000 ms
// Good luck to the code >w<
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define open(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define abs(x) (((x) > (0)) ? (x) : (-(x)))
#define print(x) printf("%d %d\n", x.first, x.second)
const int maxn = 2005;
bool used[maxn][maxn]; vector<int> mp[maxn];
void addEdge(int u, int v) { mp[u].push_back(v); }
int n, m, ans; bool vis[maxn];
void dfs(int u, int st) {
	if (vis[u]) return ; vis[u] = 1;
	if (st != u && !used[st][u]) used[st][u] = 1, ans ++;
	for (auto v : mp[u])
		dfs(v, st);
}
int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1, u, v; i <= m; i ++)
		scanf("%d %d", &u, &v), addEdge(u, v), used[u][v] = 1;
	for (int i = 1; i <= n; i ++) {
		memset(vis, 0, sizeof(vis));
		dfs(i, i);
	} printf("%d\n", ans);
	return 0; 
}

T2

n n n 个点,初始没有连边, Q Q Q 次操作每次操作为以下操作之一:

  • 给定 u , v u,v u,v,连一条 ( u , v ) (u,v) (u,v) 的有向边。
  • 给定 u u u,删除所有连着 u u u 的边。

每次操作后输出度为 0 0 0 的点的数量。

2 ≤ n ≤ 3 × 1 0 5 2\le n\le 3\times 10^5 2n3×105 1 ≤ Q ≤ 3 × 1 0 5 1\le Q\le 3\times 10^5 1Q3×105

考虑使用 set 来维护每个点连出去的边,然后就做完了。删边时均摊 O ( Q ) O(Q) O(Q)

// Problem: E - Isolation
// Contest: AtCoder - TOYOTA MOTOR CORPORATION Programming Contest 2023#2 (AtCoder Beginner Contest 302)
// URL: https://atcoder.jp/contests/abc302/tasks/abc302_e?lang=en
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
// Good luck to the code >w<
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define open(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define abs(x) (((x) > (0)) ? (x) : (-(x)))
#define print(x) printf("%d %d\n", x.first, x.second)
const int maxn = 3e5 + 5;
set<int> s[maxn];
int n, Q;
int main() {
	scanf("%d %d", &n, &Q);
	for (int i = 1, ans = n, op, u, v; i <= Q; i ++) {
		scanf("%d", &op);
		if (op == 1) {
			scanf("%d %d", &u, &v); 
			ans -= s[u].empty() + s[v].empty();
			s[u].insert(v), s[v].insert(u);
		} else {
			scanf("%d", &u), ans += !s[u].empty();
			for (auto x : s[u]) {
				s[x].erase(s[x].find(u));
				if (s[x].empty()) ans ++;
			}
			s[u].clear();
		} printf("%d\n", ans);
	}
	return 0;
}

T3

给定 n n n 个集合,每个集合包含 [ 1 , m ] [1,m] [1,m] 中的某些数。每次操作可以找到两个集合 S , T S,T S,T 满足 S S S T T T 的交不为空集,然后将 S , T S,T S,T 删去,获得一个 S S S T T T 的并的集合。求获得一个至少包含 1 1 1 m m m 两个原色的集合,需要的最少操作次数。

1 ≤ n ≤ 2 × 1 0 5 1\le n\le 2\times 10^5 1n2×105 2 ≤ m ≤ 2 × 1 0 5 2\le m\le 2\times 10^5 2m2×105,集合总大小 ≤ 5 × 1 0 5 \le 5\times 10^5 5×105

考虑分别建 n n n 个点表示 n n n 个集合,建 m m m 个点表示数字,每个集合向集合中的数连边权为 1 1 1 的有向边、数向包含它的集合连边权为 0 0 0 的边,从所有包含 1 1 1 的集合开始跑最短路,最后的答案即为 m m m 值对应的点的最短路 − 1 -1 1,原因是最后一步不需要操作。

// Problem: F - Merge Set
// Contest: AtCoder - TOYOTA MOTOR CORPORATION Programming Contest 2023#2 (AtCoder Beginner Contest 302)
// URL: https://atcoder.jp/contests/abc302/tasks/abc302_f?lang=en
// Memory Limit: 1024 MB
// Time Limit: 3000 ms
// Good luck to the code >w<
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define open(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define abs(x) (((x) > (0)) ? (x) : (-(x)))
#define print(x) printf("%d %d\n", x.first, x.second)
const int maxn = 2e5 + 5, maxm = 5e5 + 5;
namespace Graph {
	struct Edge { int to, nxt, val; } e[maxm << 1];
	int head[maxn << 1], ecnt = 0;
	void addEdge(int u, int v, int w) {
		e[++ ecnt] = Edge { v, head[u], w };
		head[u] = ecnt;
	}
} using namespace Graph;
int n, m, dis[maxn << 1];
int main() {
	scanf("%d %d", &n, &m); deque<int> q;
	for (int i = 1; i <= n + m; i ++) dis[i] = -1;
	for (int i = 1, siz; i <= n; i ++) {
		scanf("%d", &siz);
		for (int x; siz --; ) {
			scanf("%d", &x), addEdge(i, x + n, 1), addEdge(x + n, i, 0);
			if (x == 1) q.push_back(i), dis[i] = 0;
		}
	} while (!q.empty()) {
		int u = q.front(); q.pop_front();
		// cout << u << '\n';
		if (u == n + m) break; 
		for (int i = head[u]; i; i = e[i].nxt)
			if (dis[e[i].to] == -1)
				dis[e[i].to] = dis[u] + e[i].val, e[i].val ? q.push_back(e[i].to) : q.push_front(e[i].to);
	} printf("%d\n", dis[n + m] == -1 ? -1 : dis[n + m] - 1);
	return 0;
}

T4

给定一个长度为 n n n 的数列 a a a,其中 a i ∈ [ 1 , 4 ] a_i\in [1,4] ai[1,4],每次操作可以选择两个数交换,求最少操作次数使得 a a a 单调不降。

2 ≤ n ≤ 2 × 1 0 5 2\le n\le 2\times 10^5 2n2×105 1 ≤ a i ≤ 4 1\le a_i\le 4 1ai4

先将 a a a 从小到大排序得到目标状态,记为 b b b;那么对于 a i ≠ b i a_i\ne b_i ai=bi i i i,显然要把 a i a_i ai 进行交换到 b b b 中值为 a i a_i ai 的段里。我们令 f ( i , j ) f(i,j) f(i,j) 表示所有 x x x,满足 a x = i a_x=i ax=i b x = j b_x=j bx=j,即它需要从 b b b 中值为 j j j 的一个段移到值为 i i i 的段。显然若能找到一对点 ( u , v ) (u,v) (u,v) 使得 u u u v v v 的起点是对方的终点,那么直接交换是最优的。其次是 3 3 3 个点顺次交换,最后是 4 4 4 个点。然后就做完了。

// Problem: G - Sort from 1 to 4
// Contest: AtCoder - TOYOTA MOTOR CORPORATION Programming Contest 2023#2 (AtCoder Beginner Contest 302)
// URL: https://atcoder.jp/contests/abc302/tasks/abc302_g?lang=en
// Memory Limit: 1024 MB
// Time Limit: 2000 ms
// Good luck to the code >w<
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define open(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define abs(x) (((x) > (0)) ? (x) : (-(x)))
#define print(x) printf("%d %d\n", x.first, x.second)
const int maxn = 2e5 + 5;
int a[maxn], b[maxn];
int cnt[5][5], n;
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i ++)
		scanf("%d", &a[i]), b[i] = a[i];
	sort(b + 1, b + n + 1);
	int chg = 0;
	for (int i = 1; i <= n; i ++)
		if (a[i] != b[i])
			chg ++, cnt[a[i]][b[i]] ++;
	int ans = 0;
	for (int i = 1; i <= 4; i ++)
		for (int j = 1; j <= 4; j ++) if (i != j)
			for (; cnt[i][j] && cnt[j][i]; ans ++, chg -= 2, cnt[i][j] --, cnt[j][i] --);
	for (int i = 1; i <= 4; i ++)
		for (int j = 1; j <= 4; j ++) if (i != j)
			for (int k = 1; k <= 4; k ++) if (i != k && k != j)	
				for (; cnt[i][j] && cnt[j][k] && cnt[k][i]; ans += 2, chg -= 3, cnt[i][j] --, cnt[j][k] --, cnt[k][i] --);
	printf("%d\n", ans + (chg >> 2) * 3);
	return 0;
}

T5

给定 n n n 个长度均为 m m m 的字符串,每个字符串中仅包含 0 , 1 , ⋯   , 9 , ? \texttt{0},\texttt{1},\cdots,\texttt{9},\texttt{?} 0,1,,9,? 这些字符,其中 ? \texttt{?} ? 可以替换为 0 \texttt{0} 0 9 \texttt{9} 9 中的任意字符。求使这 m m m 个字符串字典序严格递增的方案数。

2 ≤ n ≤ 40 2\le n\le 40 2n40 1 ≤ m ≤ 40 1\le m\le 40 1m40

赛时想了一个数位 dp,但是非常难写而且好像是错的。考虑区间 dp,令 f ( l , r , i , v ) f(l,r,i,v) f(l,r,i,v) 表示当前考虑让 [ l , r ] [l,r] [l,r] 中的字符串合法,当前填到了第 i i i 位,当前位的下界是 v v v。边界条件即为 i > m i>m i>m 时返回 [ l = r ] [l=r] [l=r],即填完了检查一下是否还有相等的元素。

显然 f ( l , r , i , v ) f(l,r,i,v) f(l,r,i,v) 可以从 f ( l , r , i , v + 1 ) f(l,r,i,v+1) f(l,r,i,v+1) 处转移,然后我们考虑枚举一个分界点 k ∈ [ l , r − 1 ] k\in [l,r-1] k[l,r1],将区间分为 [ l , k ] [l,k] [l,k] [ k + 1 , r ] [k+1,r] [k+1,r] 两部分,前部分全部在 i i i 位上填 v v v(填 > v >v >v 的情况在 f ( l , r , i , v + 1 ) f(l,r,i,v+1) f(l,r,i,v+1) 中已经统计),答案是 f ( l , k , i + 1 , 0 ) f(l,k,i+1,0) f(l,k,i+1,0);后部分的下界提到了 v + 1 v+1 v+1,答案是 f ( k + 1 , r , i , v + 1 ) f(k+1,r,i,v+1) f(k+1,r,i,v+1);如果前部分有能力全部填成 v v v 那么对答案的贡献即为 f ( l , k , i + 1 , 0 ) × f ( k + 1 , r , i , v + 1 ) f(l,k,i+1,0)\times f(k+1,r,i,v+1) f(l,k,i+1,0)×f(k+1,r,i,v+1),如果 [ l , r ] [l,r] [l,r] 可以全部填成 v v v 那么还可以从 f ( l , r , i + 1 , 0 ) f(l,r,i+1,0) f(l,r,i+1,0)​ 处转移。记忆化搜索即可解决转移顺序问题。

// Problem: [ABC292G] Count Strictly Increasing Sequences
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/AT_abc292_g
// Memory Limit: 1 MB
// Time Limit: 2000 ms
// Good luck to the code >w<
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define open(x) freopen(#x".in", "r", stdin), freopen(#x".out", "w", stdout)
#define abs(x) (((x) > (0)) ? (x) : (-(x)))
#define print(x) printf("%d %d\n", x.first, x.second)
const int maxn = 45, P = 998244353;
int f[maxn][maxn][maxn][10], n, m;
char s[maxn][maxn];
void addto(int &x, int y) { (x += y) %= P; }
int add(int x, int y) { return (x + y) % P; }
int mul(int x, int y) { return 1ll * x * y % P; }
int calc(int L, int R, int i, int val) {
	if (i > m) return L == R;
	if (L > R) return 1; if (val > 9) return 0; 
	if (f[L][R][i][val] != -1) return f[L][R][i][val];
	int res = calc(L, R, i, val + 1);
	for (int k = L; k <= R; k ++) {
		if (s[k][i] != '?' && s[k][i] != val + '0') break;
		addto(res, mul(calc(L, k, i + 1, 0), calc(k + 1, R, i, val + 1)));
	}
	return f[L][R][i][val] = res;
}
int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i ++)
		scanf("%s", s[i] + 1);
	for (int i = 1; i <= n; i ++)
		for (int j = 1; j <= n; j ++)
			for (int k = 1; k <= m; k ++)
				for (int t = 0; t < 10; t ++)
					f[i][j][k][t] = -1;
	return printf("%d\n", calc(1, n, 1, 0)), 0;
}

T6

n n n 场比赛,初始第 i i i 场比赛得分为 p i p_i pi,前 i i i 场比赛的 rating 为 ∑ j = 1 i p j i \cfrac{\sum_{j=1}^i p_j}{i} ij=1ipj;特别地,若存在一个最小的 i i i 使得 [ 1 , i ] [1,i] [1,i] 的 rating ≥ B \ge B B,那么 i i i 及以后的比赛 rating 全部为 B B B Q Q Q 次操作每次修改一场比赛的得分,每次操作后求 [ 1 , n ] [1,n] [1,n] 的 rating。

1 ≤ n ≤ 5 × 1 0 5 1\le n\le 5\times 10^5 1n5×105 1 ≤ Q ≤ 1 0 5 1\le Q\le 10^5 1Q105

我们令 p i ′ = p i − B p'_i= p_i-B pi=piB,然后求 p ′ p' p 的前缀和,记为 s s s;因为
1 i ∑ j = 1 i p j ≥ B 1 i ∑ j = 1 i p j − B ≥ 0 1 i [ ( ∑ j = 1 i p j ) − B i ] ≥ 0 1 i ∑ j = 1 i ( p j − B ) ≥ 0 1 i s i ≥ 0 s i ≥ 0 \begin{aligned}\cfrac{1}{i}\sum_{j=1}^i p_j &\ge B\\\cfrac{1}{i}\sum_{j=1}^i p_j-B&\ge0\\\cfrac{1}{i}\left[\left(\sum_{j=1}^i p_j\right)-Bi\right]&\ge0\\\cfrac{1}{i}\sum_{j=1}^i (p_j-B)&\ge 0\\ \cfrac 1i s_i&\ge0\\s_i&\ge0\end{aligned} i1j=1ipji1j=1ipjBi1[(j=1ipj)Bi]i1j=1i(pjB)i1sisiB00000
所以我们只需要找到第一个 s i ≥ 0 s_i\ge 0 si0 i i i 即可。用线段树轻松维护,每次操作影响一段后缀。

// Problem: [ABC292Ex] Rating Estimator
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/AT_abc292_h
// Memory Limit: 1 MB
// Time Limit: 3000 ms
// Good luck to the code >w<
//
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define lson l, mid, rt << 1
#define rson mid + 1, r, rt << 1 | 1
using pr = pair<int, int>;
const int maxn = 5e5 + 5;
int a[maxn], n, B, Q;
ll mx[maxn << 2], col[maxn << 2], sum[maxn];
void update(int rt) { mx[rt] = max(mx[rt << 1], mx[rt << 1 | 1]); }
void build(int l, int r, int rt) {
	if (l == r) return mx[rt] = sum[l], void(0);
	int mid = (l + r) >> 1;
	build(lson), build(rson), update(rt);
} void color(int rt, ll k) { col[rt] += k, mx[rt] += k; }
void pushcol(int rt) {
	if (!col[rt]) return ;
	color(rt << 1, col[rt]), color(rt << 1 | 1, col[rt]), col[rt] = 0;
} double query(int l, int r, int rt) {
	if (l == r) return 1.0 * (mx[rt] + 1ll * B * l) / l;
	int mid = (l + r) >> 1; pushcol(rt);
	if (mx[rt << 1] >= 0) return query(lson);
	return query(rson);
} void modify(int l, int r, int rt, int nowl, int nowr, ll k) {
	if (nowl <= l && r <= nowr) return color(rt, k);
	int mid = (l + r) >> 1; pushcol(rt);
	if (nowl <= mid) modify(lson, nowl, nowr, k);
	if (mid < nowr) modify(rson, nowl, nowr, k);
	update(rt);
} ll all;
int main() {
	scanf("%d %d %d", &n, &B, &Q);
	for (int i = 1; i <= n; i ++)
		scanf("%d", &a[i]), sum[i] = sum[i - 1] + a[i] - B, all += a[i];
	build(1, n, 1);
	for (int i = 1, x, k; i <= Q; i ++) {
		scanf("%d %d", &x, &k), all += k - a[x];
		modify(1, n, 1, x, n, k - a[x]), a[x] = k;
		if (mx[1] < 0) printf("%.15lf\n", 1.0 * all / n);
		else printf("%.15lf\n", query(1, n, 1));
	}
	return 0;
}

T7

给定一棵 n n n 个点的树,第 i i i 个点上有 ( a i , b i ) (a_i,b_i) (ai,bi) 两个数。每经过一个点可以选择 a , b a,b a,b 中的一个数加入集合 S S S(重复加入仅计算一次),求所有从 1 1 1 出发的路径能得到的最大的 ∣ S ∣ |S| S

2 ≤ n ≤ 2 × 1 0 5 2\le n\le 2\times 10^5 2n2×105 1 ≤ a i , b i ≤ n 1\le a_i,b_i\le n 1ai,bin

对于一条路径,我们考虑将路径上的点 i i i a i , b i a_i,b_i ai,bi 进行连边,这样会得到一张无向图,且有若干连通块。对于一个连通块,设它的点数为 p p p 边数为 q q q,那么它对答案的贡献就是 min ⁡ ( p , q ) \min(p,q) min(p,q)。若这个连通块是一棵树,那么不选根节点后其他点都能入选,答案为边数 q q q;否则考虑图中的环和链,显然环上点都可以入选,而链总是连着一个环,第一个元素已经入选,于是剩下的也都可以选上,答案为点数 p p p。前者 q < p q<p q<p 后者 q ≥ p q\ge p qp,于是取 min ⁡ ( p , q ) \min(p,q) min(p,q) 即为答案。

我们考虑在树上 dfs,每遍历一个点就将 ( a i , b i ) (a_i,b_i) (ai,bi) 加入,回溯时撤销加入这条边,显然可以使用可撤销并查集,用一个栈维护进行过的操作,合并时分类讨论一下即可。代码还没写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值