1017div4(D~H)

Codeforces Round 1017 (Div. 4) (D~H)

ABC被我吃掉了

D. Tung Tung Sahur

一个鼓有两面left和right,每一面的响声不同,分别是L和R,且敲打一次可能发出1次或2次声响

现给出敲打的顺序和给定一串声响,问按这个顺序敲打是否能发出给定的声响(例如:LLR和LLLR合法,但LR和LLLR不合法)

可以看出一个L可以对应1-2个L声响,遇到连着的如LLL,其对应的声响为L*3到L*6,所以由此可以想到:

将顺序中连续的L、R和声响中连续的L、R对应,如果对应不上则NO;设顺序中一段L的数量为cntL,则对应的这段声响的L数量应该在cntL与cntL*2之间,否则也NO

#include<bits/stdc++.h>
using namespace std;
int T;
string str_a;
string str_A;
vector<int>a;
vector<int>A;
int main()
{
    scanf("%d\n",&T);
    while(T--) {
        a.clear();
        A.clear();
        getline(cin,str_a);
        getline(cin,str_A);
        //cout<<str_a<<"\n"<<str_A<<endl;
        if(str_a[0]!=str_A[0]) {
            printf("NO\n");
            continue;
        }
        char last=0;
        for(auto var : str_a) {
            if(var==last) {
                a.back()++;
            } else {
                last=var;
                a.emplace_back(1);
            }
        }

        last=0;
        for(auto var : str_A) {
            if(var==last) {
                A.back()++;
            } else {
                last=var;
                A.emplace_back(1);
            }
        }

        if(A.size()!=a.size()) {
            printf("NO\n");
            continue;
        }

        int flag=0;
        for(int i=0;i<a.size();i++) {
            //printf("%d %d\n",a[i],A[i]);
            if(a[i]>A[i]||a[i]*2<A[i]) {
                flag=1;
                break;
            }
        }
        if(flag) printf("NO\n");
        else printf("YES\n");
    }
    return 0;
}
E. Boneca Ambalabu

给出一个数列a,求所有 1≤k≤n 中\((a_k⊕a_1)+(a_k⊕a_2)+…+(a_k⊕a_n)\)的最大值

显然不能直接枚举k再一个个求异或和,不过我们可以发现,题目是由每个 \(a_i\) 异或某一个 \(a_k\)\(a_k\)不固定但\(a_i\)固定,所以很容易想到对异或之和的计算进行一些处理。

我们可以发现如果 \(a_k\) 的某一位是0/1,在之后的计算中可以直接用这一位和所有 \(a_i\) 的这一位进行异或,根据所有 \(a_i\) 都是固定的这个性质,我们可以提前存好所有 \(a_i\) 在每一个二进制位数下的0/1的个数,当得知 \(a_k\) 在某一位是0/1后就能直接计算出这一位的异或之和,就能在\(O(log\ a_{max})\)的时间内算出异或之和,在这个基础上枚举k取最大值,复杂度\(O(nlog\ a_{max})\)

注意由于要求和,会爆int

#include<bits/stdc++.h>
#define int long long
using namespace std;
int T;
int n;
int a[200100];
int b[2001000];
int cnt[1000];
signed main()
{
    scanf("%lld",&T);
    while(T--) {
        int topbit=0;
        int maxn=0;
        scanf("%lld",&n);
        for(int i=1;i<=n;i++) {
            scanf("%lld",&a[i]);
            b[i]=a[i];
        }

        for(int i=1;i<=n;i++) {
            for(int j=1;b[i];j++) {
                cnt[j]+=b[i]%2;
                b[i]/=2;
                topbit=max(topbit,j);
            }
        }

        /*for(int i=1;i<=n;i++) {
            for(int j=1;j<=topbit;j++) {
                if((a[i]>>(j-1))%2) printf("%6d(1) ",((n-cnt[j])<<(j-1)));
                else printf("%6d(0)",cnt[j]<<(j-1));
            }
            printf("\n");
        }*/

        for(int i=1;i<=n;i++) {
            int sum=0;
            for(int j=1;j<=topbit;j++) {
                if((a[i]>>(j-1))%2) sum+=((n-cnt[j])<<(j-1));
                else sum+=(cnt[j]<<(j-1));
            }
            maxn=max(maxn,sum);
            //printf("SUM=%d\n",sum);
        }
        printf("%lld\n",maxn);

        for(int i=0;i<1000;i++) {
            cnt[i]=0;
        }
    }
    return 0;
}

/*

第i位之和为X:则a_j
1. a_j_i=1 : sum(a_j_i^a_x_i)=n-1-(X-1)=n-X;
2. .....=0 : sum(...........)=X;

注:1. 第i位可以直接用 (x>>(i-1))%2 表示 
    2. 即使a_i<=2^30,sum仍可能爆int 
00001
00010
00100
01000
10000

*/
F. Trulimero Trulicina

题目让我们构造一个 n*m 的由数字构成的矩形,要求:

  • 由1 和 k 之间的数组成
  • 1 到 k 的每个整数出现的次数相同
  • 相邻两个单元格中的整数不同

显然,m<k时,复读1到k的数即可

m==k时,会出现{123456, 123456, 123456}这种情况,我们每隔一行把它往后挪一格,变成{123456, 234561, 123456}即可

m>k时,看似也是复读1到k的数,但这里有个陷阱:

12341234

12341234

当m%k0时直接复读会重复,所以我们可以像mk时那样,每隔一行往后挪一格即可:

12341234

23412341

(挪的方向不甚重要)

#include<bits/stdc++.h>
using namespace std;
int T;
int n,m,k;
int a[200100];
signed main()
{
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d",&n,&m,&k);
        if(m%k==0) {//包含了m==k
            for(int i=1;i<=n;i++) {
                if(i%2==0) {
                    for(int j=0;j<m;j++) {
                        printf("%d ",j%k+1);
                    }
                    printf("\n");
                } else {
                    for(int j=1;j<=m;j++) {
                        printf("%d ",j%k+1);
                    }
                    printf("\n");
                }
            }
        } else if(m>k) {
            int cnt=0;
            for(int i=1;i<=n;i++) {
                for(int j=1;j<=m;j++) {
                    printf("%d ",cnt%k+1);
                    cnt++;
                }
                printf("\n");
            }
        } else if(m<k) {
            int cnt=0;
            for(int i=1;i<=n;i++) {
                for(int j=1;j<=m;j++) {
                    printf("%d ",cnt%k+1);
                    cnt++;
                }
                printf("\n");
            }
        }
    }
    return 0;
}

/*
1. m>k
   1234561
   23456
2. m<k
   12345
   61234
   56
3. m==k
   123456
   234561

*/
G. Chimpanzini Bananini

给出一个长度为 m 的数组 b,定义粗糙度为\(∑^m_{i=1}b_i⋅i=b_1⋅1+b_2⋅2+b_3⋅3+…+b_m⋅m\),现在我们要进行q次操作,操作有 3 种,分别是:

1. 将\(b_m\) 删除,转而插入到 \(b_1\) 的前面变成新的 \(b_1\)

2. 将数组 b 倒转,即: 将 \(b_i\)\(b_{m-i+1}\) 交换

3. 在数组的末尾插入一个元素 k

在每进行一种操作后,输出当前的粗糙度

和重庆市赛的L在解题的形式上有几分相似(L更简单一些),都是将操作后的数组和ans分开计算,各优化各的

整体的思路是模拟,然后用一些数学规律去优化粗糙度的计算(以下直接管粗糙度叫ans了)

对于操作1:

存储:使用deque存数组b,该操作\(O(1)\)

ans:可以等效为所有的 \(b_i\) 都后移一位:\(ans+=sum(b)\),然后单独计算 \(b_m\)\(ans-=b_m * m\);其中sum(b) 表示数组b所有元素之和,能预处理

对于操作2:

存储:使用一个flag标记,flag为0表示数组是正向的,为1表示数组反向了,进行操作1和3时都要先检查flag

ans:可以观察到正向的ans+反向的ans等于\(b_1⋅(m+1)+b_2⋅(m+1)+b_3⋅(m+1)+…+b_m⋅(m+1)\),即ans=sum(b)*(m+1)-ans

对于操作3:

存储:deque真是太好用辣!

ans:ans+=k*(m+1),然后注意要更新m和sum(b)

解题的关键是操作2和操作1的ans计算的优化,比较次要的还有操作2的存储的方式,核心还是找数学规律

(以及deque确实好用)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,opt,n;
deque<int>que;
signed main()
{
    int x;
    scanf("%lld",&T);
    while(T--) {
        int flag=0;
        int ans=0;
        int sum=0;
        int sum_plus=0,size=0;
        scanf("%lld",&n);
        for(int i=1;i<=n;i++) {
            scanf("%lld",&opt);
            if(opt==1) {//迁移 
                ans+=sum;
                if(flag==0) {
                    //printf("ans=%lld %lld %lld %lld %lld\n",ans,flag,sum,que.back(),que.front());
                    ans-=(size)*que.back();
                    que.push_front(que.back());
                    que.pop_back();
                } else {
                    ans-=(size)*que.front();
                    que.push_back(que.front());
                    que.pop_front();
                }
            } else if(opt==2) {//reverse 
                flag==0 ? flag=1 : flag=0;
                ans=sum_plus+sum-ans;
            } else {//add
                size++;
                scanf("%lld",&x);
                flag==0 ? que.push_back(x) : que.push_front(x);
                ans+=x*size;
                sum+=x;
                sum_plus+=sum+x*(size-1);
            }
            printf("%lld\n",ans);
        }
        que.clear();
    }
    return 0;
}
/*
byd两次把flag写成opt,还把公式写错了(像这种推公式的题尽量先在纸上写清楚吧) 
这再次告诉了我们头疼不一定是头的问题可能是脚的问题 
以及找性质的重要性(方法论) 
*/
H. La Vaca Saturno Saturnita

有点超出我的能力范围了,之前做的时候看了题解懂了,隔了两周补题时想了很久,看了之前的代码才想出来(上次div3的最后一题不看题解还能想个七七八八,这个是一点思路没有)

给出一个数组 a,进行q次操作,每次操作在数组 a 的[ l , r ]的下标内对k进行以下操作

function f(k, a, l, r):
   ans := 0
   for i from l to r (inclusive):
      while k is divisible by a[i]:
         k := k/a[i]
      ans := ans + k
   return ans

要求在每次操作执行结束后,输出ans

模拟的做法是\(O(n^2)\),其中 q 次操作的 n 是省不了的,所以显然得从优化每次操作的复杂度入手

每次操作实际是去除 k 的因子中值为 \(a_l\)\(a_r\) 的因子,并按顺序累加这个过程中 k 的值,不难发现有以下性质:

  • 如果在 l 到 r 中有某一段[ L, R ],\(a_i\) 均不是 k 的因数,那么k不会更新,我们可以直接跳过这一段并累加 k * (R - L+1)

  • 如果一个因数在前面出现过,那么在后面再次出现的时候是不生效的,因为 k 已经去除了这个因数。更进一步,这个因数的倍数在后面出现时也不会生效(不过这题用不到这个性质)。结合上一点我们只需要找到 l 到 r 中 k 的每个因数第一次出现的位置,只在这些因数位置更新 k 并累加,然后快进到下一个因数节点并累加中间的 k

关于寻找 l 到 r 中 k 的每个因数第一次出现的位置,我的实现方法是先把数组 a 预处理,用二维 vector 把每个\(a_i\)与其位置装进去( i 装数值,j 装位置),由于从左往右遍历,所以 vec[i][j] 中 vec[i] 的每个元素是递增的(位置),因此我们可以先枚举 k 的每个因数(枚举 i <= sqrt(k) 然后取 i 和 k/i 两个因子),用二分找到其在 [ l , r ] 第一次出现的位置并存下来,将这些位置排序后就能执行性质二中的步骤了(排序是因为当前的位置序列是由枚举 k 的因数产生的,是按因数的大小排序的,而我们需要的是其在数组 a 中的位置排序)

此外,关于上一步的实现,一开始想过直接用multimap找第一次出现的位置,但multimap只能lower_bound因数在1-n第一次出现的位置,没法精确到[ l, r ]之内,而写补题的时候又想到了使用map<<pair<int,int>,int> 同时存下因数和位置,然后直接lower_bound,不过TLE了,估计map对pair的lower_bound是二分找到pair键的first,然后枚举second,在这题会超时,所以还是得搓二分(你就这么懒吗)

#include<bits/stdc++.h>
#define int long long
using namespace std;
int T,n,q;
int x,l,r,k;
int ans;
map<int,int>order;
vector<vector<int> >vec;
vector<int>emp;
void ser(int j) {
    if(vec[j].empty())return;
    int L=0,R=vec[j].size()-1,mid;
    while(L<R) {
        mid=(L+R)/2;
        if(vec[j][mid]>=l)R=mid;
        else L=mid+1;
    }
    if(vec[j][R]>=l&&vec[j][R]<=r) order[vec[j][R]]=j;
    ///else printf("order=%d %d %d %d %d\n",vec[j][R],j,l,r,vec[j].size());
}
signed main()
{
    scanf("%lld",&T);
    while(T--) {
        scanf("%lld%lld",&n,&q);
        vec.assign(1e5+1,emp);
        for(int i=1;i<=n;i++) {
            scanf("%lld",&x);
            vec[x].emplace_back(i);
            //printf("YES");
        }
        for(int i=1;i<=q;i++) {
            ans=0;
            scanf("%lld%lld%lld",&k,&l,&r);
            //if(!order.empty())printf("CHECK");
            for(int j=2;j*j<=k;j++) {
                if(k%j==0) {
                    ser(j);
                    ser(k/j);
                }
            }
            ser(k);
            int last=l;
            //printf("order=%d\n",order.size());
            for(auto var : order) {
                //printf("ans=%d last=%d chu=%d po=%d\n",ans,last,var.first,var.second);
                ans+=(var.first-last)*k;
                while(k%var.second==0) k/=var.second;
                last=var.first;
                //printf("ans=%d k=%d\n",ans,k);
            }
            ans+=(r-last+1)*k;
            printf("%lld\n",ans);
            order.clear();
        }
        vec.clear();
    }
    return 0;
}
/*
想好映射关系
枚举因子的时候记得自身也是自己的因子 
二分条件搞错了,弄清需求 
弄清在二分什么,不要搞错下标的区别了 
*/
posted @ 2025-06-01 20:42  Sonatto  阅读(26)  评论(0)    收藏  举报