AtCoder Beginner Contest 438(ABCDEF)

前言

我不想复习期末,我想加训qwq

一、A - First Contest of the Year

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int d,f;
    cin>>d>>f;

    int cur=f;
    while(cur<=d)
    {
        cur+=7;
    }

    cout<<cur-d<<endl;
}

void init()
{
}

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

根据题意,直接模拟即可。就是从当前天数f开始,每次加7直到超过一年中的d天。此时就是新一年的第一次比赛,所以把上一年的天数d减去就是新一年的第一次比赛。

二、B - Substring 2

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n,m;
    cin>>n>>m;
    string s,t;
    cin>>s>>t;
    s=" "+s;
    t=" "+t;

    int ans=INF;
    for(int l=1;l<=n-m+1;l++)
    {
        int sum=0;
        for(int i=1,j=l;i<=m;i++,j++)
        {
            int sc=s[j]-'0';
            int tc=t[i]-'0';

            sum+=(sc-tc+10)%10;
        }
        ans=min(ans,sum);
    }
    cout<<ans<<endl;
}

void init()
{
}

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

因为整个数据范围不大,所以还是可以直接暴力,那么就是枚举从s串的每个位置开始,计算让后续部分成为t串的代价,那么就是每次取余即可。

三、C - 1D puyopuyo

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    vector<int>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }

    stack<int>stk;
    for(int i=1;i<=n;i++)
    {
        stk.push(i);

        vector<int>t;
        while(t.size()<4&&!stk.empty())
        {
            t.push_back(stk.top());
            stk.pop();
        }

        if(t.size()==4)
        {
            bool ok=true;
            for(int i=0;i<4;i++)
            {
                if(a[t[i]]!=a[t[0]])
                {
                    ok=false;
                    break;
                }
            }

            if(ok)
            {
                continue;
            }
        }

        while(!t.empty())
        {
            stk.push(t.back());
            t.pop_back();
        }
    }

    cout<<stk.size()<<endl;
}

void init()
{
}

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

这个题感觉见过好几次了,不管是在abc还是牛客还是cf,还是挺常见的。

对于这种拼接的问题,可以考虑用栈去模拟整个过程。那么就是每次考察栈里前三个字符,如果全一样就可以拼起来删除,否则就再压回去,等着后续去拼接即可。

四、D - Tail of Snake

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

void solve()
{
    int n;
    cin>>n;
    vector<ll>a(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    vector<ll>b(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>b[i];
    }
    vector<ll>c(n+1);
    for(int i=1;i<=n;i++)
    {
        cin>>c[i];
    }

    vector<ll>best(n+1);
    best[n]=0;
    ll sum=c[n];
    for(int i=n-1;i>=2;i--)
    {
        best[i]=b[i]+max(best[i+1],sum);
        sum+=c[i];
    }

    ll pre=a[1];
    ll ans=0;
    for(int i=2;i<n;i++)
    {
        ans=max(ans,pre+best[i]);
        pre+=a[i];
    }
    cout<<ans<<endl;
}

void init()
{
}

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

其实不难发现,这个题本质上就是让你选择两个划分点x和y,使得三部分的累加和最大,那么其实就是一个使得f(x,y)最大的问题。

对于求f(i,j)的问题,有一个很常见的trick,要么暴力n^2求,要么枚举i快速求j,要么计算贡献。在这个题里,很明显应该是需要枚举i,然后对于每个i快速求跟哪个j组合使得贡献最大。所以对于每个当前的i,需要构建出一个结构,可以做到快速查询当前位置i右侧选择哪个划分点j,使得中间部分b数组累加和和最后部分c数组累加和最大。那么就可以考虑定义best[i]表示从i位置开始往后,选择一个划分点j,使得b数组和c数组累加和最大。那么构建时就可以从后往前枚举,因为b数组至少需要有一个元素,所以对于当前位置i肯定需要选择b[i]。之后,每次考虑分直接在当前位置划分和不在当前位置划分两种情况讨论。若sum表示c数组的后缀和,那么依赖就是best[i+1]和sum的最大值。

构建完以后,就可以考虑从前往后枚举i,若pre表示a数组的前缀和,那么就是统计pre+best[i]的最大值即可。

五、E - Heavy Buckets

该加训倍增了......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

const int MAXN=2e5+5;
const int LIMIT=31;

int n,q;
vector<int>a(MAXN);

vector<vector<int>>stjump(MAXN,vector<int>(LIMIT));
vector<vector<ll>>stsum(MAXN,vector<ll>(LIMIT));

void solve()
{
    cin>>n>>q;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        stjump[i][0]=a[i];
        stsum[i][0]=i;
    }

    for(int p=1;p<=30;p++)
    {
        for(int i=1;i<=n;i++)
        {
            stjump[i][p]=stjump[stjump[i][p-1]][p-1];
            stsum[i][p]=stsum[i][p-1]+stsum[stjump[i][p-1]][p-1];
        }
    }

    int t,x;
    while(q--)
    {
        cin>>t>>x;

        ll ans=0;
        for(int p=30;p>=0;p--)
        {
            if((t>>p)&1)
            {
                ans+=stsum[x][p];
                x=stjump[x][p];
            }
        }
        cout<<ans<<endl;
    }
}

void init()
{
}

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

这个题的建模其实真不难,感觉赛时也就一眼的事,但之后的维护和求解就是个问题了......

举个例子玩一下就可以发现,如果连一条从i到a[i]的边,那么就会形成若干棵基环树。那么不管从哪开始,若干轮之后肯定就是在环上转圈了。之后,对于快速查出走了多少步后到了哪以及收益多少的问题,就可以考虑去倍增构建st表。那么直接初始化stjump[i][0]为a[i],stsum[i][0]为i,然后去倍增,每次从大到小对t拆位计算即可。同时因为是倍增,所以其实过程中根本不用考虑环的问题,根据上一步的位置直接依赖了。

六、F - Sum of Mex

太变态了这个题......

cpp 复制代码
#include <bits/stdc++.h>
using namespace std;

/*   /\_/\
*   (= ._.)
*   / >  \>
*/

/*
*想好再写
*注意审题 注意特判
*不要红温 不要急躁 耐心一点
*WA了不要立马觉得是思路不对 先耐心找反例
*/

#define dbg(x) cout<<#x<<endl;cout<<x<<endl;
#define vdbg(a) cout<<#a<<endl;for(auto x:a)cout<<x<<" ";cout<<endl;
#define YES cout<<"YES"<<endl;return ;
#define Yes cout<<"Yes"<<endl;return ;
#define NO cout<<"NO"<<endl;return ;
#define No cout<<"No"<<endl;return ;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int INF=1e9;
const ll INFLL=1e18;
const int dx[]={-1,1,0,0};
const int dy[]={0,0,-1,1};
const int ddx[]={-2,-1,1,2,2,1,-1,-2};
const int ddy[]={1,2,2,1,-1,-2,-2,-1};

const int MAXN=2e5+5;
 
const int LIMIT=20;
 
vector<int>deep(MAXN);
vector<vector<int>>stjump(MAXN,vector<int>(LIMIT));

vector<int>sz(MAXN);

int n;
 
vector<vector<int>>g(MAXN);
 
int power;
 
int log2()
{
    power=0;
    while((1<<power)<=(n>>1))
    {
        power++;
    }
    return power;
}
 
void dfs(int u,int fa)
{
    deep[u]=deep[fa]+1;
    stjump[u][0]=fa;
 
    for(int p=1;(1<<p)<=deep[u];p++)
    {
        stjump[u][p]=stjump[stjump[u][p-1]][p-1];
    }
 
    sz[u]=1;
    for(int v:g[u])
    {
        if(v!=fa)
        {
            dfs(v,u);
            sz[u]+=sz[v];
        }
    }
}
 
int LCA(int a,int b)
{
    //让a是更深的节点
    if(deep[a]<deep[b])
    {
        swap(a,b);
    }
 
    for(int p=power;p>=0;p--)
    {
        if(deep[stjump[a][p]]>=deep[b])
        {
            a=stjump[a][p];
        }
    }
 
    if(a==b)
    {
        return a;
    }
 
    for(int p=power;p>=0;p--)
    {
        if(stjump[a][p]!=stjump[b][p])
        {
            a=stjump[a][p];
            b=stjump[b][p];
        }
    }
 
    return stjump[a][0];
}

int subtree(int u,int v)
{
    int len=deep[u]-deep[v]-1;
    for(int p=0;p<=power;p++)
    {
        if((len>>p)&1)
        {
            u=stjump[u][p];
        }
    }
    return u;
}

void solve()
{
    cin>>n;
    for(int i=1,u,v;i<=n-1;i++)
    {
        cin>>u>>v;
        u++,v++;
        g[u].push_back(v);
        g[v].push_back(u);
    }

    //trick:对于求f(i,j)的问题,要么暴力n^2求,要么枚举i快速求j,要么计算贡献
    //这道题考虑计算贡献的方法
    //因为是mex,所以对于f(i,j)=k的所有路径,其必然经过0~k-1中的所有点
    //对于路径(i,j),从0开始枚举,当发现第一个没经过的点k时,其mex就已经确定了
    //此时可以统计出所有终止在0的个数c[1],在1的个数c[2],在2的个数c[3]......
    //那么答案就是1*c[0]+2*c[1]+3*c[3]+......
    //又因为在枚举的过程中,每经过一个检查点,f(i,j)都会+1,所以在经过的时候可以直接产生1点贡献
    //那么就只需要统计出经过0的个数k[1],经过1的个数k[2],经过2的个数k[3]......
    //所以答案就是k[1]+k[2]+k[3]+......

    log2();

    dfs(n,0);
    
    //特判所有经过0单点的路径数
    
    //0内部子树里的一点和0外部的一点形成路径
    ll ans=1ll*sz[1]*(n-sz[1]+1);
    
    //从0内部的两棵子树里任选两点,形成经过0的路径
    int cnt=0;
    for(auto v:g[1])
    {
        if(v!=stjump[1][0])
        {
            ans+=1ll*sz[v]*cnt;
            cnt+=sz[v];
        }
    }

    int U=1,V=1,W=1;
    for(int i=2;i<=n;i++)
    {
        int lca=LCA(i,W);
        bool ok=false;

        //特判
        if(i==2)
        {
            ok=true;
            W=lca;
            V=i;

            //默认U的深度更大
            if(deep[U]<deep[V])
            {
                swap(U,V);
            }
        }
        else
        {
            //U-V(W)单链
            if(V==W)
            {
                //状态为U-V-i(W),扩展
                if(lca==i)
                {
                    V=W=i;
                    ok=true;
                }
                else if(lca==W)
                {
                    int a=LCA(U,i);
                    
                    //状态为i-U-V(W),扩展
                    if(a==U)
                    {
                        U=i;
                        ok=true;
                    }
                    //状态为U-i-V,不动
                    else if(a==i)
                    {
                        ok=true;
                    }
                    //状态为U-W(V)-i,改成双链
                    else
                    {
                        V=i;
                        ok=true;
                    }
                }
                //状态为U-V-lca-i,改成双链
                else
                {
                    V=i,W=lca;
                    ok=true;
                }
            }
            //U-W-V双链
            else
            {
                //状态为U-i-W-V,不动
                if(!ok&&lca==W&&LCA(U,i)==i)
                {
                    ok=true;
                }
                //状态为i-U-W-V,扩展
                if(!ok&&lca==W&&LCA(U,i)==U)
                {
                    U=i;
                    ok=true;
                }
                //状态为U-W-i-V,不动
                if(!ok&&lca==W&&LCA(V,i)==i)
                {
                    ok=true;
                }
                //状态为U-W-V-i,扩展
                if(!ok&&lca==W&&LCA(V,i)==V)
                {
                    V=i;
                    ok=true;
                }
            }
        }

        //当前点无法构成路径
        if(!ok)
        {
            break;
        }

        //双链
        if(V!=W)
        {
            //所有合法的路径为
            //从 U的子树 和 V的子树 中任选两个节点
            ans+=1ll*sz[U]*sz[V];
        }
        //单链
        else
        {
            //所有合法的路径为
            //从 U的子树 和 V的外部+除了路径的所有子树 中任选两个点
            //对于第二部分,可以转化为找路径上V下面的那个点的外部
            ans+=1ll*sz[U]*(n-sz[subtree(U,V)]);
        }
    }
    cout<<ans<<endl;
}

void init()
{
}

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

还是这个在D题里用到的trick,对于求f(i,j)的问题,要么暴力n^2求,要么枚举i快速求j,要么计算贡献。又因为这道题前两种可以发现不太可行,所以考虑计算贡献的方法。

因为是mex,所以对于f(i,j)=k的所有路径,其必然经过0~k-1中的所有点。对于路径(i,j),从0开始枚举,当发现第一个没经过的点k时,其mex就已经确定了。此时可以统计出所有终止在0的个数c[1],在1的个数c[2],在2的个数c[3]......,那么答案就是1*c[0]+2*c[1]+3*c[3]+......。又因为在枚举的过程中,每经过一个检查点,f(i,j)都会+1,所以在经过的时候可以直接产生1点贡献。那么就只需要统计出经过0的个数k[1],经过1的个数k[2],经过2的个数k[3]......,所以答案就是k[1]+k[2]+k[3]+......。

那么之后需要先特判经过节点0的路径条数,然后分许多种情况讨论,维护路径的两端点和lca,每次计算路径条数作为贡献。具体的不想多说了太麻烦了,都写注释里了......

总结

先期末,考完开始寒假加训!

END

相关推荐
k***921621 小时前
【c++】多态
java·开发语言·c++
Murphy_3121 小时前
从根上了解一下复指数
算法
Run_Teenage21 小时前
Linux:理解IO,重定向
linux·运维·算法
Cappi卡比21 小时前
C++性能优化
c++
深盾科技21 小时前
C++ 中 std::error_code 的应用与实践
java·前端·c++
你撅嘴真丑21 小时前
素数对 与 不吉利日期
算法
多米Domi01121 小时前
0x3f 第20天 三更24-32 hot100子串
java·python·算法·leetcode·动态规划
wzfj1234521 小时前
Opaque Pointer / Incomplete Type
c++·算法·c
冰西瓜60021 小时前
贪心(四)——拟阵 算法设计与分析 国科大
算法·贪心算法