2021CCPC四川省赛题解ABDEFHIJKLM

2021CCPC四川省赛题解ABDEFHIJKLM

A. Chuanpai

题意

每张牌上有两个数字 x , y    ( 1 ≤ x ≤ y ≤ 6 ) x,y\ \ (1\leq x\leq y\leq 6) x,y  (1xy6),两张牌 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1),(x_2,y_2) (x1,y1),(x2,y2)不同当且仅当 x 1 ≠ y 1 x_1\neq y_1 x1=y1 x 2 ≠ y 2 x_2\neq y_2 x2=y2.

t    ( 1 ≤ t ≤ 100 ) t\ \ (1\leq t\leq 100) t  (1t100)足测试数据.每组测试数据输入一个整数 k    ( 1 ≤ k ≤ 100 ) k\ \ (1\leq k\leq 100) k  (1k100),求有多少种不同的牌满足 x + y = k x+y=k x+y=k.

代码I -> 2021CCPC四川省赛题解-A(打表)

map ans = { {1,0},{2,1},{3,1},{4,2},{5,2},{6,3},{7,3},{8,3},{9,2},{10,2},{11,1},{12,1},{13,0} };

void solve() {
	int k; cin >> k;
	cout << ans[min(13, k)] << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}

代码II -> 2021CCPC四川省赛题解-A(暴力)

void solve() {
	int k; cin >> k;
	
	if (k > 12) {
		cout << 0 << endl;
		return;
	}

	int ans = 0;
	for (int i = 1; i <= 6; i++) {
		for (int j = i; j <= 6; j++) {
			if (i + j > k) break;  // 优化

			ans += i + j == k;
		}
	}
	cout << ans << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}

代码III -> 2021CCPC四川省赛题解-A(暴力+优化)

void solve() {
	int k; cin >> k;
	
	if (k > 12) {
		cout << 0 << endl;
		return;
	}

	int ans = 0;
	for (int i = 1; i <= k / 2; i++) ans += k - i <= 6;
	cout << ans << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


K. K-skip Permutation

题意

1 ∼ n 1\sim n 1n的一个排列 P = p 1 p 2 ⋯ p n P=p_1p_2\cdots p_n P=p1p2pn,定义 f ( P , k ) f(P,k) f(P,k)为使得 p i + k = p i + 1 p_i+k=p_{i+1} pi+k=pi+1 i ∈ [ 1 , n ) i\in [1,n) i[1,n)的个数.给定整数 n , k    ( 1 ≤ n , k ≤ 1 e 6 ) n,k\ \ (1\leq n,k\leq 1\mathrm{e}6) n,k  (1n,k1e6),构造一个 1 ∼ n 1\sim n 1n的排列使得 f ( P , k ) f(P,k) f(P,k)最大.

思路

1 , 1 + k , 1 + 2 k , ⋯   , 2 , 2 + k , 2 + 2 k , ⋯   , 3 , 3 + k , 3 + 2 k , ⋯ 1,1+k,1+2k,\cdots,2,2+k,2+2k,\cdots,3,3+k,3+2k,\cdots 1,1+k,1+2k,,2,2+k,2+2k,,3,3+k,3+2k,.

代码 -> 2021CCPC四川省赛题解-K(思维)

void solve() {
	int n, k; cin >> n >> k;

	set s;
	for (int i = 1; i <= n; i++) s.insert(i);

	string ans;
	while (s.size()) {
		int a = *s.begin(); s.erase(a);
		ans += to_string(a) + " ";
		while (s.count(a + k)) {
			ans += to_string(a + k) + " ";
			s.erase(a + k);
			a += k;
		}
	}
	cout << ans.substr(0, ans.length() - 1);
}

int main() {
	solve();
}


D. Rock Paper Scissors

题意

A和B玩游戏.有三种类型的牌:石头、布、剪刀.初始时每个玩家有 n n n张牌,游戏有 n n n轮,A先打出一张牌,展示给B,然后B再打出一张牌.每一轮的计分方式如下表,打出的牌不能再回收.最后总得分等于每轮得分相加.

B ↓ \downarrow A → \rightarrow 石头 剪刀
石头 0 0 0 − 1 -1 1 1 1 1
1 1 1 0 0 0 − 1 -1 1
剪刀 − 1 -1 1 1 1 1 0 0 0

A想最小化得分,B想最大化得分,两人都采取最优策略,求最后得分.

t    ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t  (1t1000)组测试数据.每组测试数据第一行输入三个整数 b r , b p , b s    ( 0 ≤ b r , b p , b s ≤ 1 e 9 ) b_r,b_p,b_s\ \ (0\leq b_r,b_p,b_s\leq 1\mathrm{e}9) br,bp,bs  (0br,bp,bs1e9),分别表示A手中的石头、布、剪刀牌数.第二行输入三个整数 d r , d p , d s    ( 0 ≤ d r , d p , d s ≤ 1 e 9 ) d_r,d_p,d_s\ \ (0\leq d_r,d_p,d_s\leq 1\mathrm{e}9) dr,dp,ds  (0dr,dp,ds1e9),分别表示B手中的石头、布、剪刀牌数.数据保证 b r + b p + b s = d r + d p + d s b_r+b_p+b_s=d_r+d_p+d_s br+bp+bs=dr+dp+ds.

思路

A出石头,B尽量出布, a n s + + ans++ ans++.若A还有石头,B尽量出石头, a n s ans ans不变.若A还有石头,B只能出剪刀, a n s − − ans-- ans.

代码 -> 2021CCPC四川省赛题解-D(贪心+模拟)

void solve() {
	int br, bp, bs, dr, dp, ds; cin >> br >> bp >> bs >> dr >> dp >> ds;

	ll ans = 0;
	int tmp;
	tmp = min(br, dp); ans += tmp; br -= tmp, dp -= tmp;
	tmp = min(bp, ds); ans += tmp; bp -= tmp, ds -= tmp;
	tmp = min(bs, dr); ans += tmp; bs -= tmp, dr -= tmp;

	if (br) {
		br -= min(br, dr);
		ans -= br;
	}
	if (bp) {
		bp -= min(bp, dp);
		ans -= bp;
	}
	if (bs) {
		bs -= min(bs, ds);
		ans -= bs;
	}
	cout << ans << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


H. Nihongo wa Muzukashii Desu

题意

2021CCPC四川省赛题解ABDEFHIJKLM_第1张图片

给定 t    ( 1 ≤ t ≤ 100 ) t\ \ (1\leq t\leq 100) t  (1t100)个长度不超过 30 30 30的单词的masu形式,按上述规则将其转化为te形式.数据保证不出现以"imasu"结尾的单词.

思路

易想到用map存下所有的替换规则,但map和unordered_map都可能改变其中元素的顺序,可能出现以"mimasu"结尾的优先被判为"imasu"结尾的情况,故改用vector>存,并将"imasu"的替换规则放在最后.

代码 -> 2021CCPC四川省赛题解-H(模拟)

vector> mp = {
	{"shimasu","shite"},{"chimasu","tte"},{"rimasu","tte"},
	{"mimasu","nde"},{"bimasu","nde"},{"nimasu","nde"},
	{"kimasu","ite"},{"gimasu","ide"},{"imasu","tte"} };  // 注意imasu放最后

void solve() {
	string str; cin >> str;
	
	if (str == "ikimasu") {  // 注意特判
		cout << "itte" << endl;
		return;
	}

	for (auto s : mp) {
		int tmp = str.length() - s.first.length();
		if (tmp <= 0) continue;

		if (str.substr(tmp) == s.first) {
			cout << str.substr(0, str.length() - s.first.length()) + s.second << endl;
			break;
		}
	}
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


B. Hotpot

题意

编号 0 ∼ ( n − 1 ) 0\sim (n-1) 0(n1) n n n个人依次围在火锅旁,每个人有一个喜悦值,初始时为 0 0 0.锅中共有 k k k种原料,其中第 i    ( 0 ≤ i ≤ n − 1 ) i\ \ (0\leq i\leq n-1) i  (0in1)个人喜欢原料 a i a_i ai,初始时锅中无原料.从 0 0 0号人开始,每个人轮流做 m m m次操作,轮到第 i i i个人时,若锅中有他喜欢的原料,他会吃一个并增加 1 1 1点喜悦值;否则他会加入一个他喜欢的原料 a i a_i ai.求最终每个人的喜悦值.

t    ( 1 ≤ t ≤ 1000 ) t\ \ (1\leq t\leq 1000) t  (1t1000)组测试数据.每组测试数据第一行输入三个整数 n , k , m    ( 1 ≤ n , k ≤ 1 e 5 , 1 ≤ m ≤ 1 e 9 ) n,k,m\ \ (1\leq n,k\leq 1\mathrm{e}5,1\leq m\leq 1\mathrm{e}9) n,k,m  (1n,k1e5,1m1e9).第二行输入 n n n个整数 a 0 , ⋯   , a n − 1    ( 1 ≤ a i ≤ k ) a_0,\cdots,a_{n-1}\ \ (1\leq a_i\leq k) a0,,an1  (1aik).数据保证所有测试数据的 n n n之和、 k k k之和都不超过 2 e 5 2\mathrm{e}5 2e5.

思路

显然 n n n次操作是一个周期.

考察人数 n n n的奇偶性对结果的影响,不妨设 n n n个人都喜欢原料 1 1 1.

n n n为偶数时,不妨设 n = 2 n=2 n=2.第一次操作时锅中无原料 1 1 1,则 0 0 0号人加入一个原料 1 1 1.第二次操作时 1 1 1号人吃一个原料 1 1 1,增加 1 1 1点喜悦值.显然 0 0 0号人每次操作都是加入原料 1 1 1, 1 1 1号人每次操作都是吃一个原料 1 1 1.

n n n为奇数,不妨设 n = 3 n=3 n=3.第一次操作时锅中无原料 1 1 1,则 0 0 0号人加入一个原料 1 1 1.第二次操作时 1 1 1号人吃一个原料 1 1 1,增加 1 1 1点喜悦值.第三次操作时锅中无原料 1 1 1,则 2 2 2号人加入一个原料 1 1 1.第四次操作时 0 0 0号人吃一个原料 1 1 1,增加 1 1 1点喜悦值.第五次操作时锅中无原料 1 1 1,则 1 1 1号人加入一个原料 1 1 1.第六次操作时 2 2 2号人吃一个原料 1 1 1,增加 1 1 1点喜悦值.

n n n的奇偶性对结果有影响,而 n n n为偶数时每个人最终的喜悦值易求出,不妨取 2 n 2n 2n为周期,统一为 n n n是偶数的情况.

m m m次操作,先做 ⌊ m 2 n ⌋ \left\lfloor\dfrac{m}{2n}\right\rfloor 2nm次完整周期,再做 m % ( 2 n ) m\% (2n) m%(2n)次剩下的操作.一个周期和剩下的操作直接暴力模拟即可.

代码 -> 2021CCPC四川省赛题解-B(思维+模拟)

void solve() {
	int n, k, m; cin >> n >> k >> m;
	vii a(n, { 0,0 });  // first为喜欢吃的食物,second为喜悦值
	for (int i = 0; i < n; i++) cin >> a[i].first;

	vi food(k + 1);  // 当前锅中每种食物的数量
	int round = m / (2 * n);  // 完整周期数
	if (round) {
		for (int i = 0; i < 2 * n; i++) {
			int j = i % n;  // 当前轮到的人
			if (food[a[j].first]) food[a[j].first]--, a[j].second++;
			else food[a[j].first]++;
		}

		for (int i = 0; i < n; i++) a[i].second *= round;
	}

	for (int i = 0; i < m % (2 * n); i++) {  // 剩下的操作
		int j = i % n;  // 当前轮到的人
		if (food[a[j].first]) food[a[j].first]--, a[j].second++;
		else food[a[j].first]++;
	}

	for (int i = 0; i < n; i++) cout << a[i].second << " \n"[i == n - 1];
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


L. Spicy Restaurant

题意 ( 2   s 2\ \mathrm{s} 2 s)

有编号 1 ∼ n 1\sim n 1n n n n个餐厅,其中第 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)个餐厅的火锅辣度为 w i w_i wi.每个餐厅可视为包含 m m m条边的无向图上的一个节点,每条边的权值都为 1 1 1.现有 q q q个人,每个人有一个能接受的最大辣度.对每个人,求他到一个他能接受的辣度的餐厅的最短距离.

第一行输入三个整数 n , m , q    ( 1 ≤ n , m ≤ 1 e 5 , 1 ≤ q ≤ 5 e 5 ) n,m,q\ \ (1\leq n,m\leq 1\mathrm{e}5,1\leq q\leq 5\mathrm{e}5) n,m,q  (1n,m1e5,1q5e5).第二行输入 n n n个整数 w 1 , ⋯   , w n    ( 1 ≤ w i ≤ 100 ) w_1,\cdots,w_n\ \ (1\leq w_i\leq 100) w1,,wn  (1wi100).接下来 m m m行每行输入两个相异的整数 u , v    ( 1 ≤ u , v ≤ n ) u,v\ \ (1\leq u,v\leq n) u,v  (1u,vn),表示节点 u u u v v v间存在边.接下来 q q q行每行输入两个整数 p , a    ( 1 ≤ p ≤ n , 1 ≤ a ≤ n ) p,a\ \ (1\leq p\leq n,1\leq a\leq n) p,a  (1pn,1an),分别表示一个人所在的节点编号和他能接受的最大辣度.

对每个人,输出其到他能接受的辣度的餐厅的最短距离,若不存在这样的餐厅,输出 − 1 -1 1.

思路

d i s [ i ] [ j ] dis[i][j] dis[i][j]表示节点 j j j到辣度不超过 i i i的节点的最短距离.因 i ≤ 100 i\leq 100 i100,用 100 100 100次BFS预处理即可.

代码 -> 2021CCPC四川省赛题解-L(思维+BFS)

const int MAXN = 1e5 + 5, MAXM = 105;
int n, m, q;  // 节点数、边数、询问数
int w[MAXN];
int dis[MAXM][MAXN];  // dis[i][j]表示节点j到辣度不超过i的节点的最短距离
vi edges[MAXN];

void bfs(int a) {  // 辣度a
	qi que;
	for (int i = 1; i <= n; i++) {  // 辣度不超过a的节点入队,并初始化dis[a][]
		if (w[i] <= a) {
			que.push(i);
			dis[a][i] = 0;
		}
	}

	while (que.size()) {
		int u = que.front(); que.pop();
		for (auto v : edges[u]) {
			if (dis[a][v] > dis[a][u] + 1) {
				dis[a][v] = dis[a][u] + 1;
				que.push(v);
			}
		}
	}
}

void solve() {
	memset(dis, INF, so(dis));

	cin >> n >> m >> q;
	for (int i = 1; i <= n; i++) cin >> w[i];
	while (m--) {
		int u, v; cin >> u >> v;
		edges[u].push_back(v), edges[v].push_back(u);
	}

	for (int i = 1; i <= 100; i++) bfs(i);  // 预处理dis[i][]

	while (q--) {
		int p, a; cin >> p >> a;
		cout << (dis[a][p] == INF ? -1 : dis[a][p]) << endl;
	}
}

int main() {
	solve();
}


M. True Story

题意

编号 1 ∼ n 1\sim n 1n n n n个人要赶不上飞机了.在 0 0 0时初,他们距机场 x   k m x\ \mathrm{km} x km,登机时间为 p 0 p_0 p0时初.第 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)个人的速度为 s i   k m / h s_i\ \mathrm{km/h} si km/h,他们在不晚于登机时间赶到机场.现登机时间会推迟 k k k次,其中第 i    ( 1 ≤ i ≤ k ) i\ \ (1\leq i\leq k) i  (1ik)次推迟会在 t i t_i ti时初发布,并将登机时间推迟到 p i p_i pi时初.每次推迟发布时每个人会立即收到消息,且每个人只会在推迟发布时才能知道消息,不能提前预判.每个人只会在自己能赶上飞机的前提下才会移动,若他发现此时已赶不上飞机,他会停在原地;否则他会从上次停下的地方继续移动.求最后能赶上飞机的人数.

第一行输入四个整数 n , k , x , p 0    ( 1 ≤ n , k ≤ 1 e 5 , 1 ≤ x , p 0 ≤ 1 e 9 ) n,k,x,p_0\ \ (1\leq n,k\leq 1\mathrm{e}5,1\leq x,p_0\leq 1\mathrm{e}9) n,k,x,p0  (1n,k1e5,1x,p01e9).第二行输入 n n n个整数 s 1 , ⋯   , s n    ( 1 ≤ s i ≤ 1 e 9 ) s_1,\cdots,s_n\ \ (1\leq s_i\leq 1\mathrm{e}9) s1,,sn  (1si1e9).第三行输入 k k k个整数 t 1 , ⋯   , t k    ( 1 ≤ t i ≤ 1 e 9 , t i < t i + 1 , t i < p i − 1 ) t_1,\cdots,t_k\ \ (1\leq t_i\leq 1\mathrm{e}9,t_it1,,tk  (1ti1e9,ti<ti+1,ti<pi1).第四行输入 k k k个整数 p 1 , ⋯   , p k    ( 1 ≤ p i ≤ 1 e 9 , p i < p i + 1 ) p_1,\cdots,p_k\ \ (1\leq p_i\leq 1\mathrm{e}9,p_ip1,,pk  (1pi1e9,pi<pi+1).

思路I

最坏的情况是初始时每个人都赶不上,直至某次推迟后才发现自己能赶上.只需考虑最大间隔 Δ t = max ⁡ 1 ≤ i ≤ k ( p i − t i ) \displaystyle\Delta t=\max_{1\leq i\leq k}(p_i-t_i) Δt=1ikmax(piti)的时间内能否赶上即可.

代码I -> 2021CCPC四川省赛题解-M(思维I)

void solve() {
	int n, k, x, p0; cin >> n >> k >> x >> p0;

	vi s(max(n, k) + 1), t(max(n, k) + 1);  // 速度、推迟发布时间
	for (int i = 1; i <= n; i++) cin >> s[i];
	for (int i = 1; i <= k; i++) cin >> t[i];

	int maxtime = 0;
	for (int i = 1; i <= k; i++) {
		int p; cin >> p;
		maxtime = max(maxtime, p - t[i]);
	}

	int ans = 0;
	for (int i = 1; i <= n; i++) ans += (ll)s[i] * maxtime >= x;
	cout << ans;
}

int main() {
	solve();
}

思路II

注意到最终能赶上的人数 = = =原来能赶上的人数 + + +每次延迟后能赶上的人数,且一开始能赶上的人推迟后也能赶上,将所有人按速度降序排列后每次检查速度最大的能能否赶上即可.

代码II -> 2021CCPC四川省赛题解-M(思维II)

void solve() {
	int n, k, x, p0; cin >> n >> k >> x >> p0;

	vi s(max(n, k) + 1), t(max(n, k) + 1), p(max(n, k) + 1);  // 速度、推迟发布时间、登机时间
	for (int i = 1; i <= n; i++) cin >> s[i];
	for (int i = 1; i <= k; i++) cin >> t[i];
	for (int i = 1; i <= k; i++) cin >> p[i];

	sort(s.begin() + 1, s.end(), greater());

	int idx = 1;
	while (idx <= n) {  // 求原来能赶上的人数
		if ((ll)s[idx] * p0 >= x) idx++;
		else break;
	}

	for (int i = 1; i <= k; i++)  // 求推迟后能赶上的人数
		while (idx <= n && (ll)s[idx] * (p[i] - t[i]) >= x) idx++;

	cout << idx - 1;  // 注意-1
}

int main() {
	solve();
}


E. Don’t Really Like How The Story Ends

题意

给定一张包含 n n n个节点、 m m m条边的无向图,问至少添加多少条边才能使得从节点 1 1 1开始的DFS序为 1 ∼ n 1\sim n 1n.

t t t组测试数据.每组测试数据第一行输入两个整数 n , m    ( 1 ≤ n , m ≤ 1 e 5 ) n,m\ \ (1\leq n,m\leq 1\mathrm{e}5) n,m  (1n,m1e5).接下来 m m m行每行输入两个整数 u , v    ( 1 ≤ u , v ≤ n ) u,v\ \ (1\leq u,v\leq n) u,v  (1u,vn),表示节点 u u u v v v间存在边.数据保证所有测试数据的 ( n + m ) (n+m) (n+m)之和不超过 1 e 6 1\mathrm{e}6 1e6.

思路

从节点 1 1 1开始搜索,若节点 1 1 1与节点 2 2 2间存在边,则继续搜索节点 2 2 2.

①若节点 2 2 2与节点 3 3 3间存在边,继续搜索节点 3 3 3.

②若节点 2 2 2与节点 3 3 3间不存在边,且节点 2 2 2不与节点 u    ( u > 3 ) u\ \ (u>3) u  (u>3)相连,则节点 2 2 2已无搜索价值,直接回溯即可.

③若节点 2 2 2与节点 3 3 3间不存在边,但节点 2 2 2与节点 u    ( u > 3 ) u\ \ (u>3) u  (u>3)相连,则需添加一条边 < 2 , 3 > <2,3> <2,3>.

DFS模拟上述过程即可.

注意连一条节点 1 1 1到节点 ( n + 1 ) (n+1) (n+1)的边作为哨兵,否则若 1 1 1是孤立点则无法往下搜.

代码 -> 2021CCPC四川省赛题解-E(思维+DFS)

const int MAXN = 1e5 + 5;
int n, m;
set edges[MAXN];
int nxt;  // 下一个要搜索的节点
int ans;

void dfs(int u) {
	if (u == n + 1) return;  // 遍历完

 	for (auto v : edges[u]) {
		if (v < nxt) continue;  // 遍历过
		
		while (v >= nxt) {
			ans += v > nxt;
			dfs(nxt++);
		}
	}
}

void solve() {
	cin >> n >> m;

	ans = 0, nxt = 2;
	for (int i = 1; i <= n; i++) edges[i].clear();

	while (m--) {
		int u, v; cin >> u >> v;
		edges[u].insert(v), edges[v].insert(u);
	}

	edges[1].insert(n + 1);  // 哨兵

	dfs(1);
	cout << ans << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


J. Ants

题意

编号 1 ∼ n 1\sim n 1n n n n只蚂蚁在长为 ( 1 e 9 + 1 ) (1\mathrm{e}9+1) (1e9+1)的木棒上,初始时第 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)只蚂蚁在距离棒左端 a i a_i ai单位处,有些蚂蚁面朝左,有些蚂蚁面朝右.所有蚂蚁以速度 1 1 1单位 / s /\mathrm{s} /s运动,两蚂蚁相遇时各自立即转向.棒的两端各有一个障碍物,每个障碍物有一个耐久值,左边的耐久值为 A A A,右边的耐久值为 B B B,耐久值降为 0 0 0时障碍物被破坏.一只蚂蚁撞到障碍物时会立即转身,同时障碍物的耐久 − 1 -1 1.经过被破坏的障碍物的蚂蚁会掉下木棒.求所有蚂蚁掉下木棒所需的时间.

第一行输入三个整数 n , a , b    ( 1 ≤ n ≤ 1 e 6 , 1 ≤ a , b ≤ 1 e 9 ) n,a,b\ \ (1\leq n\leq 1\mathrm{e}6,1\leq a,b\leq 1\mathrm{e}9) n,a,b  (1n1e6,1a,b1e9).第二行输入 n n n格整数 a 1 , ⋯   , a n    ( 1 ≤ a i ≤ 1 e 9 , a i < a i + 1 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 1\mathrm{e}9,a_ia1,,an  (1ai1e9,ai<ai+1).第三行输入 n n n个整数 d 1 , ⋯   , d n    ( d i ∈ { 0 , 1 } ) d_1,\cdots,d_n\ \ (d_i\in\{0,1\}) d1,,dn  (di{0,1}),其中 d i = 0 d_i=0 di=0表示第 i i i只蚂蚁初始时面朝左, d i = 1 d_i=1 di=1表示第 i i i只蚂蚁初始时面朝右.

思路

两蚂蚁相遇时转向视为互相穿过对方.设棒长 l l l,显然每 2 l 1 = 2 l   s \dfrac{2l}{1}=2l\ \mathrm{s} 12l=2l s所有蚂蚁回到自己的初始位置,且朝向不变.注意到前 ⌊ min ⁡ { A , B } n ⌋ \left\lfloor\dfrac{\min\{A,B\}}{n}\right\rfloor nmin{A,B}个完整的周期没有障碍物被破坏,直接计算障碍物耐久值的减少量即可.

对不完整的周期,用两个小根堆 t l e f t , t r i g h t tleft,tright tleft,tright分别表示面朝左、右的蚂蚁走到对应的端点所需的时间,每次取所需时间最短的蚂蚁进行扩展,转向后插入另一个堆即可.

代码 -> 2021CCPC四川省赛题解-J(思维+模拟) By : 塔子哥来了

const int len = 1e9 + 1;  // 棒长

void solve() {
	int n, A, B; cin >> n >> A >> B;
	vi a(n + 1), d(n + 1);  // 初位置、朝向
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) cin >> d[i];

	int round = min(A, B) / n;  // 完整的周期数
	ll ans1 = (ll)round * len * 2;  // 完整的周期所需时间
	int damage = round * n;  // 完整的周期对障碍物造成的伤害
	A -= damage, B -= damage;

	pque> tleft, tright;  // 面朝左、右的蚂蚁走到对应的端点所需的时间
	for (int i = 1; i <= n; i++) {
		if (d[i]) tright.push(len - a[i]);
		else tleft.push(a[i]);
	}

	ll ans2 = 0;  // 不完整的周期所需的时间
	while (tleft.size() || tright.size()) {
		if (tleft.size()) {
			ll t = tleft.top(); tleft.pop();
			ans2 = max(ans2, t);

			if (A) {
				A--;
				tright.push(t + len);  // 转向
			}
		}

		if (tright.size()) {
			ll t = tright.top(); tright.pop();
			ans2 = max(ans2, t);

			if (B) {
				B--;
				tleft.push(t + len);  // 转向
			}
		}
	}

	cout << ans1 + ans2;
}

int main() {
	solve();
}


I. Monster Hunter

题意

有编号 1 ∼ m 1\sim m 1m m m m只怪物,初始时第 i    ( 1 ≤ i ≤ m ) i\ \ (1\leq i\leq m) i  (1im)只怪物的血量为 h i h_i hi.玩家的攻击力是一个长度为 n n n的循环序列 a 1 , ⋯   , a n a_1,\cdots,a_n a1,,an.当怪物的血量 ≤ 0 \leq 0 0时被消灭.求消灭所有怪物所需的最少攻击次数.

t t t组测试数据.每组测试数据第一行输入一个整数 n    ( 1 ≤ n ≤ 1 e 5 ) n\ \ (1\leq n\leq 1\mathrm{e}5) n  (1n1e5).第二行输入 n n n个整数 a 1 , ⋯   , a n    ( 1 ≤ a i ≤ 3 ) a_1,\cdots,a_n\ \ (1\leq a_i\leq 3) a1,,an  (1ai3).第三行输入一个整数 m    ( 1 ≤ m ≤ 1 e 5 ) m\ \ (1\leq m\leq 1\mathrm{e}5) m  (1m1e5).第四行输入 m m m个整数 h 1 , ⋯   , h m    ( 1 ≤ h i ≤ 1 e 9 ) h_1,\cdots,h_m\ \ (1\leq h_i\leq 1\mathrm{e}9) h1,,hm  (1hi1e9).数据保证所有测试数据的 n n n之和、 m m m之和都不超过 1 e 5 1\mathrm{e}5 1e5.

思路 By : 墨染空

显然有解,且可二分出最小攻击次数.对每个攻击次数,可求出各种攻击力分别能用多少次.

显然为使得攻击次数最小,应优先用攻击力大的攻击,且尽量先打死血量少的怪物.故将 h [ ] h[] h[]升序排列.

对攻击力为 3 3 3 2 2 2的攻击,按怪物血量模 3 3 3 2 2 2的余数分类.

(1)①先对每个怪物在不打死的前提下尽量用攻击力为 3 3 3的攻击.注意若最大攻击次数 ⌊ h [ i ] 3 ⌋ \left\lfloor\dfrac{h[i]}{3}\right\rfloor 3h[i]后怪物剩下的血量为奇数,如原血量为 7 7 7,攻击一次后变为 1 1 1的情况,此时先用一次攻击力为 3 3 3的攻击,再用两次攻击力为 2 2 2的攻击更优.

​ ②若还有多余的攻击力为 3 3 3的攻击,对每个怪物在不打死的前提下尽量用.

​ ③若还有多余的攻击力为 3 3 3的攻击,先优先打死血量为 2 2 2的怪物,剩余的攻击再打死血量为 1 1 1的怪物.

(2)①先对血量为偶数的怪物用攻击力为 2 2 2的攻击,再对血量为奇数的怪物用攻击力为 2 2 2的攻击.

​ ②若还有多余的攻击力为 2 2 2的攻击,打死血量为 1 1 1的怪物.

(3)剩余的怪物用攻击力为 1 1 1的攻击打.

代码 -> 2021CCPC四川省赛题解-I(贪心+二分)

const int MAXN = 1e5 + 5;
int n, m;  // 攻击力数、怪物数
int a[MAXN][3];  // 攻击力出现的次数
int h[MAXN], tmph[MAXN];  // 怪物血量及其备份
ll cnt[4];  // 每种攻击力能用的次数

bool check(ll x) {
	for (int i = 1; i <= m; i++) tmph[i] = h[i];  // 备份
	for (int i = 1; i <= 3; i++) cnt[i] = (ll)(x / n) * a[n][i] + a[x % n][i];

	for (int i = 1; i <= m; i++) {  // 全部用攻击力3
		int t = min((ll)tmph[i] / 3, cnt[3]);  // 攻击次数
		if (!t) continue;

		if ((tmph[i] - 3 * t) & 1) t--;  // 打完剩下的血量为奇数,不如用若干次攻击力为2更优
		tmph[i] -= 3 * t, cnt[3] -= t;
	}

	for (int i = 1; i <= m; i++) {  // 全部用攻击力3
		if (!cnt[3]) break;  // 攻击力为3用完了

		int t = min((ll)tmph[i] / 3, cnt[3]);  // 攻击次数
		tmph[i] -= 3 * t, cnt[3] -= t;
	}

	for (int i = 1; i <= m; i++) {  // 用攻击力3打血量为2的怪物
		if (!cnt[3]) break;  // 攻击力为3用完了

		if (tmph[i] == 2) tmph[i] = 0, cnt[3]--;
	}

	for (int i = 1; i <= m; i++) {  // 用攻击力3打血量为1的怪物
		if (!cnt[3]) break;  // 攻击力为3用完了

		if (tmph[i] == 1) tmph[i] = 0, cnt[3]--;
	}

	for (int i = 1; i <= m; i++) {  // 用攻击力2打血量为偶数的怪物
		if (!cnt[2]) break;  // 攻击力为2用完了
		
		if (tmph[i] % 2 == 0) {
			int t = min((ll)tmph[i] / 2, cnt[2]);  // 攻击次数
			tmph[i] -= 2 * t, cnt[2] -= t;
		}
	}

	for (int i = 1; i <= m; i++) {  // 用攻击力2打血量为奇数的怪物
		if (!cnt[2]) break;  // 攻击力为2用完了

		if (tmph[i] % 2 == 1) {
			int t = min((ll)tmph[i] / 2, cnt[2]);  // 攻击次数
			tmph[i] -= 2 * t, cnt[2] -= t;
		}
	}

	for (int i = 1; i <= m; i++) {  // 用攻击力2打血量为1的怪物
		if (!cnt[2]) break;  // 攻击力为2用完了

		if (tmph[i] == 1) tmph[i] = 0, cnt[2]--;
	}

	ll rest = 0;
	for (int i = 1; i <= m; i++) rest += tmph[i];
	return cnt[1] >= rest;
}

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int x; cin >> x;
		for (int j = 1; j <= 3; j++) a[i][j] = a[i - 1][j];  // 继承上一个状态
		a[i][x]++;
	}

	cin >> m;
	ll l = m, r = 0;
	for (int i = 1; i <= m; i++) {
		cin >> h[i];
		r += h[i];
	}
	sort(h + 1, h + m + 1);

	while (l < r) {
		ll mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << l << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


F. Direction Setting

题意

给定一张包含编号 1 ∼ n 1\sim n 1n n n n个节点和编号 1 ∼ m 1\sim m 1m m m m条边的无向图,其中节点 i    ( 1 ≤ i ≤ n ) i\ \ (1\leq i\leq n) i  (1in)有一个限制 a i a_i ai.为每条边规定一个方向,使得该图变为有向图,同时 D = ∑ i = 1 n max ⁡ { 0 , d i − a i } \displaystyle D=\sum_{i=1}^n \max\{0,d_i-a_i\} D=i=1nmax{0,diai}最小,其中 d i d_i di表示节点 i i i的入度.

t t t组测试数据.每组测试数据第一行输入两个整数 n , m    ( 2 ≤ n ≤ 300 , 1 ≤ m ≤ 300 ) n,m\ \ (2\leq n\leq 300,1\leq m\leq 300) n,m  (2n300,1m300).第二行输入 n n n个整数 a 1 , ⋯   , a n    ( 0 ≤ a i ≤ 1 e 4 ) a_1,\cdots,a_n\ \ (0\leq a_i\leq 1\mathrm{e}4) a1,,an  (0ai1e4).接下来 m m m行每行输入两个整数 u i , v i    ( 1 ≤ u i , v i ≤ n ) u_i,v_i\ \ (1\leq u_i,v_i\leq n) ui,vi  (1ui,vin),表示第 i i i条边连接节点 u u u v v v.注意可能存在重边和自环.数据保证所有测试数据的 n n n之和、 m m m之和不超过 3000 3000 3000.

对每组测试数据,第一行输出一个整数,表示 D D D的最小值.第二行输出一个长度为 m m m 0 − 1 0-1 01 s 1 ⋯   , s m s_1\cdots,s_m s1,sm,表示边 i    ( 1 ≤ i ≤ m ) i\ \ (1\leq i\leq m) i  (1im)的方向,其中 s i = 0 s_i=0 si=0表示边 i i i从节点 u i u_i ui指向 v i v_i vi, s i = 1 s_i=1 si=1表示边 i i i从节点 v i v_i vi指向 u i u_i ui.若有多组解,输出任一组.

思路 By : kaka0010

由节点的限制和数据范围易想到网络流,考虑如何建图.因限制在节点上而不在边上,而连接节点 u u u v v v的边 < u , v > <u,v>可选择为节点 u u u贡献 1 1 1点入度还是为节点 v v v贡献 1 1 1点入度,考虑拆边,即将边 < u , v > <u,v>变成一个虚节点 P P P,分别向节点 u u u v v v连边,容量为 1 1 1、费用为 0 0 0.

对每个节点 i i i,显然可向汇点连一条容量为 a [ i ] a[i] a[i]、费用为 0 0 0的边,它表示 a [ i ] a[i] a[i]是节点 i i i的免费流量.实际流量可超过 a [ i ] a[i] a[i],此时从节点 i i i向汇点连一条容量为 I N F INF INF、费用为 1 1 1的边,表示超出 a [ i ] a[i] a[i]的流量需 1 1 1点花费,最终答案即达到最大流的最小花费.

对输出方案,只需考虑残量网络中容量减为 0 0 0的边的方向与输入数据中边的方向是否相同即可.

代码 -> 2021CCPC四川省赛题解-F(拆边+费用流)

namespace SPFA_Cost_Flow {
	static const int MAXN = 3005, MAXM = 3e5 + 10;  // 边开两倍
	int n, m, s, t;  // 点数、边数、源点、汇点
	int head[MAXN], edge[MAXM], capa[MAXM], cost[MAXM], nxt[MAXM], idx;  // capa[i]表示边i的容量,cost[i]表示边i的费用
	int min_capa[MAXN];  // min_capa[i]表示到节点i的所有边的容量的最小值
	int dis[MAXN];  // dis[i]表示源点到节点i的最短路
	int pre[MAXN];  // pre[i]表示节点i的前驱边的编号
	bool state[MAXN];  // SPFA中记录每个节点是否在队列中

	void add(int a, int b, int c, int d) {  // 建边a->b,容量为c,费用为d
		edge[idx] = b, capa[idx] = c, cost[idx] = d, nxt[idx] = head[a], head[a] = idx++;  // 正向边
		edge[idx] = a, capa[idx] = 0, cost[idx] = -d, nxt[idx] = head[b], head[b] = idx++;  // 反向边,流量初始为0,费用为正向边的相反数
	}

	bool spfa() {  // 返回是否找到增广路
		memset(dis, INF, so(dis));
		memset(min_capa, 0, so(min_capa));

		qi que;
		que.push(s);
		dis[s] = 0, min_capa[s] = INF;  // 源点处的流量无限制

		while (que.size()) {
			int u = que.front(); que.pop();
			state[u] = false;

			for (int i = head[u]; ~i; i = nxt[i]) {
				int v = edge[i];
				if (capa[i] && dis[v] > dis[u] + cost[i]) {  // 边还有容量
					dis[v] = dis[u] + cost[i];
					pre[v] = i;  // 记录前驱边
					min_capa[v] = min(min_capa[u], capa[i]);

					if (!state[v]) {
						que.push(v);
						state[v] = true;
					}
				}
			}
		}
		return min_capa[t];  // 汇点的流量非零即可以到达汇点,亦即存在增广路
	}

	pll EK() {  // first为最大流、second为最小费用
		pll res(0, 0);
		while (spfa()) {  // 当前还有增广路
			int tmp = min_capa[t];
			res.first += tmp, res.second += (ll)tmp * dis[t];
			for (int i = t; i != s; i = edge[pre[i] ^ 1])
				capa[pre[i]] -= tmp, capa[pre[i] ^ 1] += tmp;  // 正向边减,反向边加
		}
		return res;
	}
}
using namespace SPFA_Cost_Flow;

pii edges[MAXM];

void solve() {
	memset(head, -1, so(head));

	cin >> n >> m;

	s = n + m + 1, t = n + m + 2;  // 源点、汇点
	for (int i = 1; i <= n; i++) {
		int a; cin >> a;
		add(i, t, a, 0), add(i, t, INF, 1);  // 免费流量、额外流量
	}
	for (int i = 1; i <= m; i++) {
		cin >> edges[i].first >> edges[i].second;
		add(s, n + i, 1, 0);  // 拆边的虚节点
		add(n + i, edges[i].first, 1, 0), add(n + i, edges[i].second, 1, 0);  // 虚点向u,v连边
	}

	cout << EK().second << endl;
	for (int u = n + 1; u <= n + m; u++) {  // 枚举拆边的虚节点
		for (int i = head[u]; ~i; i = nxt[i]) {
			int v = edge[i];
			if (v > n + m) continue;  // 不是原图中的节点

			if (!capa[i]) {
				if (v == edges[u - n].second) cout << 0;
				else cout << 1;
				break;
			}
		}
	}
	cout << endl;
}

int main() {
	CaseT  // 单测时注释掉该行
	solve();
}


你可能感兴趣的:(CCPC,ICPC补题,算法)