2025牛客寒假算法基础集训营5(无F,G,H,K)

打的晕晕的

A

减法时注意数据范围

1
2
3
4
5
6
7
8
9
10
11
12
13
void solve(){
int n;cin>>n;
char c;cin>>c;
if(c=='+'){
cout<<1<<" "<<n-1;
}
if(c=='*'){
cout<<1<<" "<<n;
}
if(c=='-'){
cout<<n*2<<" "<<n;
}
}

B

注意限制间隔

1
2
3
4
void solve(){
int n,k,t;cin>>n>>t>>k;
cout<<min(((n-k)/t),k+1)<<"\n";
}

C

贪心,已经合法的位我们不对其进行二次操作,且每个非法位我们操作且仅执行一次操作,无论是对a执行还是对b执行

注意到四种非法情况

a=0,b=0,c=1;

a=0,b=1,c=0;

a=1,b=0,c=0;

a=1,b=1,c=1;

每两种情况都可以通过交换操作使得两个非法位合法,无非是交换a/b的区别;

所以思路就是,最大化两两配对四种非法位,自身不能和自身配对

配对过程就是很典的思路:当最大值大于剩下的所有数,非配对数为最大值-其他所有数之和;否则非配对数为所有数字之和为偶数?0:1

ans=min(全用反置操作,配对数$\times$y+非配对数$\times$x)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void solve(){
int n,x,y,ans=0;cin>>n>>x>>y;
string s1,s2,s3;cin>>s1>>s2>>s3;
int pos=0;
vector<int>v(4);
for(int i=0;i<n;i++){
if((s1[i]-'0')^(s2[i]-'0')!=(s3[i]-'0')){
if(s1[i]=='0'&&s2[i]=='0') v[0]++;
if(s1[i]=='1'&&s2[i]=='1') v[1]++;
if(s1[i]=='0'&&s2[i]=='1') v[2]++;
if(s1[i]=='1'&&s2[i]=='0') v[3]++;
pos++;
}
}
sort(v.begin(),v.end());
if(v[3]>v[0]+v[1]+v[2]){
ans=(v[3]-v[1]-v[2]-v[0])*x+(v[1]+v[2]+v[0])*y;
}else{
int sum=(v[0]+v[1]+v[2]+v[3]);
ans=(sum%2)*x+(sum-(sum%2))/2*y;
}
cout<<min(ans,x*pos);
}

D

贪心的想,我们构造字符串一定是某段中为连续的0和连续的1,前面一段的字符串如果为11…00,那么下一段字符串就应该是从0开始,即0…01…1,两段拼接起来并删除重复串后为101,也就是说这样每一段最多只会贡献1

考虑什么时候执行区间反置操作,当前一段的字符串为01时,若下一段全是1则该段对答案没有贡献,也就是说当该段全是0时,我们执行区间反置操作;同理,前一段是10时,下一段全是1,我们执行反置操作

用前缀和维护,调和级数复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void solve(){
int n;cin>>n;
string s;cin>>s;
s=" "+s;
int ans=0;
vector<int>pre(n+1,0);
for(int i=1;i<=n;i++){
pre[i]=pre[i-1]+(s[i]=='0');
}
for(int k=1;k<=n;k++){
int tem=0;
for(int j=k;j<=n;j+=k){
int cnt0=pre[j]-pre[j-k],cnt1=k-cnt0;
if(cnt0!=0&&cnt1!=0) tem++;
if(j+k>n){
cnt0=pre[n]-pre[j],cnt1=n-j-cnt0;
if(cnt0!=0&&cnt1!=0) tem++;
}
}
ans^=(tem+1);
}
cout<<ans<<"\n";
}

E

可以想到胜利方式有两种,直接胜利和间接胜利

直接胜利就是在下两步之后,横/竖/对角线直接连成三个

间接胜利就是下两步之后,我们有两行/两列/一行一列/一行一对角/一列一对角连成两个且第三个空位没有O阻挡,这样的话无论炸鸡去阻挡哪个,小L都能取得胜利

我检查间接胜利的方式就是遍历所有可能的组合判断是否符合

复杂度最高为O(300T)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void solve(){
vector<vector<char>>g(10,vector<char>(10));
vector<array<int,2>>pos;
for(int i=1;i<=3;i++){
for(int j=1;j<=3;j++){
cin>>g[i][j];
if(g[i][j]=='G'){
pos.push_back({i,j});
}
}
}
auto check1=[&](char c){
return c=='G'||c=='X';
};
auto check2=[&](char c){
return c=='X';
};
int ok=0;
for(int i=1;i<=3;i++){
int num1=check1(g[i][1])+check1(g[i][2])+check1(g[i][3]);
int num11=check2(g[i][1])+check2(g[i][2])+check2(g[i][3]);
int num2=check1(g[1][i])+check1(g[2][i])+check1(g[3][i]);
int num22=check2(g[1][i])+check2(g[2][i])+check2(g[3][i]);
if((num11>=1&&num1==3)||(num22>=1&&num2==3)) ok=1;
}
int num1=check1(g[1][1])+check1(g[2][2])+check1(g[3][3]);
int num11=check2(g[1][1])+check2(g[2][2])+check2(g[3][3]);
int num2=check1(g[1][3])+check1(g[2][2])+check1(g[3][1]);
int num22=check2(g[1][3])+check2(g[2][2])+check2(g[3][1]);
if((num1==3&&num11>=1)||(num2==3&&num22>=1)) ok=1;
for(auto i:pos){
for(auto j:pos){
if(i[0]==j[0]&&i[1]==j[1]) continue;
g[i[0]][i[1]]='X';g[j[0]][j[1]]='X';
int op=0;
for(int x=1;x<=3;x++){
int num1=check1(g[x][1])+check1(g[x][2])+check1(g[x][3]);
if(num1==3) op++;
int num2=check1(g[1][x])+check1(g[2][x])+check1(g[3][x]);
if(num2==3) op++;
}
int num11=check1(g[1][1])+check1(g[2][2])+check1(g[3][3]);
if(num11==3) op++;
int num22=check1(g[1][3])+check1(g[2][2])+check1(g[3][1]);
if(num22==3) op++;
if(op>=2) ok=1;
g[i[0]][i[1]]='G';g[j[0]][j[1]]='G';
}
}
cout<<(ok?"Yes\n":"No\n");
}

I

想复杂了,最后结论就是除非n为0,通过若干次两种操作组合可以操作出来任何数

应该是可以证出来的,这里放个伪证明

给定一个目标m>=1,我们只需要使得n最终的值在$[m^2,(m+1)^{2})$之间即可通过开方得到m,这中间有2m+1个数字,在m<=1e9时,任何2m+1个连续数字之间一定存在2的幂次

1
2
3
4
5
6
7
8
9
10
11
12
void solve(){
int n,m,ok=0;cin>>n>>m;
if(m==0||n==0){
if(n==m){
cout<<"Yes\n";
}else{
cout<<"No\n";
}
return;
}
cout<<("Yes\n");
}

J

没啥说的,模拟

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void solve(){
int n;cin>>n;
string s;cin>>s;
int v=0,ans=0;
for(auto i:s){
if(i=='0'){
v+=10;
ans+=v;
}else if(i=='1'){
v-=5;v=max(v,0LL);
ans+=v;
}else{
ans+=max(v-10,0LL);
}
}
cout<<ans;
}

L

构造题,可以很贪心的想到答案除了n==3之外就是n/3

想到互质,联想两个连续的数一定互质,自然的想到构造多组连续的三元组,即

1 2 3

4 5 6

7 8 9

10 11 12

可以观察到有的符合题意,有的不符合(每个三元组是三对质数),要想方法缩减质数的对数

可以发现每两个数就会出现一个偶数,每三个数就会出现一个整除3的数,无论是两个偶数/两个是3的倍数的数都会使得质数的对数-1,初步构造方法如下

x x+1 x+1+2

x+2 x+2+2 x+2+2+1

即1 2 4

​ 3 5 6

这样每行都能出现两个偶数/两个3的倍数,缺点就是当n%3==0且为奇数的时候,右下角的数==n+1,即

1 2 4

3 5 6

7 8 10

考虑将10调整为9的方法即可,swap(6,10)可以轻松的满足条件,构造完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void solve(){
int n;cin>>n;
if(n==3){
cout<<0<<"\n";
return;
}
cout<<n/3<<"\n";
if((n/3)*3==n){
vector<vector<int>>a(n+1,vector<int>(4));
for(int i=1;i<=n/3;i++){
if(i&1){
a[i][1]=(i-1)*3+1;a[i][2]=(i-1)*3+2;a[i][3]=(i-1)*3+4;
}else{
a[i][1]=(i-1)*3;a[i][2]=(i-1)*3+2;a[i][3]=(i-1)*3+3;
}
}
a[n/3][3]=n;
if(n&1) swap(a[n/3][3],a[n/3-1][3]);
for(int i=1;i<=n/3;i++){
cout<<a[i][1]<<" "<<a[i][2]<<" "<<a[i][3]<<"\n";
}
}else{
for(int i=1;i<=n/3;i++){
if(i&1) cout<<(i-1)*3+1<<" "<<(i-1)*3+2<<" "<<(i-1)*3+4<<"\n";
else cout<<(i-1)*3<<" "<<(i-1)*3+2<<" "<<(i-1)*3+3<<"\n";
}
}
}

牛客周赛Round 79(A-F)

A

除1外的n*2必不为质数

1
2
3
4
void solve(){
int n;cin>>n;
cout<<(n==1?-1:n*2);
}

B

1
2
3
4
5
void solve(){
int n;cin>>n;
int mx=n/2,mn=(n+1)/3;
cout<<mn<<" "<<mx;
}

C

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void solve(){
int n;cin>>n;
if(n==1){
cout<<0;
return;
}
if(n==2){
cout<<1;
return;
}
int ans=0;
vector<int>pre(100000);
pre[2]=1;
for(int i=3;i<=n;i++){
int cnt=power(2,i-1,mod);
pre[i]=(pre[i-1]+cnt+(cnt*power(2,mod-2,mod))%mod)%mod;
}
cout<<pre[n];
}

Codeforces Round 1002 (Div. 2,A-C)

想了一下,我觉得个人赛的一个基本流程大致是这样的

  1. 认真的读一遍题,如果有,一定要看样例的解释,最后模拟样例
  2. 从问题的性质出发,包括但不限于复杂度,题目与数据的限制,重新读题,将问题转化为子问题。如果一个hint都没有,建议重复步骤1,如果卡这超过10分钟,建议切题
  3. 给出相应的解决方法,一定要思考可做性,可以参考过题人数与速度,同时思考一下代码的实现难易程度,在这里卡超过10分钟,也建议重新读题/切题

A

GuessForces

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void solve(){
int n;cin>>n;
vector<int>a(n),b(n);
for(int i=0;i<n;i++) cin>>a[i];
for(int i=0;i<n;i++) cin>>b[i];
sort(a.begin(),a.end());
sort(b.begin(),b.end());
set<int>st;
vector<int>v1,v2;
for(int i=0;i<n;i++){
if(!st.count(a[i]+b[i])) st.insert(a[i]+b[i]);
else{
v1.push_back(a[i]);
v2.push_back(b[i]);
}
}
sort(v1.begin(),v1.end());
sort(v2.begin(),v2.end(),greater<int>());
for(int i=0;i<v1.size();i++){
st.insert(v1[i]+v2[i]);
}
cout<<(st.size()>=3?"YES\n":"NO\n");
}

B

贪心的想,越长的串出现非i数一定越多

如果恰好填满(即k==n),只能按部就班的模拟

否则我们使得前2个子数组尽可能长(即让后k-2个子数组每个只包含一个数字),遍历前[2,n-(k-2)]的数组,容易发现一旦a[i]!=1,我们就将其作为第二段子数组的起点,此时答案为1

否则只能是该段数组都只包含1,此时将a[1]作为第一段子数组,第二段子数组将包含>1个数的1(因为此时n>k),此时答案为2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void solve(){
int n,k,cnt=1;cin>>n>>k;
vector<int>a(n+1),st;
for(int i=1;i<=n;i++){
cin>>a[i];
}
if(k==n){
for(int i=1;i<=n;i++){
if(i%2==0) st.push_back(a[i]);
}
st.push_back(0);
for(auto i:st){
if(i!=cnt){
cout<<cnt<<"\n";
return;
}
cnt++;
}
}
for(int i=2;i<=n-(k-2);i++){
if(a[i]!=1){
cout<<1<<"\n";
return;
}
}
cout<<2<<"\n";
}



Manacher

能在O(N)的时间内求出以每个位置为回文中心的回文半径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
vector<int> manacher(string s) {
string t = "#";
for (auto c : s) {
t += c;
t += '#';
}
int n = t.size();
vector<int> r(n);
for (int i = 0, j = 0; i < n; i++) {
if (2 * j - i >= 0 && j + r[j] > i) {
r[i] = min(r[2 * j - i], j + r[j] - i);
}
while (i - r[i] >= 0 && i + r[i] < n && t[i - r[i]] == t[i + r[i]]) {
r[i] += 1;
}
if (i + r[i] > j + r[j]) {
j = i;
}
}
return r;
}

void solve(){
string s;cin>>s;
auto r=manacher(s);
int ans=0;
for (int i = 1; i < 2 * s.size(); i++) {
ans = max(ans, r[i] - 1);
}
cout<<ans<<"\n";
}

KMP

1
2
3
4
5
6
7
8
9
10
11
12
std::vector<int> kmp(std::string s) {
int n = s.size();
std::vector<int> f(n + 1);
for (int i = 1, j = 0; i < n; i++) {
while (j && s[i] != s[j]) {
j = f[j];
}
j += (s[i] == s[j]);
f[i + 1] = j;
}
return f;
}

Z函数(扩展kmp/exkmp)

能在O(n)的时间范围里找到给定字符串s,对于每个位置i,s整个字符串和$(s[i]-s[n-1])(等价于s.substr(i))$的LCP(最长公共前缀)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::vector<int> Z(std::string s) {
int n = s.size();
std::vector<int> z(n + 1);
z[0] = n;
for (int i = 1, j = 1; i < n; i++) {
z[i] = std::max(0, std::min(j + z[j] - i, z[i - j]));
while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {
z[i]++;
}
if (i + z[i] > j + z[j]) {
j = i;
}
}
return z;
}