• 2020牛客暑期多校训练营第六场题解


    2020牛客暑期多校训练营(第六场)


    A. African Sort

    假题。对于一个大小为 (4) 的环,我们第一次选前三个数字操作会得到比标解 (frac{34}{3}) 更小的期望。

    例如:我们要将 2 3 4 1 变回 1 2 3 4 ,第一次只变换前三个数字(即 (2,3,4))。暴力枚举所有情况,显然有:

    2 3 4 1
    2 4 3 1
    3 2 4 1
    3 4 2 1
    4 2 3 1
    4 3 2 1
    

    其中,2 3 4 13 4 2 1 都未变化,仍是大小为 (4) 的环;2 4 3 13 2 4 1 有一个数字回到了原位置,变成了大小为 (3) 的环(期望为 (frac{15}{2}));4 2 3 1 有两个数字回到了原位置,变成了大小为 (2) 的环(期望为 (4));4 3 2 1 变成了两个大小为 (2) 的环。综上,列出期望计算式:

    [egin{aligned}x&=frac{x}{6} imes 2+frac{2}{6} imesfrac{15}{2}+frac{1}{6} imes 4+frac{1}{6} imes8+3 \x&=frac{45}{4}<frac{34}{3}end{aligned} ]

    B. Binary Vector

    找规律发现本题公式为:

    [egin{aligned}f_n=prod^n_{i=1}frac{2^i-1}{2^i}end{aligned} ]

    由于要求的范围是 (2e7) ,并且要求异或前缀和,因此 (O(n)) 预处理出每一项 (f_i) ,然后做一个异或前缀和。

    #include <bits/stdc++.h>
    #define ll long long
    using namespace std;
    const ll mod = 1e9 + 7;
    const int top = 2e7;
    int fac[top + 10], two[top + 10], inv[top + 10], ans[top + 10];
    
    ll qpow(ll a, ll b) {
    	ll res = 1;
    	for (; b; b >>= 1) {
    		if (b & 1) res = res * a % mod;
    		a = a * a % mod;
    	}
    	return res;
    }
    
    void init() {
    	fac[1] = 1;
    	ll base = 2ll;
    	two[1] = 2;
    	for (int i = 2; i <= top; ++i) {
    		base = (base * 2ll) % mod;
    		fac[i] = 1ll * fac[i - 1] * (base - 1ll) % mod;
    		two[i] = base;
    	}
    	inv[top] = qpow(qpow(2ll, 1ll * top * (top + 1ll) / 2ll), mod - 2ll);
    	for (int i = top - 1; i; --i)
    		inv[i] = 1ll * inv[i + 1] * two[i + 1] % mod;
    	ans[0] = 0;
    	for (int i = 1; i <= top; ++i) {
    		ll tmp = 1ll * inv[i] * fac[i] % mod;
    		ans[i] = (ans[i - 1] ^ tmp);
    	}
    }
    
    int main() {
    	ios::sync_with_stdio(false);
    	cin.tie(nullptr); cout.tie(nullptr);
    	init();
    	int T; cin >> T;
    	while (T--) {
    		ll n; cin >> n;
    		cout << ans[n] << '
    ';
    	}
    	return 0;
    }
    

    C. Combination of Physics and Maths

    题意:选择矩阵的子矩阵,使得子矩阵和除子矩阵最底层的和值最大。
    只要枚举每位当作底部元素,上方所有元素当作子矩阵即可,复杂度(O(n*m))

    #include<bits/stdc++.h>
    #define ll long long
    #define maxn 100010
    using namespace std;
    ll a[210][210];
    
    int main() {
    	int t;
    	scanf("%d", &t);
    	while (t--) {
    		int n, m;
    		scanf("%d%d", &n, &m);
    		for (int i = 1; i <= n; i++) {
    			for (int j = 1; j <= m; j++) scanf("%lld", &a[i][j]);
    		}
    		double ans = 0;
    		for (int i = 1; i <= m; i++) {
    			ll sum = 0;
    			for (int j = 1; j <= n; j++) {
    				sum += a[j][i];
    				ans = max(ans, sum * 1.0 / a[j][i]);
    			}
    		}
    		printf("%.10lf
    ", ans);
    	}
    	return 0;
    }
    

    E. Easy Construction

    首先发现:由于我们的构造需要对任意 (iin [1,n]) 都存在一个连续子序列使得其字段和等于 (kmod n) ,因此这个 (k) 必须满足 (frac{n(n+1)}{2}mod{n} = k)

    然后我们分奇偶构造,偶数时构造形如:n, k, 1, n - 1, 2, n - 2, ... 的数列(实际上,当 (n) 为偶数时 (k=frac{n}{2}),因此该构造成立);奇数时构造形如:n, 1, n - 1, 2, n - 2, ... 的数列(因为 (n) 为奇数时,(frac{n(n+1)}{2}mod{n} = 0)) 。

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    
    int main() {
    	ios::sync_with_stdio(false);
    	cin.tie(nullptr); cout.tie(nullptr);
    	long long n, k;
    	cin >> n >> k;
    	vector<ll> ans;
    	ll res = ((n + 1ll) * n / 2ll) % n;
    	if (res != k) cout << -1;
    	else if (!(n & 1)) {
    		ans.push_back(n);
    		ans.push_back(k);
    		for (int i = 1; i < n / 2; ++i) {
    			ans.push_back(i);
    			ans.push_back(n - i);
    		}
    		for (auto i : ans) cout << i << ' ';
    	}
    	else {
    		for (int i = 0; i < n; i++) {
    			if (i % 2 == 0) cout << n - i / 2 << ' ';
    			else cout << (i + 1) / 2 << ' ';
    		}
    	}
    	return 0;
    }
    

    G. Grid Coloring

    先按照如图所示的方法给网格图上的边标号:

    我们先考虑不可能构造的情况,再给出构造方案。不能构造的情况有:

    1. (n=1)(k=1)
    2. (2n(n+1)mod{k} eq 0) (其中 (2n(n+1)) 是图形的总边数)。

    构造方案是:自顶向下分别给边按照 (1)(n) 的顺序标号(如上图),然后我们将颜色 (1)(k) 按照顺序轮流放到这些边上。(序号为 (1) 的边放颜色 (1) ,序号为 (2) 的边放颜色 (2) …… 序号为 (k) 的边放颜色 (k) ,序号为 (k+1) 的边放 (1) ,序号为 (k+2) 的边放 (2) ……)

    然后,我们证明这样构造的正确性:

    • 对于一个 (1 imes 1) 的小环(如图中橙色框),它的两条纵边必定是相邻的序号,因此染的颜色必定不同;
    • 对于任意一个 (l imes (l+c), c>0) 的环(如图中绿色框是一个 (1 imes 2) 的环),它的横向边必定有相邻序号,因此染的颜色不同;同理可以证明所有横向边均不同色。
    • 对于任意一个 ((l+c) imes l, c>0) 的环(如图中紫色框是一个 (2 imes 1) 的环),如果其横向边长 (=1) ,那么它就有相邻的纵向边;如果其横向边长 (>1) ,那么它就有相邻横向边。综上所述,任意一个环以及任意横边均不同色,因此我们只需要分析纵边是否同色。

    我们最后证明,任意两条相邻纵向边必定不同色。观察上图,任意两条相邻纵向边的序号之差必定是 (2n+1) (图中所示的 (n+1, 3n+2, 5n+3) 均是相邻纵向边),于是我们只需要证明 (gcd(k,2n+1)=1) ,即 (2n+1) 不能是一个循环节((k))的倍数。由于 (k|2n(n+1)) ,我们直接证明 (gcd(2n(n+1),2n+1)=1)

    [egin{aligned}&gcd(2n(n+1),2n+1) \Leftrightarrow &gcd(2n(n+1)-(2n+1)n,2n+1) \Leftrightarrow &gcd(n, 2n+1 - n) \Leftrightarrow &gcd(n, n+1) = 1end{aligned} ]

    #include<bits/stdc++.h>
    using namespace std;
    int ans1[210][210], ans2[210][210];
    
    int main() {
    	int t;
    	scanf("%d", &t);
    	while (t--) {
    		int n, k;
    		scanf("%d%d", &n, &k);
    		int sum = 2 * n * (n + 1);
    		if (n == 1 || sum % k || k == 1) {
    			printf("-1
    ");
    			continue;
    		}
    		int now = 0;
    		for (int i = 0; i < n; i++) {
    			for (int j = 0; j < n; j++) {
    				now = now % k + 1;
    				ans1[i][j] = now;
    			}
    			for (int j = 0; j < n + 1; j++) {
    				now = now % k + 1;
    				ans2[j][i] = now;
    			}
    		}
    		for (int j = 0; j < n; j++) {
    			now = now % k + 1;
    			ans1[n][j] = now;
    		}
    		for (int i = 0; i < n + 1; i++) {
    			for (int j = 0; j < n; j++) printf("%d ", ans1[i][j]);
    			printf("
    ");
    		}
    		for (int i = 0; i < n + 1; i++) {
    			for (int j = 0; j < n; j++) printf("%d ", ans2[i][j]);
    			printf("
    ");
    		}
    	}
    	return 0;
    }
    

    H. Harmony Pairs

    数位 (DP)

    (dp[pos][sub][euq]) 前三位分别表示第 (pos-1) 位,前面(pos-1)位 数位和的差值,前面几位是否相等

    数位 (dp) 每层暴力枚举 (a),(b) 下一位上的数字

    • 若前面的几位相等,则该位上要求 (aleq b)
    • 否则 (a,b) 取任意值

    然后记录 (a、b) 位数和之差

    若递归到最后 (sub>0) 则可取,否则不可取

    时间复杂度为 (O(n^2*10^3))

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int maxn = 105;
    const LL mod = 1e9 + 7;
    char s[maxn];
    int len, a[maxn];
    
    LL res[maxn][2005][2][2][2][2][2];
    LL dfs2(int pos, int sub, bool euq, bool lead1, bool limit1, bool lead2, bool limit2) {
    	if (pos > len) {
    		if (euq) return 0;
    		return sub > 0;
    	}
    	if (res[pos][sub + 1000][euq][lead1][limit1][lead2][limit2] != -1)
    		return res[pos][sub + 1000][euq][lead1][limit1][lead2][limit2];
    
    	LL cnt = 0;
    	int top1 = limit1 ? a[pos] : 9;
    	int top2 = limit2 ? a[pos] : 9;
    	bool lead, limit;
    	for (int i = 0; i <= top1; i++) {
    		if ((!i) && lead1) lead = true;
    		else lead = false;
    		limit = (i == top1 && limit1);
    		if (!euq) {
    			for (int j = 0; j <= top2 && j < i; j++) {
    				if (!j && lead2)
    					cnt = cnt + dfs2(pos + 1, sub + i - j, euq && (i == j), lead, limit, true, j == top2 && limit2);
    				else
    					cnt = cnt + dfs2(pos + 1, sub + i - j, euq && (i == j), lead, limit, false, j == top2 && limit2);
    				cnt %= mod;
    			}
    		}
    		for (int j = i; j <= top2; j++) {
    			if (!j && lead2)
    				cnt = cnt + dfs2(pos + 1, sub + i - j, euq && (i == j), lead, limit, true, j == top2 && limit2);
    			else
    				cnt = cnt + dfs2(pos + 1, sub + i - j, euq && (i == j), lead, limit, false, j == top2 && limit2);
    			cnt %= mod;
    		}
    	}
    	res[pos][sub + 1000][euq][lead1][limit1][lead2][limit2] = cnt;
    	return cnt;
    }
    
    void doit() {
    	len = strlen(s + 1);
    	for (int i = 1; i <= len; i++)
    		a[i] = s[i] - '0';
    	memset(res, -1, sizeof(res));
    	cout << dfs2(1, 0, true, true, true, true, true) << '
    ';
    }
    
    int main() {
    	scanf("%s", s + 1);
    	doit();
    	return 0;
    }
    

    J. Josephus Transform

    因为约瑟夫环每次变换只与位置有关,可以看成

    for (int i = 1; i <= n; i++)
        a[p[i]] = a[i];
    

    (p) 数组是全排列,那么就与上一次的全排列迭代相同。计算出每一位所在的环,可以很轻松得到每一位的循环节。

    约瑟夫环的位置变换可以通过权值树状数组或者线段树得到:

    删除第 (x) 个数后,下 (k) 个数通过二分得到(可以直接套二分,也可以树上二分)。

    #include <bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5 + 5;
    int a[maxn], h[maxn], p[maxn], v[maxn];
    bool vis[maxn];
    int cnt, g = 0;
    vector<int> E[maxn];
    void dfs(int now) {
    	vis[now] = true;
    	h[now] = g;
    	p[now] = cnt++;
    	E[g].push_back(a[now]);
    	if (!vis[v[now]]) dfs(v[now]);
    }
    int res[maxn];
    int tree[maxn], limit;
    inline int lowbit(int x) {
    	return x & -x;
    }
    void update(int x, int v) {
    	for (int i = x; i <= limit; i += lowbit(i))
    		tree[i] += v;
    }
    int query(int x) {
    	int res = 0;
    	for (int i = x; i; i -= lowbit(i))
    		res += tree[i];
    	return res;
    }
    
    void empty(vector<int>& a) {
    	vector<int> b;
    	swap(a, b);
    }
    
    int main() {
    	int n, q;
    	scanf("%d%d", &n, &q);
    	limit = n;
    	for (int i = 1; i <= n; i++) a[i] = i;
    
    	while (q--) {
    		int k, x;
    		scanf("%d%d", &k, &x);
    
    		for (int i = 1; i <= n; i++) update(i, 1);
    		int now = 0;
    		for (int i = 1; i <= n; i++) {
    			int least = query(n) - query(now);
    			int m = k;
    			int left, right, mid, ans;
    			if (least >= k) left = now + 1, right = n;
    			else {
    				now = 0;
    				m -= least;
    				m %= (n - i + 1);
    				if (!m) m = n - i + 1;
    				left = now + 1, right = n;
    			}
    			while (left <= right) {
    				mid = (left + right) >> 1;
    				if (query(mid) - query(now) >= m) {
    					right = mid - 1;
    					ans = mid;
    				}
    				else left = mid + 1;
    			}
    			now = ans;
    			v[i] = now;
    			update(now, -1);
    		}
    
    		g = 0;
    		fill(vis, vis + 1 + n, false);
    		for (int i = 1; i <= n; i++) {
    			if (!vis[i]) {
    				cnt = 0;
    				g++;
    				dfs(i);
    			}
    		}
    
    		for (int i = 1; i <= n; i++)
    			a[i] = E[h[i]][(p[i] + x) % E[h[i]].size()];
    		for (int i = 1; i <= g; i++) empty(E[i]);
    	}
    	for (int i = 1; i <= n; i++) printf("%d ", a[i]);
    	printf("
    ");
    	return 0;
    }
    

    K. K-Bag

    首先正向遍历每个数,判断该数之前能否组成一个带前缀的K-Bag数组,然后添加断点。如果该数前k的位置为断点,且前k个数正好组成一个排列,便可添加断点。
    然后反向处理带后缀的断点。如果正向断点和反向断点重合,便证明答案正确。

    #include<bits/stdc++.h>
    #define ll long long
    #define maxn 500010
    using namespace std;
    int a[maxn], p1[maxn], p2[maxn];
    int main() {
    	int t;
    	scanf("%d", &t);
    	while (t--) {
    		int n, k;
    		scanf("%d%d", &n, &k);
    		for (int i = 0; i <= n; i++) p1[i] = p2[i] = 0;
    		for (int i = 0; i < n; i++) scanf("%d", &a[i]);
    		unordered_map<int, int>m1, m2;
    		int sum = 0;
    		for (int i = 0; i < n; i++) {
    			int x = m1[a[i]];
    			if (i < k) {
    				m1[a[i]]++;
    				if (x == 0) sum++;
    				else if (x == 1) sum--;
    				if (sum == i + 1) p1[i] = 1;
    			}
    			else {
    				m1[a[i]]++;
    				if (x == 0) sum++;
    				else if (x == 1) sum--;
    				int y = m1[a[i - k]];
    				m1[a[i - k]]--;
    				if (y == 1) sum--;
    				else if (y == 2) sum++;
    				if (sum == k && p1[i - k]) p1[i] = 1;
    			}
    		}
    		int fl = 0;
    		sum = 0;
    		for (int i = 0; i < n; i++) {
    			if (i < k) {
    				int x = m2[a[n - 1 - i]];
    				m2[a[n - 1 - i]]++;
    				if (x == 0) sum++;
    				else if (x == 1) sum--;
    				if (sum == i + 1) p2[i] = 1;
    			}
    			else {
    				int x = m2[a[n - 1 - i]];
    				m2[a[n - 1 - i]]++;
    				if (x == 0) sum++;
    				else if (x == 1) sum--;
    				int y = m2[a[n - 1 - i + k]];
    				m2[a[n - 1 - i + k]]--;
    				if (y == 1) sum--;
    				else if (y == 2) sum++;
    				if (sum == k && p2[i - k]) p2[i] = 1;
    			}
    			if (p2[i] && p1[n - 2 - i]) {
    				fl = 1;
    				break;
    			}
    		}
    		if (p1[n - 1] || p2[n - 1]) fl = 1;
    		if (fl) printf("YES
    ");
    		else printf("NO
    ");
    	}
    	return 0;
    }
    
  • 相关阅读:
    【★】KMP算法完整教程
    【★】KMP算法完整教程
    算法之【牛顿迭代法】
    算法之【牛顿迭代法】
    【★】Web精彩实战之
    【★】Web精彩实战之
    ★RFC标准库_目录链接
    ★RFC标准库_目录链接
    ★教师工资为什么这么低?/整理
    ★教师工资为什么这么低?/整理
  • 原文地址:https://www.cnblogs.com/st1vdy/p/13387806.html
Copyright © 2020-2023  润新知