• 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    代码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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    代码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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28


    H. Nihongo wa Muzukashii Desu

    题意

    在这里插入图片描述

    给定 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    思路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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94


    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94


  • 相关阅读:
    ctfhub技能树---http协议
    springboot项目经验
    D - Square Permutation-AtCoder Beginner Contest 324
    Vue3 Vite3 多环境配置 - 基于 vite 创建 vue3 全家桶项目(续篇)
    m1 安装 cocoapods
    前端面试题:基础理论整理(篇1)
    Pytorch将数据(张量)写入视频
    python之Numpy(三)
    XCTF1-web unseping
    ChatTTS 开源文本转语音模型本地部署、API使用和搭建WebUI界面(建议收藏)
  • 原文地址:https://blog.csdn.net/Hytidel/article/details/126815868