Educational Codeforces Round 81 (Rated for Div. 2)
A. Display The Number
给\(n\)个区域,问最大能构成的数字是多少?
- \(n\)为奇数:\(7111...\)
- \(n\)为偶数:\(1111...\)
#include<bits/stdc++.h> #define mes(a, b) memset(a, b, sizeof a) using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 2e5+10; const ll mod = 1e9+7; int n, m, T; int main(){ scanf("%d", &T); while(T--){ scanf("%d", &n); if(n % 2 == 1 ) printf("7"); else printf("1"); int len = n/2-1; for(int i = 1; i <= len; i++) printf("1"); printf("\n"); } return 0; }
B. Infinite Prefixes
给一个长度为\(n\)的字符串\(s\),字符串\(t\)是字符串\(s\)的无限循环。\(cnt_0[q]\)表示在\(0-q\)之间有多少个\(0\),\(cnt_1[q]\)表示在\(0-q\)之间有多少个\(1\)。问\(cnt_0[q]-cnt_1[q] = x\)中,\(q\)的个数。
- 首先循环一遍求出\(0-n\)位置上的值,并且\(a[n-1]\)为每次循环的变化。
- 当\(a[i] + a[n-1] * temp =x(temp\ge 0)\)则当前位置\(i\)肯定能有一次变为\(x\)。
- 如果\(a[n-1]=0 \ \&\&\ a[i] = x\),那么则无限次。
#include<bits/stdc++.h> #define mes(a, b) memset(a, b, sizeof a) using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 2e5+10; const ll mod = 1e9+7; int n, m, T; int a[maxn]; char ch[maxn]; int main(){ scanf("%d", &T); while(T--){ scanf("%d%d", &n, &m); scanf("%s", ch+1); int len = strlen(ch+1), cnt = 0; for(int i = 1; i <= len; i++){ if(ch[i] == '0') a[i] = a[i-1]+1; else a[i] = a[i-1]-1; } int flag = 0; if(a[len] == 0){ for(int i = 1; i <= len; i++){ if(a[i] == m){ printf("-1\n"); flag = 1; break; } } if(!flag) printf("0\n"); continue; } int ans = 0; for(int i = 1; i <= len; i++){ int x = (m-a[i])/a[len]; if(x >= 0 && x *a[len] + a[i] == m) ans++; } if(m == 0) ans++; printf("%d\n", ans); } return 0; }
C. Obtain The String
给你字符串\(s\)和字符串\(t\),每次可以从字符串\(s\)中取出一个子序列加到当前字符串\(z\)的末尾,字符串\(z\)初始为空。问最少取几次,字符串\(z\)可以变成字符串\(t\)。
记录字符串\(s\)每个位置之后最近的每个字母的位置。暴力遍历即可。
#include<bits/stdc++.h> #define mes(a, b) memset(a, b, sizeof a) using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 2e5+10; const ll mod = 1e9+7; int n, m, T; char s[maxn], t[maxn]; struct Node{ int num[30]; }a[maxn]; int main(){ scanf("%d", &T); while(T--){ scanf("%s%s", s, t); int slen = strlen(s), tlen = strlen(t); mes(a[slen].num, -1); for(int i = slen-1; i >= 0; i--){ int x = s[i] - 'a'; a[i] = a[i+1]; a[i].num[x] = i; } int ans = 0, cnt = 0; for(int i = 0; i < tlen; i++){ int x = a[cnt].num[t[i]-'a']; if(x == -1){ x = a[0].num[t[i]-'a']; if(x == -1){ ans = -1; break; } else ans++; } if(i == tlen-1) ans++; cnt = x+1; } printf("%d\n", ans); } return 0; }
D. Same GCDs
问在\(n\le x\le n+m-1\)中存在几个\(x\),\(gcd(x, m) =gcd(n, m)\)
- 令\(Gcd = gcd(n, m)\), 用唯一分解定理求出\(m/Gcd\)的质因子。
- 用容斥定理算出\(n/Gcd\le x \le (n+m-1)/Gcd\)中为\(m/Gcd\)的质因子的倍数的个数\(tmp\)
- 答案\(ans = (n+m-1)/Gcd -(n-1)/Gcd -tmp\)
(感觉自己写的很麻烦不知道有没有简单一点的做法)
#include<bits/stdc++.h> #define mes(a, b) memset(a, b, sizeof a) using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 2e5+10; const ll mod = 1e9+7; ll n, m, ans, x; int T, cnt, num[maxn], p[maxn]; vector<ll> vec; ll gcd(ll a, ll b){ return b == 0 ? a: gcd(b, a%b); } void init(){ mes(num , 0); cnt = 0; for(ll i = 2; i < maxn; i++){ if(!num[i]){ p[++cnt] = i; for(ll j = i*i; j < maxn; j+=i){ num[j] = 1; } } } } void get(ll x){ vec.clear(); for(int i = 1; i <= cnt && p[i]*p[i] <= x; i++){ if(x % p[i] == 0){ vec.push_back(p[i]); while(x%p[i] == 0) x /= p[i]; } } if(x > 1) vec.push_back(x); } ll dfs(int pre, int pos, ll num){ ll tmp; for(int i = pre; i < vec.size(); i++){ tmp = num * vec[i]; if(pos & 1) ans += x/tmp; else ans -= x/tmp; dfs(i+1, pos+1, tmp); } } int main(){ scanf("%d", &T); init(); while(T--){ scanf("%lld%lld", &n, &m); int len = sqrt(m); ll Gcd = gcd(n, m); get(m/Gcd); ans = 0; x = (n+m-1)/Gcd; dfs(0, 1, 1); ll tmp = x - ans; x = (n-1)/Gcd; ans = 0; dfs(0, 1, 1); tmp -= (x-ans); printf("%lld\n", tmp); } return 0; }
E. Permutation Separation
把一个长度为\(n\)数组\(p\),分为两个非空集合\(p_1、p_2、p_3... p_k\)和\(p_{k+1}、p_{k+2}...p_n\),可以随意移动第一个集合的元素到第二个集合,也可以移动第二个集合的元素到第二个集合。移动元素\(p_i\)所需要的花费为\(a_i\)。问让第一个集合的元素小于任意一个第二个集合的元素(其中一个集合为空集也可满足条件),最小的花费是多少?
首先能想到遍历所有的位置\(k\),从而找出最优解。那么要解决的问题是怎么在每次遍历的过程中快速的找到当前位置\(k\)的最优解。
如果当前位置中,要让第一个集合小于数字\(x\),第二个集合数字大于\(x\),则花费为第一个集合大于\(x\)的数的花费+第二个集合小于\(x\)的数的花费。因为中间数字为\(x\),那么\(x\)在哪个集合都是成立的,则当前不用管数字\(x\)的花费。
反过来说,如果数字\(x\)在第一个集合,而当前枚举的中间的数小于 \(x\)那么数字\(x\)肯定要移动到第二个集合。如果数字\(x\)在第二个集合,而当前枚举的中间的数大于 \(x\)那么数字\(x\)肯定要移动到第一个集合。
那么数字\(x\)在第一个集合的时候会对中间数为\([1,x-1]\)产生贡献。数字\(x\)在第二个集合的时候会对中间数为\([x+1,n]\)产生贡献。
- 可以用线段树来维护枚举每个位置为中间数的最小值。
- 当\(x\)在第一个集合,把\([1, x-1]\)的范围都加上\(a_i\)
- 当\(x\)在第二个集合,把\([x+1, n]\)的范围都加上\(a_i\)
- 遍历每个位置\(k\)找出最小值
#include<bits/stdc++.h> #define mes(a, b) memset(a, b, sizeof a) using namespace std; typedef long long ll; typedef unsigned long long ull; const int maxn = 2e5+10; const ll mod = 1e9+7; int n; ll lazy[maxn<<2]; ll node[maxn<<2], a[maxn]; int p[maxn]; void pushdown(int rt){ if(lazy[rt]){ node[rt<<1] += lazy[rt]; node[rt<<1|1] += lazy[rt]; lazy[rt<<1] += lazy[rt]; lazy[rt<<1|1] += lazy[rt]; lazy[rt] = 0; } } void update(int L, int R, ll c, int l, int r, int rt){ if(R < L) return; if(L <= l && r <= R){ node[rt] += c; lazy[rt] += c; return; } pushdown(rt); int mid = l+r>>1; if(L <= mid) update(L, R, c, l, mid, rt<<1); if(R > mid) update(L, R, c, mid+1, r, rt<<1|1); node[rt] = min(node[rt<<1], node[rt<<1|1]); } int main(){ scanf("%d", &n); mes(node, 0); mes(lazy, 0); for(int i = 1; i <= n; i++) scanf("%d", &p[i]); for(int i = 1; i <= n; i++){ scanf("%lld",&a[i]); update(p[i]+1, n, a[i], 1, n, 1); } ll ans = min(a[1], a[n]); for(int i = 1; i < n; i++){ update(p[i]+1, n, -1*a[i], 1, n, 1); update(1, p[i]-1, a[i], 1, n, 1); ans = min(node[1], ans); } printf("%lld\n", ans); return 0; }
来源:https://www.cnblogs.com/zhuyou/p/12242631.html