• AGC030 简要题解


    A - Poisonous Cookies

    题意

    • (A)个能解毒的普通饼干,(B)个能解毒的美味饼干,(C)个有毒的美味饼干,求最多能吃多少个美味饼干,每次吃完有毒的饼干后要解毒后才能继续吃。

    题解

    输出( ext{min(A + B + 1, C) + B})即可。

    代码

    #include <bits/stdc++.h>
    
    using namespace std;
     
    int main() {
     
    	int a, b, c; 
     
    	cin >> a >> b >> c; 
    	printf("%d
    ", min(a + b + 1, c) + b);
     
    	return 0;
    }
    

    B - Tree Burning

    题意

    • 在一个长为(L)的环上有(n)个关键点,求访问完所有关键点走的路径长度最大值。

    题解

    可以发现最优解一定是先往一个方向走,然后每次变换方向,那么枚举往一个方向走到第几个点,取最大值即可。

    代码

    #include <bits/stdc++.h>
    
    using namespace std; 
    
    const int N = 2e5 + 9;
     
    ll n, L, ans, a[N], b[N], Sa[N], Sb[N];
     
    ll Qa(int l, int r) { return Sa[r] - Sa[l - 1]; }
    ll Qb(int l, int r) { return Sb[r] - Sb[l - 1]; }
     
    void Solve() { 
    	for (int i = n; i; -- i) { 
    		int blen = (n - i) >> 1, alen = n - i - blen;
    		chkmax(ans, ((n - i) & 1 ? b[n - blen] : a[n - alen]) + (Qb(n - blen + 1, n) + Qa(i, i + alen - 1)) * 2);
    	}
    }
     
    int main() {
     
    	scanf("%d%d", &L, &n);
    	for (int i = 1; i <= n; ++ i)
    		scanf("%d", &a[i]);
     
    	for (int i = 1; i <= n; ++ i) { 
    		b[i] = L - a[i];
    		Sa[i] = Sa[i - 1] + a[i];
    		Sb[i] = Sb[i - 1] + b[i];
    	}
    	Solve();
     
    	for (int i = 1; i <= n; ++ i) 
    		a[i] = b[n - i + 1];
     
    	for (int i = 1; i <= n; ++ i) {
    		b[i] = L - a[i];
    		Sa[i] = Sa[i - 1] + a[i];
    		Sb[i] = Sb[i - 1] + b[i];
    	}
    	Solve();
     
    	cout << ans << endl;
     
    	return 0;
    }
    

    C - Coloring Torus

    题意

    • 构造一个( ext{n × n})的矩阵,满足其中出现了(k)种数,对于每种数出现的每个位置,满足这些位置上下左右出现的每种数的个数相同,(kle10^3,nle500)

    题解

    首先(kle500)的很好填,直接(n = k),每行都一样填满所有颜色即可。

    如果(k>500),我们令(n = 2lceil frac k 4 ceil),我们有个很直观的想法就是每个位置((x,y))( ext{(x + y) mod n + 1}),这个东西满足题意应该很显然,但是颜色数可能不够,我们把偶数行拿出来,让偶数行填( ext{(x + y) mod n + n + 1}),如果大于( ext{k})就不加( ext n),想一下这样为什么是对的,可以加( ext{n})的位置一定是一行连续的数,那么对于奇数行的一个数,加( m{n})的位置一定一个在下方一个在上方,左右的数是不会加的,而对于偶数行加的位置一个在左边一个在右边,上下的数是不会加的,这样就可以了。

    代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    int a[501][501];
    
    int main() {
    
    	int k;
    	scanf("%d", &k);
    
    	if (k <= 500) {
    		cout << k << endl;
    		for (int i = 1; i <= k; ++ i)
    			for (int j = 1; j <= k; ++ j)
    				printf("%d%c", i, j == k ? '
    ' : ' ' );
    		return 0;
    	}
    
    	int n = ((k + 3) / 4) << 1;
    	cout << n << endl;
    	for (int i = 1; i <= n; ++ i)
    		for (int j = 1; j <= n; ++ j) {
    			a[i][j] = (i + j) % n + (i & 1 ? 0 : n) + 1; 
    			if (a[i][j] > k) a[i][j] -= n;
    			printf("%d%c", a[i][j], j == n ? '
    ' : ' ');
    		}
    
    	return 0;
    }
    

    D - Inversion Sum

    题意

    • 给你一个长为(n)的数列,(q)个交换两个位置的数的操作,对于每个操作是否执行所有(2^q)种情况的逆序对数的和。

    题解

    我们可以计算(q)轮操作后的期望逆序对数,乘上(2^q)即可,那么就很简单了,设( m{dp[i][j][k]})(i)轮操作后(j<k)的概率,那么每次操作只会修改(O(n))( m{DP})值,复杂度(O(n^2+nq))

    代码

    #include <bits/stdc++.h>
    
    #define For(i, a, b) for (int i = a; i <= b; ++ i)
    
    using namespace std;
    
    const int N = 3000 + 3;
    const int mod = 1e9 + 7;
    
    int qpow(int a, int x) { 
    	int ret = 1; 
    	while (x) {
    		if (x & 1) ret = ret * (ll)a % mod;
    		x >>= 1, a = a * (ll)a % mod;
    	}
    	return ret; 
    }
    
    int f[N][N], a[N], ans;
    
    int main() {
    	int n, q, x, y;
    
    	scanf("%d%d", &n, &q);
    	For(i, 1, n) scanf("%d", &a[i]);
    	For(i, 1, n) For(j, 1, n) f[i][j] = a[i] < a[j];
    
    	For(cas, 1, q) { 
    		read(x), read(y);
    		f[x][y] = f[y][x] = 500000004ll * (f[x][y] + f[y][x]) % mod;
    		For(i, 1, n) if (i ^ x && i ^ y) {
    			f[x][i] = f[y][i] = 500000004ll * (f[x][i] + f[y][i]) % mod;
    			f[i][x] = f[i][y] = 500000004ll * (f[i][x] + f[i][y]) % mod;
    		}
    	}
    
    	For(i, 1, n) For(j, i + 1, n) (ans += f[j][i]) %= mod;
    	cout << 1ll * ans * qpow(2, q) % mod << endl;
    
    	return 0;
    }
    

    E - Less than 3

    题意

    • 给你两个(01)( m{S, T}),每次可以把一位取反,求( m{S-T})的最小操作次数,同时需要保证操作过程种不会出现连续的三个字符。

    题解

    这个东西不太好做,考虑转换模型,我们在(01)之间加一块红色的隔板,(10)间加一块蓝色的隔板,同时串首尾都有无穷多块隔板。

    那么我们发现每次取反一个位置相当于把隔板移动了一格,同时隔板间是不能跨越的,隔板匹配的一个方案对应着一个合法的操作方案。

    如果已经知道了隔板位置的话,因为隔板不能跨越,我们知道操作次数的下界是每对对应隔板的坐标差,同时我们可以构造一种方法达到这个下界,大概就是每次要移动隔板的时候移动中间一段连续的两边的都不动,所以可以(O(n))求出操作次数,那么直接枚举( m S)串前有多少个隔板,取最小值即可,复杂度(O(n^2))

    代码

    #include <bits/stdc++.h>
    
    #define mp make_pair
    #define For(i, a, b) for (int i = a; i <= b; ++ i)
    
    using namespace std;
    
    const int N = 5e3 + 7;
    
    pair<int, int> a[N], b[N];
    
    char s[N], t[N];
    
    int n, ans = inf, c, d;
    
    void Solve() { 
    	For(i, 0, n) { 
    		if ((a[1].y + b[1].y + i) & 1) continue;
    		int res = 0, now = 1;
    		while (now <= c) { 
    			int ps = a[now].x, pt = now + i <= d ? b[now + i].x : n + 1;
    			res += abs(pt - ps), ++ now;
    		}
    		while (now + i <= d) res += n + 1 - b[now + i].x, ++ now;
    		For(j, 1, i) {
    			int pt = j <= d ? b[j].x : n + 1; 
    			res += pt - 1; 
    		}
    		chkmin(ans, res);
    	}
    }
    
    int main() {
    
    	scanf("%d%s%s", &n, s + 1, t + 1);
    
    	For(i, 1, n) { 
    		if (s[i] != s[i - 1]) 
    			a[++ c] = mp(i, s[i] ^ 48);
    		if (t[i] != t[i - 1]) 
    			b[++ d] = mp(i, t[i] ^ 48);
    	}
    	Solve(), swap(c, d), swap(a, b), Solve();
    
    	printf("%d
    ", ans);
    
    	return 0;
    }
    
    

    F - Permutation and Minimum

    题意

    • 给你一个长为(2n)的排列(a)(a)的有些位置还没有填数,定义序列(b)满足(b[i]=min(a[2i],a[2i-1])),求有多少种不同的序列(a)

    题解

    先考虑(a)序列什么都没填的情况,我们可以先不考虑顺序,那么方案数就是长度为(2n)的括号序列数乘上(n!),这启示我们往括号序列的方向上思考。

    我们把(2n)个数分类,有的已经确定了就不用管,而没有确定的就只有相邻数没填或者两个数都没填的,我们分别记为特殊括号和普通括号。设(f(i,j,k))表示([i,2n])的数已经确定,其中还有(j)个特殊括号,(k)个普通括号没有匹配的方案数,我们每次考虑当前这个数作为左括号还是右括号。特别地如果当前这个数是普通左括号要匹配一个普通右括号时,先把方案数记作(1),也就是先不分配位置,最后若有(m)对普通括号匹配了,他们的位置可以任意排,所以还要乘个(m!),复杂度(O(n^3))

    代码

    #include <bits/stdc++.h>
    
    using namespace std;
    
    const int N = 300 + 7;
    const int mod = 1e9 + 7;
    
    int n, cnt;
    int f[N << 1][N][N];
    int a[N << 1], b[N << 1];
    
    int main() { 
      
    	scanf("%d", &n);
    	for (int i = 1; i <= n; ++ i) {
    		int y = i << 1, x = y - 1; 
    		scanf("%d%d", &a[x], &a[y]);
    		if (a[x] ^ -1) { 
    			if (a[y] == -1) b[a[x]] = -1;
    			else b[a[x]] = b[a[y]] = 1; 
    		} else {
    			if (a[y] ^ -1) b[a[y]] = -1;
    			else ++ cnt;
    		}
    	}
    
    	// f[i][j][k] [i, 2n] is ok, j special ), k normal )。
    
    	f[n << 1 | 1][0][0] = 1; 
    	for (int i = n << 1; i; -- i) { 
    		if (b[i] == 1) memcpy(f[i], f[i + 1], sizeof(f[i]));
    		else { 
    			for (int j = 0; j <= n; ++ j)
    				for (int k = 0; k <= n; ++ k) {
    					if (b[i] ^ -1) { 
    						(f[i][j][k + 1] += f[i + 1][j][k]) %= mod;
    						if (j) (f[i][j - 1][k] += 1ll * j * f[i + 1][j][k] % mod) %= mod;
    						if (k) (f[i][j][k - 1] += f[i + 1][j][k] % mod) %= mod;
    					} else { 
    						(f[i][j + 1][k] += f[i + 1][j][k]) %= mod;
    						if (k) (f[i][j][k - 1] += f[i + 1][j][k]) %= mod;
    					}
    				}
    		}
    	}
    
    	for (int i = 1; i <= cnt; ++ i) 
    		f[1][0][0] = 1ll * f[1][0][0] * i % mod;
    	printf("%d
    ", f[1][0][0]);
    
    	return 0;
    }
    
  • 相关阅读:
    多态及鸭子类型
    面向对象三大特性之——继承
    类的组合
    类的成员和命名空间
    JAVA中常用的类
    JAVA自学笔记(5)
    JAVA自学笔记(4)
    JAVA自学笔记(3)
    JAVA自学笔记(2)
    JAVA自学笔记(1)
  • 原文地址:https://www.cnblogs.com/brunch/p/10419956.html
Copyright © 2020-2023  润新知