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("
");
}
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(tempge 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
");
flag = 1;
break;
}
}
if(!flag)
printf("0
");
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
", 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
", ans);
}
return 0;
}
D. Same GCDs
问在(nle xle n+m-1)中存在几个(x),(gcd(x, m) =gcd(n, m))
- 令(Gcd = gcd(n, m)), 用唯一分解定理求出(m/Gcd)的质因子。
- 用容斥定理算出(n/Gcdle 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
", 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
", ans);
return 0;
}