Codeforces 1017 Div4(ABCDEFG)

发布于:2025-05-19 ⋅ 阅读:(17) ⋅ 点赞:(0)

前言

补题补题补题

一、A. Trippi Troppi

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

void solve()
{
    for(int i=0;i<3;i++)
    {
        string s;
        cin>>s;

        cout<<s[0];
    }
    cout<<endl;
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

签到题,每次输出第一个字符即可,没啥好说的。

二、B. Bobritto Bandito

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

void solve()
{
    int n,m,l,r;
    cin>>n>>m>>l>>r;

    for(int i=n;i>m;i--)
    {
        if(l<0)
        {
            l++;
        }
        else 
        {
            r--;
        }
    }
    cout<<l<<" "<<r<<endl;
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

因为给出一种可能性即可,所以只要从后往前推,左边界小于零就算左侧的,否则就算右边界的即可。

三、C. Brr Brrr Patapim

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

void solve()
{
    int n;
    cin>>n;
    vector<vector<int>>grid(n+1,vector<int>(n+1));
    vector<bool>vis((n<<1)+1);//注意数字的范围是2n!!!
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n;j++)
        {
            cin>>grid[i][j];
            vis[grid[i][j]]=true;
        }
    }

    for(int i=1;i<=(n<<1);i++)
    {
        if(!vis[i])
        {
            cout<<i<<" ";
            break;
        }
    }

    for(int j=1;j<=n;j++)
    {
        cout<<grid[1][j]<<" ";
    }
    for(int i=2;i<=n;i++)
    {
        cout<<grid[i][n]<<" ";
    }

    cout<<endl;
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

观察可得整个矩阵是对角线对称的,所以先统计出第0号数,再沿着边界摸一遍即可。

四、D. Tung Tung Sahur

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

string p,s;
int n,m;

//TLE错解
bool dfs(int i,int j)
{
    if(i>=n&&j<m)
    {
        return false;
    }
    if(i<n&&j>=m)
    {
        return false;
    }
    if(i==n&&j==m)
    {
        return true;
    }

    bool ans=false;
    if(p[i]==s[j])
    {
        ans=ans||dfs(i+1,j+1);
        if(p[i]==s[j+1])
        {
            ans=ans||dfs(i+1,j+2);
        }
    }

    return ans;
}

bool ways1()
{
    return dfs(0,0);
}

bool ways2()
{
    if(n>2*m)
    {
        return false;
    }

    int j=0;
    for(int i=0,cntp,cnts;i<n;i++,j++)
    {
        if(p[i]!=s[j])
        {
            return false;
        }
        if(j>=m)
        {
            return false;
        }

        cntp=1;
        cnts=1;
        while(i+1<n&&p[i]==p[i+1])
        {
            i++;
            cntp++;
        }
        while(j+1<m&&s[j]==s[j+1])
        {
            j++;
            cnts++;
        }
        if(cnts>2*cntp||cnts<cntp)
        {
            return false;
        }
    }

    //边界讨论!!!!!!!!!!
    if(j<m)
    {
        return false;
    }

    return true;
}

string randomString(int n)
{
    string str;
    for(int i=0,tmp;i<n;i++)
    {
        tmp=rand()%2;
        str+=(tmp?"L":"R");
    }
    return str;
}

void solve()
{
    cin>>p>>s;

    n=p.length();
    m=s.length();

    //对数器
    // srand(time(0));

    // int testTime=10000;
    // for(int i=0;i<testTime;i++)
    // {
    //     int n=rand()%10000+1;
    //     int m=rand()%20000+1;

    //     p=randomString(n);
    //     s=randomString(m);

    //     bool ans1=ways1();
    //     bool ans2=ways2();
    //     if(ans1!=ans2)
    //     {
    //         cout<<"Wrong!"<<endl;
    //         cout<<p<<endl<<s<<endl;
    //     }
    // }
    // cout<<"Over"<<endl;

    cout<<(ways2()?"YES":"NO")<<endl;
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

ways1就是双指针暴力递归,然后喜提TLE。

然后再观察两个字符串的特征,可以发现可以根据p串里听到的声音在s串中分组,那么对于任意一组,听到的声音必然全和敲击的位置相同,且听到的次数必然介于一倍敲击次数到两倍敲击次数之前。所以那就设两个指针,然后进行分组滑动即可。重点要注意这些边界讨论,因为这个爆了两次WA……

五、E. Boneca Ambalabu

#include <bits/stdc++.h>
using namespace std;

//每一位相互独立 -> 拆位计算,最后计算总贡献

typedef long long ll;

void solve()
{
    int n;
    cin>>n;
    vector<int>nums(n);
    vector<int>cnts(32,0);
    for(int i=0;i<n;i++)
    {
        cin>>nums[i];
        for(int j=0;j<32;j++)
        {
            cnts[j]+=((nums[i]>>j)&1);//统计这位上有几个1
        }
    }

    ll ans=0;
    for(int i=0;i<n;i++)
    {
        ll sum=0;
        for(int j=0;j<32;j++)
        {
            int bit=(nums[i]>>j)&1;
            //计算贡献
            if(bit==0)
            {
                sum+=cnts[j]*((ll)1<<j);//开long long!!!
            }
            else
            {
                sum+=(n-cnts[j])*((ll)1<<j);//开long long!!!
            }
        }
        ans=max(ans,sum);
    }

    cout<<ans<<endl;
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

这下真不开long long见祖宗了……

这个题就提供了一种新思路,对于位运算相关的题,若每位在计算贡献时相互独立,那么就可以考虑进行拆位,将所有数的同一位累加起来,最后计算总贡献。

具体实现就是用个cnts表存所有数在该位上1的数量,然后只需要遍历一遍枚举ak即可。对于每个ak,就是取ak每一位的数字,若是0,那就只有和1异或得到1才能对累加和产生贡献,所以就从cnts数组里拿个数,再乘以该位的权值。若是1那就找0的个数即可。

六、F. Trulimero Trulicina

#include <bits/stdc++.h>
using namespace std;

//注意:要求三是指上下左右的数都不相同
//思路:1.若m%k!=0,则从上往下从左往右顺着1~k填即可
//      横向必满足,纵向两数之间必差m个数,又因为m%k!=0,所以必不同
//      2.若m%k==0,就考虑每行分成m/k个区域,每个区域顺着填,下一行往前移一个填
//        e.g.当m=9,k=3 -> 123 123 123
//                         231 231 231
//                         123 123 123
//                             ……

typedef long long ll;

int n,m,k;

void solve()
{
    cin>>n>>m>>k;

    vector<vector<int>>grid(n,vector<int>(m));
    
    if(m%k!=0)
    {
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                grid[i][j]=(m*i+j)%k+1;
            }
        }
    }
    else
    {
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<m;j++)
            {
                grid[i][j]=i%2==0?j%k+1:(j+1)%k+1;
            }
        }
    }

    for(int i=0;i<n;i++)
    {
        for(int j=0;j<m;j++)
        {
            cout<<grid[i][j]<<" ";
        }
        cout<<endl;
    }
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

这个构造题的思路太逆天了……

注意到(注意力惊人),若m%k不等于0,那直接从上往下从左往右顺着填1~k即可。这样的话左右肯定不相同,而上下的话观察可得两数中间正好差k个数,又因为m%k不等于0,所以上下必不可能相等。若m%k等于0,那就考虑将列分成m/k个区域,第一行每个区域从1~k顺着填,第二行把1~k往前移一位,按照2,3,4~k,1这个顺序填,第三行再从1~k,之后轮换两种填法即可。(逆天思路)

七、G. Chimpanzini Bananini

#include <bits/stdc++.h>
using namespace std;

//纯纯数据结构设计题
//思路:同时维护正序和、逆序和,并用变量记录是否处于reverse状态

typedef long long ll;
typedef pair<int,int> pii;

void solve()
{
    int q;
    cin>>q;

    deque<int>nums;
    bool rev=false;//记录是否处于reverse状态
    ll sum1=0;//正序累加和
    ll sum2=0;//逆序累加和
    ll sum=0;//数字累加和
    ll head1=1;//正序的头下标 -> 初始值不影响最后结果,能修正回来
    ll head2=1;//逆序的头下标

    auto put=[&](bool dir,int k){
        sum+=k;
        if(!dir)//尾插
        {
            //e.g.      k=6
            //操作前:   
            //          num:   5 1 2 3 4
            //          idx1:  0 1 2 3 4
            //          idx2:  4 3 2 1 0
            //          idx:   1 2 3 4 5
            //          nums.size()=5,head1=0,head2=0
            //操作后:  
            //          num:   5 1 2 3 4  6
            //          idx1:  0 1 2 3 4  5
            //          idx2:  4 3 2 1 0 -1
            //          idx:   1 2 3 4 5  6
            //          nums.size()=6,head1=0,head2=1,sum1+=(0+5)*6,sum2+=(-1)*6
            sum1+=(head1+nums.size())*k;//
            sum2+=(--head2)*k;
            nums.push_back(k);
        }
        else
        {
            sum1+=(--head1)*k;
            sum2+=(head2+nums.size())*k;
            nums.push_front(k);
        }
    };

    while(q--)
    {
        int op;
        cin>>op;
        if(op==1)///旋转
        {
            if(!rev)//正序
            {
                int x=nums.back();
                nums.pop_back();
                sum-=x;

                //操作前:   
                //          num:   1 2 3 4 5
                //          idx1:  1 2 3 4 5
                //          idx2:  5 4 3 2 1
                //          idx:   1 2 3 4 5
                //          nums.size()=5,head1=1,head2=1
                //操作后:  
                //          num:   1 2 3 4 
                //          idx1:  1 2 3 4
                //          idx2:  5 4 3 2
                //          idx:   1 2 3 4
                //          nums.size()=4,head1=1,head2=2,sum1-=(1+4)*5,sum2-=1*5
                sum1-=(head1+nums.size())*x;
                sum2-=head2*x;
                head2++;//逆序头左移

                //操作前:   
                //          num:   1 2 3 4
                //          idx1:  1 2 3 4
                //          idx2:  5 4 3 2
                //          idx:   1 2 3 4
                //          nums.size()=4,head1=1,head2=2
                //操作后:  
                //          num:   5 1 2 3 4 
                //          idx1:  0 1 2 3 4
                //          idx2:  6 5 4 3 2
                //          idx:   1 2 3 4 5
                //          nums.size()=5,head1=0,head2=2,sum1+=0*5,sum2+=(2+4)*5
                put(true,x);//头插
            }
            else
            {
                int x=nums.front();
                nums.pop_front();
                sum-=x;
                //操作前:   
                //          num:   1 2 3 4 5
                //          rev:   5 4 3 2 1
                //          idx1:  1 2 3 4 5
                //          idx2:  5 4 3 2 1
                //          idx:   1 2 3 4 5
                //          nums.size()=5,head1=1,head2=1
                //操作后:  
                //          num:   2 3 4 5
                //          rev:   5 4 3 2
                //          idx1:  2 3 4 5
                //          idx2:  4 3 2 1
                //          idx:   1 2 3 4
                //          nums.size()=4,head1=2;head2=1,sum1-=1*1,sum2-=(1+4)*1
                sum2-=(head2+nums.size())*x;
                sum1-=head1*x;
                head1++;

                //操作前:   
                //          num:   2 3 4 5
                //          rev:   5 4 3 2
                //          idx1:  2 3 4 5
                //          idx2:  4 3 2 1
                //          idx:   1 2 3 4
                //          nums.size()=4,head1=2,head2=1
                //操作后:  
                //          num:   2 3 4 5 1
                //          rev:   1 5 4 3 2
                //          idx1:  2 3 4 5 6
                //          idx2:  4 3 2 1 0
                //          idx:   1 2 3 4 5
                //          nums.size()=5,head1=2;head2=0,sum1+=(2+4)*1,sum2+=0*1
                put(false,x);//尾插
            }
        }
        else if (op==2)//反转
        {
            rev=!rev;
        }
        else    //添加
        {
            int k;
            cin>>k;
            put(rev,k);
        }

        //输出时,因为head会因为put小于,所以要加上1到head的距离修正
        //e.g.队列数字:   6  1  2  3  4  5
        //    维护下标:  -1  0  1  2  3  4    head1=-1
        //    实际下标:   1  2  3  4  5  6
        //              sum=1+2+3+4+5+6=21
        //              sum1=-6+0+2+6+12+20=34
        //              ans=6+2+6+12+20+30=76
        //          所以ans=sum1+sum*(1-(head1))=34+21*2=76
        if(!rev)
        {
            cout<<sum1+sum*(1-head1)<<endl;
        }
        else
        {
            cout<<sum2+sum*(1-head2)<<endl;
        }
    }
}

int main()
{
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int t=1;
    cin>>t;
    while(t--)
    {
        solve();    
    }
    return 0;
}

这个题感觉已经不能用逆天来形容了……

因为必须实现每个操作的时间复杂度都为常数时间,所以就得考虑设计多个数据结构,用空间换时间。

观察可得,因为其中有反转操作,所以考虑同时维护正序指标sum1和逆序指标sum2,再用一个变量记录当前状态是否为反转,最后输出时直接根据状态输出对应指标即可。

接着就需要考虑将旋转和添加操作转化成只对sum1和sum2两头进行操作,而不对中间值操作,最后输出答案的时候再通过别的方法一步修正对。思路就是去修改正序和逆序的下标,若当前状态是正序,在旋转时就是先让sum1减去下标乘以最后一个数。而在头部插入这个数时,因为要尽可能减小对中间值的影响,所以考虑不修改中间值的下标,而是让头部下标head1-1乘以这个数。与此同时,还需要修改逆序指标sum2,因为此时尾部元素在逆序里为头部,所以就是让sum2减去head2乘以该数,同样因为要减小对中间值的影响,所以考虑让头部下标head2+1。之后在正序头部插入这个数时,就是让sum2加上尾部下标,即head2加个数乘以这个数。逆序状态的思路类似。(要命了)

所以可以看出,还需要用一个双端队列deque来时刻维护正常的顺序,以及方便快速查出个数来通过head求尾部下标。

最后就是修正答案的部分。观察例子可以发现,不管是sum1还是sum2,下标的改变是与ans不一致的根本原因,所以考虑从下标入手进行修正。而进一步观察可以发现,此时每个数和正确值的差值就是下标偏移量乘以自己。所以通过求这个偏移量,即正确下标1到head的距离,再乘以所有数的累加和sum,再加上sum1或sum2就是正确答案。

总结

H题就是拼尽全力无法战胜了,学的还是不够呜呜……

END