牛客周赛 Round 69(A~F)
https://ac.nowcoder.com/acm/contest/96115#question
真难过,一个小时竟然做不出F。没想到F是线段树的变式,我还想开7,8个树状数组进行维护,直接秀逗了。不得不说,从分治的角度看线段树去做F简直太妙了,而且看了官方题解,用struct存起来,直接可以快速复制出另一个线段树。收获颇丰
A.构造C的歪
题面:
小歪有两个整数 $ a $ 和 $ b $ ,他想找到这样一个整数 $ c $ ,使得这三个整数在经过排序后能成为一个等差数列。
输入:
在一行上输入两个整数 $ a $ 和 $ b $ $ (1 \leq a, b \leq 10^6)$ 代表已有的数字。
输出:
在一行上输出一个整数代表你所找到的第三个数字。
如果存在多个解决方案,您可以输出任意一个,系统会自动判定是否正确。注意,自测运行功能可能因此返回错误结果,请自行检查答案正确性。
样例:
3 2
——————
1
思路:思维,签到题,第三个数必须为整数,所以就输出最小数的减去公差吧。
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"// 交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
int main()
{
fio();
ll a,b;
cin>>a>>b;
cout<<min(a,b)-(abs(a-b))<<endl;
}
B.不要三句号的歪
题面:
在书写超过3个的连续数字时,我们通常会将第一、二项和最后一项写出,中间的部分使用三个英文句号作为省略号 ...
替代。例如,2,3,...,7
其实就是使用省略号省略了 4,5,6
这三个数字。
现在,对于给定的数列,你需要直接求解出省略了多少数字。
输入:
在一行上输入一个长度不超过20的字符串。具体的规范为:
- 字符串仅包含三个数字 $ a, b, c $ 满足 $ a+1=b $ 且 $ b+1<c $。
- 数字 $ a $ 与 $ b$ 之间使用一个半角逗号
,
间隔。 - 省略号由三个半角句号
.
构成。 - 省略号前后各使用一个半角逗号间隔。
输出:
在一行上输出一个整数代表被省略的数字数量。
样例1:
2,3,...,7
——————
3
样例2:
1,2,...,100000000
——————
99999997
思路:暴力,其实用官方的scanf读入很好或着string+stoll转化也好。知道第一个和最后一个数就行了,因为输入说了b=a+1,公差为1
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"// 交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
int main()
{
fio();
string f;
cin>>f;
ll l,r;
l=0,r=0;
ll ans=0;
for(ll i=0;i<f.size();i++)
{
if(f[i]==',')
{
for(ll k=0;k<=i-1;k++)
{
ans=ans*10+f[k]-'0';
}
break;
}
}
//cout<<ans<<endl;
ll cnt=0;
for(ll i=f.size()-1;i>=0;i--)
{
if(f[i]==',')
{
for(ll u=i+1;u<=f.size()-1;u++)
cnt=cnt*10+f[u]-'0';
break;
}
}
cout<<cnt-ans-2<<endl;
}
C.仰望水面的歪
题面:
小歪正在水底潜水,他所在的位置距离水面的直线距离为 $ h $ 。小歪有一个神奇的激光装置,激光射向水面后会发生全反射现象。
以小歪所在的位置为原点建立三维坐标轴,小歪的坐标即为$ (0,0,0) $ 。在水中,有一些坐标需要小歪使用激光击中,第$ i $ 个坐标使用 $ (x_i, y_i, z_i)$ 表示。
输入:
第一行输入两个整数 $ n, h $ $(1 \leq n \leq 100; 1 \leq h \leq 10^9) $,分别代表需要击中的坐标位置数量和距离水面的距离。
- 随后 $n $行,每行输入三个整数 $ x, y, z $ $(1 \leq x, y \leq 10^9; -10^9 \leq z \leq h) $,代表需要击中的坐标。
输出:
对于每一个需要击中的坐标点,小歪需要计算出激光的发射方向,以确保激光经过水面全反射后能够精确击中目标。这个方向由三个整数 $ i, j, k $ 表示,它们构成一个向量,且需要满足条件 $ \text{gcd}(i, j, k) = 1 $,即这三个整数的最大公约数为1。
样例:
2 5
3 3 2
4 4 0
——————
3 3 8
2 2 5
思路:思维,因为往各个方向的速度分量是有比例的,所以再到达目标x,y对应点时,z也必须得对应,所以答案为对应距离比x:y:h+h-z。
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"// 交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
int main()
{
fio();
ll n,h;
cin>>n>>h;
for(ll i=1;i<=n;i++)
{
ll a,b,c;
cin>>a>>b>>c;
ll k=h+h-c;
ll u=gcd(a,b);
ll f=gcd(u,k);
cout<<a/f<<" "<<b/f<<" "<<k/f<<endl;
}
}
D.小心火烛的歪
题面:
小歪正在一个占地 $ n \times m $ 大小的草地上研究他的燃放烟花计划。草地上一些位置已经堆放了杂物,这些位置在字符矩阵中用数字1标注,其余位置用数字0标注。
小歪已经准备好了若干个烟花燃放计划,每个计划也是一个 $ n \times m $ 大小的字符矩阵。在这些计划中,将会被燃放烟花的地块用数字1标注,没有烟花的地块用数字0标注。
小歪希望选择一些计划同时实施,使得:
- 所有有杂物的位置都不会燃放烟花。
- 所有没有杂物的位置都燃放上烟花。
他想要知道是否存在这样的选择方法,如果存在,他希望找到最少需要多少个计划。
输入:
小歪正在一个占地$ n \times m $ 大小的草地上研究他的燃放烟花计划。草地上一些位置已经堆放了杂物,这些位置在字符矩阵中用数字1标注,其余位置用数字0标注。
小歪已经准备好了若干个烟花燃放计划,每个计划也是一个 $ n \times m $ 大小的字符矩阵。在这些计划中,将会被燃放烟花的地块用数字1标注,没有烟花的地块用数字0标注。
输出:
如果不存在满足要求的燃放方案,输出 -1
。
- 否则,按以下格式输出:
- 第一行输出一个整数 $p $ \((0 \leq p \leq q)\),代表使用到的计划数量。
- 第二行输出 $ p $个整数,代表所选择的计划编号。编号从1开始计数。
样例1:
2 2 1
00
01
11
10
1
1
样例2:
7 7 5
1110111
1111111
1100001
0101000
1100001
1111111
1110111
0001000
0000000
0000000
1000001
0000000
0000000
0001000
0000000
0000000
0011100
0000000
0011100
0000000
0000000
0000000
0000000
0000010
0000111
0000010
0000000
0000000
0000000
0000000
0010000
0010000
0010000
0000000
0000000
0000000
0000000
0010000
0010111
0010000
0000000
0000000
————————
4
1 2 3 4
思路:二进制枚举+思维,数据很小,考虑暴力吧,因为涉及不同组合得问题,考虑用二进制枚举或者next_permutation函数去枚举子集,对于每个子集,用个二维数组去把烟花点标记下,然后在和一开始的草地情况比较下符不符合,然后去维护一个满足的最优方案。要注意,当草地上全为杂物时,输出0.
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"// 交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
string g[30][30];
ll a[30][30];
int main()
{
fio();
ll n,m,q;
cin>>n>>m>>q;
string f[100];
ll ans=0;
for(ll i=1;i<=n;i++)cin>>f[i],f[i]='0'+f[i];
for(ll i=1;i<=n;i++)
{
for(ll j=1;j<=m;j++)if(f[i][j]=='1')ans++;
}
if(n*m==ans)
{
cout<<0<<endl;
return 0;
}
for(ll k=1;k<=q;k++)
{
for(ll j=1;j<=n;j++)
cin>>g[k][j],g[k][j]='0'+g[k][j];
}
vector<ll>k,o;
for(ll i=1;i<=(1ll<<q)-1;i++)
{
o.clear();
for(ll j=1;j<=10;j++)
{
for(ll u=1;u<=10;u++)a[j][u]=0;
}
for(ll j=0;j<=q-1;j++)
{
if(i&(1ll<<j))
{
for(ll u=1;u<=n;u++)
{
for(ll z=1;z<=m;z++)
{
if(g[j+1][u][z]=='1')
a[u][z]++;
}
}
o.push_back(j+1);
}
}
ll pd=0;
for(ll c=1;c<=n;c++){
for(ll j=1;j<=m;j++)
{
if(f[c][j]=='1'&&a[c][j]==0)continue;
else if(f[c][j]=='0'&&a[c][j])continue;
else pd=1;
}
}
if(pd==0)
{
if(k.size()==0)
{
for(auto j:o)
k.push_back(j);
}
else if(k.size()>o.size())
{
k.clear();
for(auto j:o)
k.push_back(j);
}
}
}
if(k.size()==0)
cout<<-1<<endl;
else
{
cout<<k.size()<<endl;
for(auto j:k)
cout<<j<<" ";
cout<<endl;
}
}
E.喜欢切数组的红
题面:
小红有一个长度为 $ n $ 的数组 ${a_1, a_2, \ldots, a_n} $,她打算将数组切两刀变成三个非空子数组,使得每一个子数组中至少存在一个正数,且每个子数组的和都相等。
输入:
第一行输入一个整数 $ n $ \((3 \leq n \leq 2 \times 10^5)\),代表数组中的元素数量。
第二行输入 $ n $ 个整数 $ a_1, a_2, \ldots, a_n $ \((-10^9 \leq a_i \leq 10^9)\),代表数组元素。
输出:
在一行上输出一个整数,代表切分方案数。
样例1:
3
3 3 3
——————
1
样例2:
6
1 1 4 5 1 4
——————
0
样例3:
10
0 3 4 2 3 2 1 -1 3 4
——————
2
思路:思维,要分成等价的三部分,必须总和为3的倍数.这时设能被3整数的总和值为n,可以发现,三个部分的前缀和必须为\(n/3\),\(n/3*2\),n才能符合题意。但是得注意每个区域必须要有正数,所以可以先统计\(n/3*2\)的个数cnt,然后从n往小找第一个正数,没找到之前,遇到一个\(n/3*2\)就cnt--,随后再正向走一遍,开个数组记录前cnt个\(n/3*2\)前面有多少个正数,最后就从1到n遍历一遍,边统计遇到的正数的个数,然后对于不符合要求的\(n/3*2\)进行踢出同时cnt--,随后遇到一个n/3,答案就加一次cnt就好了。
这里数据太水了,给出两版本代码,一个是混过去的,一个是ok的
混过去的:
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"// 交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll a[250000];
ll b[250000];
ll u[250000];
int main()
{
fio();
ll n;
cin>>n;
ll cnt,ans;
ans=cnt=0;
for(ll i=1;i<=n;i++)
{
ll x;
cin>>x;
a[i]=a[i-1]+x;
u[i]=x;
}
if(a[n]%3!=0)
cout<<0<<endl;
else
{
for(ll i=1;i<=n;i++)
{
if(a[i]==a[n]/3*2)
{
cnt++;
}
}
for(ll i=n;i>=1;i--)
{
if(u[i]>0)
break;
if(a[i]==a[n]/3*2)
cnt--;
}
if(cnt==0)
cout<<0<<endl;
else
{
ll gs=0;
ll ko=0;
for(ll i=1;i<=n;i++)
{
if(u[i]>0)
{
ko++;
}
if(a[i]==a[n]/3*2)
{
gs++;
b[gs]=ko;
}
if(gs==cnt)break;
}
ll co=0;
ll zp=1;
for(ll i=1;i<=n;i++)
{
if(u[i]>0)
co++;
while(b[zp]<=co&&zp<=gs)
{
zp++;
}
if(zp==gs+1)
break;
if(a[i]==a[n]/3)
ans+=gs-zp+1;
}
cout<<ans<<endl;
}
}
}
正确的:
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"// 交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
ll a[250000];
ll b[250000];
ll u[250000];
int main()
{
fio();
ll n;
cin>>n;
ll cnt,ans;
ans=cnt=0;
for(ll i=1;i<=n;i++)
{
ll x;
cin>>x;
a[i]=a[i-1]+x;
u[i]=x;
}
if(a[n]%3!=0)
cout<<0<<endl;
else
{
for(ll i=1;i<=n;i++)
{
if(a[i]==a[n]/3*2)
{
cnt++;
}
}
for(ll i=n;i>=1;i--)
{
if(a[i]==a[n]/3*2)
cnt--;
if(u[i]>0)
break;
}
if(cnt==0)
cout<<0<<endl;
else
{
ll gs=0;
ll ko=0;
for(ll i=1;i<=n;i++)
{
if(u[i]>0)
{
ko++;
}
if(a[i]==a[n]/3*2)
{
gs++;
b[gs]=ko;
}
if(gs==cnt)break;
}
ll co=0;
ll zp=1;
for(ll i=1;i<=n;i++)
{
if(u[i]>0)
co++;
while(b[zp]<=co&&zp<=gs)
{
zp++;
}
if(zp==gs+1)
break;
if(co==0)
continue;
if(a[i]==a[n]/3)
ans+=gs-zp+1;
}
cout<<ans<<endl;
}
}
}
F.研究red子序列的红
题面:
小红有两个长度为 $ n $ 的字符串 $ s, t $,仅包含小写字母,下标从 1 开始。
她每次会进行一个操作,把 $ s_i $ 和 $ t_i $交换,你需要回答每一次交换后字符串 $s $ 中的 "red" 子序列和$t $中 "red" 的子序列之差。
每次询问不独立。
子序列是指在一个序列中,通过删除某些元素(可以是零个或多个元素),而不改变其余元素的相对顺序所得到的序列。
输入:
第一行输入两个整数 $ n, q $ \((1 \leq n, q \leq 2 \times 10^5)\),分别代表字符串长度和操作次数。
第二行输入一个长度为 $ n $ 的字符串 $ s $。
第三行输入一个长度为 $ n $ 的字符串 $ t $。
接下来 $q $ 行,每行输入一个整数 $ x $ \((1 \leq x \leq n)\),表示每次交换的位置。
输出:
对于每次交换操作,输出字符串 $ s $中 "red" 子序列的数量与字符串 $ t $中 "red" 子序列数量之差。
样例:
5 2
redar
adade
5
4
——————
1
2
思路:线段树,学到了很多东西,用struct建线段树可以快速复制,然后可以通过维护R,E,D,RE,ED,RED去维护子序列RED的数量,真的很棒。所以对于固定查询同种子序列个数的单位置修改问题,线段树是个方便的解决方法。具体来说,就是左子树的R和右子树的ED组成RED,左子树的RE和右子树的D组成RED,每次往上传就好了。和常规改懒惰标志来讲,这次是改了上传标志(回溯)
#include<iostream>
#include<queue>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<deque>
#include<cctype>
#include<string.h>
#include<math.h>
#include<time.h>
#include<random>
#include<stack>
#include<string>
#define ll long long
#define lowbit(x) (x & -x)
#define endl "\n"// 交互题记得删除
using namespace std;
mt19937 rnd(time(0));
const ll mod = 998244353;
const ll maxn = 2e5 + 15;
ll ksm(ll x, ll y)
{
ll ans = 1;
while (y)
{
if (y & 1)
{
ans = ans % mod * (x % mod) % mod;
}
x = x % mod * (x % mod) % mod;
y >>= 1;
}
return ans % mod % mod;
}
ll gcd(ll x, ll y)
{
if (y == 0)
return x;
else
return gcd(y, x % y);
}
void fio()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
}
struct T
{
ll l, r;
ll R, E, D, RE, ED, RED;
};
struct s
{
ll l, r;
string f;
T p[maxn << 2];
void push_up(ll i)
{
p[i].RED = p[i << 1].R * p[i << 1 | 1].ED + p[i << 1].RE * p[i << 1 | 1].D + p[i << 1].RED + p[i << 1 | 1].RED;
p[i].RE = p[i << 1].R * p[i << 1 | 1].E + p[i << 1].RE + p[i << 1 | 1].RE;
p[i].ED = p[i << 1].E * p[i << 1 | 1].D + p[i << 1].ED + p[i << 1 | 1].ED;
p[i].R = p[i << 1].R + p[i << 1 | 1].R;
p[i].D = p[i << 1].D + p[i << 1 | 1].D;
p[i].E = p[i << 1].E + p[i << 1 | 1].E;
}
void build(ll i, ll l, ll r)
{
p[i].R = p[i].E = p[i].RE = p[i].ED = p[i].RED = p[i].D = 0;
p[i].l = l, p[i].r = r;
if (l == r)
{
if (f[l] == 'r')p[i].R++;
if (f[l] == 'e')p[i].E++;
if (f[l] == 'd')p[i].D++;
return;
}
build(i << 1, l, (l + r) >> 1);
build(i << 1 | 1, (l + r >> 1) + 1, r);
push_up(i);
}
void udq(ll i, ll l, ll r, char x)
{
if (p[i].l == l && p[i].r == r)
{
if (f[l] == 'r')p[i].R--;
if (f[l] == 'e')p[i].E--;
if (f[l] == 'd')p[i].D--;
if (x == 'r')p[i].R++;
if (x == 'e')p[i].E++;
if (x == 'd')p[i].D++;
f[l] = x;
return;
}
ll mid = (p[i].l + p[i].r) >> 1;
if (mid >= l)
udq(i << 1, l, min(mid, r), x);
if (r >= mid + 1)
udq(i << 1 | 1, max(mid + 1, l), r, x);
push_up(i);
}
T query(ll i, ll l, ll r)
{
T ans, ans1, ans2;
ans.R = ans.E = ans.D = ans.RE = ans.E = ans.ED = ans.RED = 0;
ans1.R = ans1.E = ans1.D = ans1.RE = ans1.E = ans1.ED = ans1.RED = 0;
ans2.R = ans2.E = ans2.D = ans2.RE = ans2.E = ans2.ED = ans2.RED = 0;
if (l == p[i].l && p[i].r == r)
{
ans = p[i];
return ans;
}
ll mid = (p[i].l + p[i].r) >> 1;
if (mid >= l)
ans1 = query(i << 1, l, min(r, mid));
if (r >= mid + 1)
ans2 = query(i << 1 | 1, max(l, mid + 1), r);
ans.RED = ans1.R * ans2.ED + ans1.RE * ans2.D + ans1.RED + ans2.RED;
ans.RE = ans1.R * ans2.E + ans1.RE + ans2.RE;
ans.ED = ans1.E * ans2.D + ans1.ED + ans2.ED;
ans.R = ans1.R + ans2.R;
ans.E = ans1.E + ans2.E;
ans.D = ans1.D + ans2.D;
return ans;
}
};
int main()
{
fio();
ll n, q;
cin >> n >> q;
string f1, f2;
cin >> f1 >> f2;
s k1, k2;
k1.f = '0' + f1;
k2.f = '0' + f2;
k1.build(1, 1, n);
k2.build(1, 1, n);
while (q--)
{
ll x;
cin >> x;
char j = k1.f[x];
k1.udq(1, x, x, k2.f[x]);
k2.udq(1, x, x, j);
cout << k1.query(1, 1, n).RED - k2.query(1, 1, n).RED << endl;
}
}