• Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020


    A

    题意

    要你构造 (n) 个数,每个数的范围是 ([1,4n]),使得其中任意两个数 (a,b) 不满足 (gcd(a,b)=1) 且不满足 (gcd(a,b)=a) 且不满足 (gcd(a,b)=b)

    题解

    一道一眼构造题。我们发现如果只取偶数的话就不可能出现 ((a,b)=1) 的情况,而又他的范围那么大,所以如果取 (2n+2,2n+4,...,4n)(n) 个数一定不会出现后两种情况。这样就构造完了。

    #include <bits/stdc++.h>
    using namespace std;
    int t;
    int n;
    int main() {
    	scanf("%d", &t);
    	while (t--) {
    		scanf("%d", &n);
    		for (int i = 4 * n; i > 4 * n - 2 * n; i -= 2) {
    			printf("%d ", i);
    		} printf("
    ");
    	}
    	return 0;
    }
    

    B

    题意

    给你两个数 (a,b) 和一个长度为 (n) 的01字符串。对于一个1的连通块你需要花费 (a) 去炸掉它并且你可以花费 (b) 去把一个0变成1,求你炸掉所有1的最小花费是多少。

    题解

    从左往右推,把两个1的连通块之间的0的个数记为 (x),如果 (x imes b<a) 的话就把这一段填成1,答案加上 (x imes b),否则则需一段新的1连通块,答案加上 (a)。注意一开始必须有一个1的连通块,即如果存在1的话答案至少为 (a)

    #include <bits/stdc++.h>
    using namespace std;
    int t;
    int a, b;
    char s[100010];
    int n;
    long long ans;
    int main() {
    	scanf("%d", &t);
    	while (t--) {
    		ans = 0;
    		scanf("%d%d", &a, &b);
    		scanf("%s", s + 1);
    		n = strlen(s + 1);
    		bool have = false;
    		int now = 0;
    		for (int i = 1; i <= n; i++) {
    			if (s[i] != s[i - 1]) {
    				if (s[i] == '1') ans += have ? min(now * b, a) : a, have = true;
    				now = 1;
    			} else now++;
    		}
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    
    

    C

    题意

    给你两个数列:({a},{b}),要你求出 (min_{Psubseteq {1,2,...,n}}{max{max_{i in P}a_i,sum_{i otin P}b_i}})

    题解

    此题不需要二分答案,直接排序加枚举即可。我们发现在枚举 (P) 的时候如果对于一个 (i otin P)(a_i le max_{i in P}a_i) 那这个 (i) 也包含进来一定更优。所以我们可以先对 (a) 排序,枚举前 (i) 个人是送外卖的,然后维护一个后缀和就可以了。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll inf = 0x3f3f3f3f3f3f3f3f;
    struct node{
    	int a, b;
    	friend bool operator < (node x, node y) {
    		return x.a < y.a;
    	}
    }Dish[200010];
    int t;
    int n;
    ll sum, ans;
    int main() {
    	scanf("%d", &t);
    	while (t--) {
    		ans = inf;
    		sum = 0;
    		scanf("%d", &n);
    		for (int i = 1; i <= n; i++) scanf("%d", &Dish[i].a);
    		for (int i = 1; i <= n; i++) scanf("%d", &Dish[i].b);
    		sort(Dish + 1, Dish + 1 + n);
    		for (int i = n; i >= 1; i--) {
    			ans = min(ans, max((ll)Dish[i].a, sum));
    			sum += Dish[i].b;
    		}
    		ans = min(ans, sum);
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    

    D

    考虑一个位置至少有几次操作是从最后到它这个位置的?如果 (a_{i-1}<a_i) 那么显然是 (a_{i}-a_{i-1}),那么 (a[i...n]) 这一段数至少减 (a_{i}-a_{i-1}),如果把所有这些要减的加在一起,比某个 (a_x) 大了那么就是NO,否则就是YES

    为什么不用从后往前做一遍?看一下这张图就懂了。

    这一类题因为是区间同时减去某数所以有一段的差分数组不变,多往差分上去想。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int inf = 0x3f3f3f3f;
    int q;
    int n;
    int a[100010];
    int now;
    int main() {
    	scanf("%d", &q); 
    	while (q--) {
    		scanf("%d", &n);
    		for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
    		now = 0;
    		bool ans = true;
    		for (int i = 1; i < n; i++) {
    			if (now > a[i]) {
    				ans = false;
    				break;
    			}
    			if (a[i] < a[i + 1]) now += a[i + 1] - a[i]; 
    		}
    		if (now > a[n]) ans = false;
    		if (ans) puts("YES");
    		else puts("NO");
    	}
    	return 0;
    }
    

    E

    挺水的一题,可惜赛时不知道F更水,然后花了挺多时间搞这题害的最后没时间写F了/kk/kk/kk。

    看到这道题让你实现的就是跳到字典序比当前大一的排列上,而字典序最大为 (2 imes 10^{10}),这对于排列的个数来说其实是很小的。保守点说,排列变化的肯定是最后 (20) 个数。那么对于前 (n-20) 个数维护一个前缀和,后 (20) 个数暴力统计就可以做第一问。对于第二个操作,根据排名求排列有个逆康托展开的算法,具体就是挨个确定每个位置是什么数,如果暴力做的话复杂度上限是 (20^3),然而到不了所以这题可以过,如果要追求效率的话可以做到 (20 imes log^2{20}) 或者 (20 imes log{20}).

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int n, q;
    ll now = 1;
    ll a[200010], cnt[200010];
    bool vis[200010];
    ll ans;
    int main() {
    	scanf("%d%d", &n, &q);
    	for (int i = 1; i <= n; i++) a[i] = i, cnt[i] = a[i] + cnt[i - 1];
    	while (q--) {
    		int opt, l, r;
    		scanf("%d", &opt);
    		if (opt == 1) {
    			scanf("%d%d", &l, &r);
    			ans = 0;
    			if (n > 20) {
    				if (l < n - 20 && r >= n - 20) {
    					ans = cnt[n - 21] - cnt[l - 1];
    					l = n - 20;
    					for (int i = l; i <= r; i++) {
    						ans += a[i];
    					}
    				} else if (r < n - 20) {
    					ans = cnt[r] - cnt[l - 1];
    				} else {
    					for (int i = l; i <= r; i++) {
    						ans += a[i];
    					}
    				}
    			} else {
    				for (int i = l; i <= r; i++) {
    					ans += a[i];
    				}
    			}
    			printf("%lld
    ", ans);
    		} else {
    			scanf("%d", &l);
    			now += l;
    			ll tmp = now;
    			ll cur = 1;
    			int pos;
    			for (pos = n; pos; pos--) {
    				if (cur >= now) {
    					break;
    				}
    				cur *= (n - pos + 1);
    			}
    			for (int i = pos + 1; i <= n; i++) vis[i] = false;
    			for (int i = pos + 1; i <= n; i++) {
    				cur /= (n - i + 1);
    				for (int j = pos + 1; j <= n; j++) {
    					if (vis[j]) continue;
    					ll num = 0;
    					for (int k = pos + 1; k < i; k++) {
    						if (a[k] < j) {
    							num++;
    						}
    					}
    					if (cur * (j - pos - num) >= now) {
    						now -= cur * (j - pos - num - 1);
    						a[i] = j;
    						vis[j] = true;
    						break;
    					}
    				}
    			}
    			now = tmp;
    		}
    	}
    	return 0;
    }
    
    

    F

    首先我们发现要想某个数加入 (b) 就得把它旁边两个中的一个删掉,然后这是两种情况,所以每次操作最多两种情况,答案肯定是 (2) 的倍数。考虑什么情况不是 (2)?记录每个数在 (a) 中出现的位置 (p),如果 (a_{p_{b_i}+1}) 是在 (b)(i) 的后面出现的数,那么一定不能删。如果 (p_{b_i}=n) 那么后面的数也没得删,前面同理。于是我们从后往前推,对于每个 (b_i) 打上标记,这样就可以 (O(1)) 判断了。

    为什么只有一开始的位置才影响答案?因为如果想要把旁边的删掉一定要把你这个位置的删掉,而你这个位置在之前有不能删,所以旁边的一定还存在。

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const ll mod = 998244353;
    const int inf = 0x3f3f3f3f;
    int T;
    int n, m;
    int a[200010], b[200010], pos[200010];
    bool vis[200010];
    ll ans;
    int main() {
    	scanf("%d", &T); 
    	while (T--) {
    		scanf("%d%d", &n, &m);
    		for (int i = 1; i <= n; i++) scanf("%d", &a[i]), pos[a[i]] = i, vis[i] = false;
    		for (int i = 1; i <= m; i++) scanf("%d", &b[i]);
    		ans = 1;
    		for (int i = m; i >= 1; i--) {
    			ll cur = 2;
    			if (pos[b[i]] == 1 || vis[a[pos[b[i]] - 1]]) cur--;
    			if (pos[b[i]] == n || vis[a[pos[b[i]] + 1]]) cur--;
    			ans = (ans * cur) % mod;
    			vis[b[i]] = true;
    		}
    		printf("%lld
    ", ans);
    	}
    	return 0;
    }
    

    总结一下这场比赛,前3题很水随便过,但是B花的时间还是多了点,以后要做到前两题15min以内做完(除非是公认的毒瘤场)。然后这个D的套路一开始竟然没有直接想到,这种区间加减的一般都要考虑差分,我还是太菜了。E应该算是马上就想到了只有最后几个会变,但是逆康托展开打了好久,还是知识点不熟悉,并且犯了低级错误wa了两发,打代码时还是不能太急。最后这个F应该比E水但是考试时没看榜导致还是先去做了E,最后没时间打F了。。。然而还是比较套路的,这种一个一个策略的问题其实可以考虑从后往前倒推。大概就这么多吧,我还是太菜了。

  • 相关阅读:
    洛谷 P2053 :[SCOI2007]修车(拆点+最小费用流)
    LightOJ
    spark简单入门
    crontab 应用
    HttpClient的使用
    build.sbt的定义格式
    Scalatra
    SBT 构建scala eclipse开发
    mysql 存在更新,不存在插入
    Flash Vector例子
  • 原文地址:https://www.cnblogs.com/zcr-blog/p/13938120.html
Copyright © 2020-2023  润新知