前言
势必要拿下的一场比赛,最后结果也算如愿。
Standings:300
重新回到蓝名了,也完成了之前 “ 早日在比赛切掉 6 题 ” 的期望。
题目链接:Dashboard - Codeforces Round 962 (Div. 3) - Codeforces
A. Legs
第一次在第一分钟就切题 ~~
题意:
Farmer John 的农场只有鸡和牛,他数出动物一共有 n 条腿,问最少有多少只动物。
思路:
因为牛有四条腿,尽可能多地让牛的腿去填满 n 。
#include<cstdio>
#include<cstring>
using namespace std;
int T,n;
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d",&n);
printf("%d\n",(n + 2) / 4);
}
return 0;
}
B. Scale
题意:
给一个 01 矩阵,你需要把这个矩阵分成若干个 k * k 的子矩阵,每一个子矩阵都缩成一个单个的块,其值等于原来子矩阵内每个格的值(题目保证每个子矩阵的所有格的数字都一样)。
例如:
0 0 0 1 1 1
0 0 0 1 1 1
0 0 0 1 1 1
1 1 1 0 0 0
1 1 1 0 0 0
1 1 1 0 0 0
k = 3 时 ->
0 1
1 0
思路:
题目说得很清楚了,好像不知道该怎么讲。。。
#include<cstdio>
#include<cstring>
using namespace std;
#define N 1005
int T,n,k,a[N][N];
char s[N];
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d",&n,&k);
for (int i = 1;i <= n;++ i)
{
scanf(" %s",s + 1);
for (int j = 1;j <= n;++ j) a[i][j] = s[j] - '0';
}
for (int i = 1;i <= n / k;++ i)
{
for (int j = 1;j <= n / k;++ j) printf("%d",a[(i - 1) * k + 1][(j - 1) * k + 1]);
printf("\n");
}
}
return 0;
}
C. Sort
题意:
给两个长度为 n 的字符串 a 和 b (由小写拉丁字母组成),你需要回答 q 个询问。
询问之间是独立的,每个询问给你一个区间范围 [l,r] ,每次操作你可以任意选择一个 i ,令 ,x 可以是任意字符,求最少进行多少次操作可以让 sorted( a[l..r] ) = sorted( b[l..r] ) 。
思路:
因为对于 b 在 a 中每个缺失的字符都需要 1 次操作来填补,而我们并不关心到底是通过变化哪个 a 中的字符来满足条件。这样问题就转化为了在区间 [l,r] 内,每种字符 a 比 b 少多少个 (a 比 b 多的话记为 0),然后算总和。
由于字符种类只有 26 种,给每种字符记录出现次数的前缀和即可算区间出现数目。
比赛的时候没想那么多直接开敲,打了树状数组,虽然麻烦了点且多耗了点时间,但好在还是一遍过了。
#include<cstdio>
#include<cstring>
using namespace std;
#define N 200005
int T,n,q,t[30],now[30],tra[30][N],trb[30][N],ans;
char a[N],b[N];
int lowbit(int x) { return x & (-x) ; }
void adda(int x,int y)
{
for (int i = y;i <= n;i += lowbit(i))
++ tra[x][i];
return;
}
void addb(int x,int y)
{
for (int i = y;i <= n;i += lowbit(i))
++ trb[x][i];
return;
}
int aska(int x,int y)
{
int tmp = 0;
for (int i = y; i ;i -= lowbit(i))
tmp += tra[x][i];
return tmp;
}
int askb(int x,int y)
{
int tmp = 0;
for (int i = y; i ;i -= lowbit(i))
tmp += trb[x][i];
return tmp;
}
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d",&n,&q);
scanf("%s%s",a + 1,b + 1);
for (int i = 1;i <= 26;++ i)
for (int j = 1;j <= n;++ j) tra[i][j] = trb[i][j] = 0;
for (int i = 1;i <= n;++ i)
{
int na = a[i] - 'a' + 1;
int nb = b[i] - 'a' + 1;
adda(na,i),addb(nb,i);
}
while (q --)
{
int l,r;
scanf("%d%d",&l,&r),ans = 0;
for (int i = 1;i <= 26;++ i)
{
int ta = aska(i,r) - aska(i,l - 1);
int tb = askb(i,r) - askb(i,l - 1);
ans += (ta < tb) ? (tb - ta) : 0;
}
printf("%d\n",ans);
}
}
return 0;
}
D. Fun
题意:
给两个正整数 n 和 x ( 1 <= n,x <= 10^6 ),计算满足条件的三元组的数目。
1. ab + ac + bc <= n
2. a + b + c <= x
3. a,b,c 为正整数
注意(1,1,2)和(1,2,1)是不同的三元组。
思路:
注意到 n 比较大,直接枚举 a,b,c 肯定会超时。
条件 1 是乘法再相加,从这里入手:假设我们枚举 a ,那么根据 ab < ab + ac + bc <= n ,b的取值就是 的量级,这时候我们枚举 b <
,根据调和级数复杂度就是 O(n log n)的。枚举完 a 和 b 之后,根据条件 2 可以确定 c 的取值范围:0 < c <= x - a - b 。我们在做的时候设定 a <= b <= c ,最后根据情况乘上对应的组合数即可。
最终的时间复杂度:O(n log n)
#include<cstdio>
#include<cstring>
using namespace std;
int T,n,x;
long long ans;
int min(int x,int y) { return x < y ? x : y ; }
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d",&n,&x),ans = 0ll;
for (int a = 1;a <= x;++ a)
for (int b = a;b <= (n / a);++ b)
{
int r = min(x - (a + b),(n - a * b) / (a + b));
int l = b;
if(l > r) break;
if(a == b) ans += 1ll + 3ll * (r - l);
else ans += 3ll + 6ll * (r - l);
}
printf("%lld\n",ans);
}
return 0;
}
E. Decode
题意:
给一个长度为 n 的 01 串 s ,对每一对(l,r),统计 0 和 1 个数相等的子区间 [x,y] 数目。
思路:
显然不能直接做,我们考虑计算每一个 0 和 1 个数相等的子区间的贡献。
记一个 0 为 +1 ,一个 1 为 -1 ,满足条件的区间 [x,y] 即为区间和为 0 的区间,即第 y 位和第 x - 1 位的前缀和相等,那么这段区间的贡献就是 x * (n - y + 1) 。但是这么做还是太慢,我们发现可以统计不同前缀和的值对应的区间左端点 x 的和,这样每加入一个新的 y 作为右端点时,y 的贡献就变成了 ,这样就把时间优化到线性了。
记得取模 !!!
#include<cstdio>
#include<cstring>
using namespace std;
#define N 200005
#define mod 1000000007
int T,n,now[N * 5],pre[N * 5],cnt[N * 5];
long long ans;
char s[N];
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%s",s + 1),n = strlen(s + 1),pre[0] = N;
for (int i = -n;i <= n;++ i) now[i + N] = -1,cnt[i + N] = 0;
now[N] = 0,ans = 0ll,cnt[N] = 1;
for (int i = 1,x,y,tmp;i <= n;++ i)
{
x = (s[i] == '0') ? 1 : -1 ;
pre[i] = pre[i - 1] + x;
if(now[pre[i]] != -1) ans = (ans + 1ll * cnt[pre[i]] * (n - i + 1)) % mod;
now[pre[i]] = i;
cnt[pre[i]] = (cnt[pre[i]] + i + 1) % mod;
}
printf("%lld\n",ans);
}
return 0;
}
F. Bomb
题意:
给两个序列 a 和 b ,你的初始得分为 0 ,每一轮你可以选择一个 i 并得到 的收益,然后令
。你只有 k 轮操作机会,求最大得分。
思路:
首先贪心地想,我们每轮都需要选择当前场上最大的那个数,但暴力做显然会超时。我们可以二分最后选择的那个数(即选择的最小的数)的大小,计算把这个数选上要花多少轮,不断地靠近 k 即可。注意二分值可能对应多个相同的数,不一定会全部选完,写得时候注意细节。
#include<cstdio>
#include<cstring>
using namespace std;
#define N 200005
int T,n,a[N],b[N];
long long k,ans;
long long max(long long x,long long y) { return x > y ? x : y ; }
long long check(int x)
{
long long tmp = 0ll;
for (int i = 1;i <= n;++ i)
if(a[i] >= x)
tmp += 1ll + (a[i] - x) / b[i];
return tmp;
}
long long count(int x)
{
long long tmp = 0ll;
for (int i = 1;i <= n;++ i)
{
if(a[i] < x) continue;
if(a[i] < b[i])
{
tmp += 1ll * a[i];
continue;
}
int m = 1 + (a[i] - x) / b[i];
int fst = a[i] - (m - 1) * b[i];
int lst = a[i];
tmp += 1ll * (fst + lst) * m / 2ll;
}
return tmp;
}
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d",&n,&k),ans = 0ll;
long long l = 0ll;
long long r = 0ll;
for (int i = 1;i <= n;++ i) scanf("%d",&a[i]),r = max(r,1ll * a[i]);
for (int i = 1;i <= n;++ i) scanf("%d",&b[i]);
while (l <= r)
{
int mid = (l + r) >> 1;
if(check(mid) <= k) r = mid - 1,ans = max(ans,count(mid));
else if(check(mid + 1) < k && check(mid) > k) r = mid - 1,ans = max(ans,count(mid + 1) + 1ll * mid * (k - check(mid + 1)));
else l = mid + 1;
}
printf("%lld\n",ans);
}
return 0;
}
G. Penacony
题意:
n 个房子 n 条道路连成一个环,所有道路都是双向的但开始时都没有维修,维修之后道路才可以通行。给出 m 对关系 a,b(a < b),表示 a,b 之间一定要有一条通路,求最少维修的道路数量。
思路:
对于每一对关系,只有两种方式可以连通,一种是 a - > b ,另一种是 b - > n - > 1 - > a 。因此假设某条道路是不维修的,那么所有关系的连通方式都是确定唯一的。
我们先假设初始状态:每对关系都是 a - > b 连通,用线段树标记路径。之后枚举不维修的道路,把曾经覆盖了这条路的记录去掉,再标记这条路相对的那条路径,查询 n 条道路中有多少条是被覆盖了的即可。
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std;
#define N 200005
int T,n,m,a[N],b[N],vis[N],ans;
vector<int> c[N];
struct Tree
{
int val,tag;
}tr[N << 2];
void build(int k,int l,int r)
{
tr[k].tag = tr[k].val = 0;
if(l == r) return;
int mid = (l + r) >> 1;
build(k << 1,l,mid),build((k << 1) | 1,mid + 1,r);
return;
}
void update(int k,int l,int r,int x,int y,int w)
{
if(x > y) return;
if(l >= x && r <= y)
{
tr[k].tag += w;
if(tr[k].tag) tr[k].val = r - l + 1;
else tr[k].val = tr[k << 1].val + tr[(k << 1) | 1].val;
if(l == r && !tr[k].tag) tr[k].val = 0;
return;
}
int mid = (l + r) >> 1;
if(x <= mid) update(k << 1,l,mid,x,y,w);
if(y > mid) update((k << 1) | 1,mid + 1,r,x,y,w);
if(tr[k].tag) tr[k].val = r - l + 1;
else tr[k].val = tr[k << 1].val + tr[(k << 1) | 1].val;
return;
}
int main()
{
scanf("%d",&T);
while (T --)
{
scanf("%d%d",&n,&m),ans = n;
for (int i = 0;i <= n;++ i) c[i].clear();
build(1,1,n);
for (int i = 1;i <= m;++ i)
scanf("%d%d",&a[i],&b[i]),vis[i] = 0,c[a[i]].push_back(i),c[b[i] - 1].push_back(i);
for (int i = 1;i <= m;++ i) update(1,1,n,a[i],b[i] - 1,1);
for (int i = 1;i <= n;++ i)
{
for (auto j : c[i])
{
if(!vis[j])
{
update(1,1,n,a[j],b[j] - 1,-1);
update(1,1,n,b[j],n,1);
update(1,1,n,1,a[j] - 1,1);
vis[j] = 1;
}
else
{
update(1,1,n,a[j],b[j] - 1,1);
update(1,1,n,b[j],n,-1);
update(1,1,n,1,a[j] - 1,-1);
}
}
ans = min(ans,tr[1].val);
}
printf("%d\n",ans);
}
return 0;
}
总结
这场比赛算是目前来说打得最满意的一场了,美中不足的是 C 题做复杂了浪费了点时间,E 题因为没看到取模也罚时了挺久,在解题速度上还有很大的提升空间,现在的愿景是早日 AK 一场比赛,任重而道远hhh 。打了一两个月 cf ,思维题上有了提升,但想想也只是填补了一些基础不牢的问题,还有很多算法和数据结构太久没碰了十分生疏,有待接着填坑。