Educational Codeforces Round 81 (Rated for Div. 2)
A. Display The Number
给\(n\)个区域,问最大能构成的数字是多少?
- \(n\)为奇数:\(7111...\)
- \(n\)为偶数:\(1111...\)
#include
#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
#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
#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
#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 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
#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;
}