AtCoder Beginner Contest 357

ABC357总结

AtCoder Beginner Contest 357

A – Sanitize Hands

翻译

有一瓶消毒剂,正好可以消毒

\(M\)

双手。


\(N\)

名外星人陆续前来消毒双手。


\(i\)

个外星人(

\(1 \leq i \leq N\)

)有

\(H_i\)

只手,想把所有的手都消毒一次。

请计算有多少个外星人可以给所有的手消毒。

在这里,即使开始时没有足够的消毒剂给一个外星人的所有手消毒,他们也会用完剩余的消毒剂。

分析

直接模拟即可。

code

#include <bits/stdc++.h>
using namespace std;
const int N=105;
int n,m,ans;
int h[N];
int main ()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) 
    {
        cin>>h[i];
        m-=h[i];
        if(m>=0) ans++;
        else m=0;
    }
    cout<<ans;
    return 0;
}

B – Uppercase and Lowercase

翻译

给你一个由小写和大写英文字母组成的字符串

\(S\)



\(S\)

的长度是奇数。

如果

\(S\)

中大写字母的数量大于小写字母的数量,请将

\(S\)

中的所有小写字母转换为大写字母。

否则,将

\(S\)

中的所有大写字母转换为小写字母。

分析

依旧是模拟,先统计

\(S\)

中小写和大写字母的个数进行比较,再根据比较结果进行修改。

code

#include <bits/stdc++.h>
using namespace std;
const int N=105;
string s;
int main ()
{
    cin>>s;
    int a=0,b=0;
    for(int i=0;i<s.size();i++)
    {
        if(s[i]>='a'&&s[i]<='z') a++;
        else if(s[i]>='A'&&s[i]<='Z') b++;
    }
    int st=0;
    if(a>b) st=1;
    for(int i=0;i<s.size();i++)
    {
        if(st)
        {
            if(s[i]>='A'&&s[i]<='Z') cout<<(char)(s[i]-'A'+'a');
            else cout<<s[i];            
        }
        else 
        {
            if(s[i]>='a'&&s[i]<='z') cout<<(char)(s[i]-'a'+'A');
            else cout<<s[i];
        }
    }
    return 0;
}

C – Sierpinski carpet

翻译

对于一个非负整数

\(K\)

,我们定义一个

\(K\)

级地毯如下:


  • \(0\)

    级地毯是由一个黑色单元格组成的

    \(1 \times 1\)

    网格。
  • 对于

    \(K>0\)

    来说,

    \(K\)

    级地毯是一个

    \(3^K \times 3^K\)

    网格。当这个网格被分成九个

    \(3^{K-1} \times 3^{K-1}\)

    块时:

    • 中央区块完全由白色单元格组成。
    • 其他八个区块是

      \((K-1)\)

      级地毯。

“#” 为黑色单元格,”.” 为白色单元格。

给你一个非负整数

\(N\)



请按照指定格式打印

\(N\)

级地毯。

分析

根据题意,

\(K\)

极地毯(

\(K>1\)

)都是可以由第

\(K-1\)

极地毯拼成的。

可以选择递推或递归来构造。

值得一提的是,不能直接打表,

\(K\)

极地毯出现在程序中的话文件大小就超过限制了。

下面是递推的做法。

code

#include <bits/stdc++.h>
using namespace std;
const int N=5005;
int n;
char s[10][10]={{"###"},{"#.#"},{"###"}};
char a[N][N];
int main ()
{
    cin>>n;
    int m=1;
    for(int i=0;i<3;i++)
        for(int j=0;j<3;j++)
            a[i][j]=s[i][j];
    for(int t=1;t<=n;t++)
    {
        m*=3;
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<3;j++) 
                for(int k=0;k<m;k++)
                    a[i][k+j*m]=a[i][k];
        }
        for(int i=0;i<m;i++)
        {
            for(int k=0;k<m;k++) a[i+m][k]=a[i][k];
            for(int k=0;k<m;k++) a[i+m][k+m]='.';
            for(int k=0;k<m;k++) a[i+m][k+2*m]=a[i][k];
        }
        for(int i=0;i<m;i++)
        {
            for(int j=0;j<3;j++) 
                for(int k=0;k<m;k++)
                    a[i+2*m][k+j*m]=a[i][k];
        }
    }
    for(int i=0;i<m;i++)
    {
        for(int j=0;j<m;j++) cout<<a[i][j];
        cout<<"\n";
    }
    return 0;
}

D – 88888888

翻译

对于正整数

\(N\)

,设

\(V_N\)

是由

\(N\)

恰好连接

\(N\)

次所组成的整数。

更确切地说,把

\(N\)

看作一个字符串,连接它的

\(N\)

份,并把结果看作一个整数,得到

\(V_N\)



例如,

\(V_3=333\)



\(V_{10}=10101010101010101010\)



\(V_N\)

除以

\(998244353\)

的余数。

分析

这是比较毒瘤的一道题了。

对于

\(n\)

它总共有

\(k\)

位数字,设

\(m=10^k\)

,可以发现,两个

\(n\)

拼起来就是

\(n+n*m\)

显然,它是一个等比数列。

根据等比数列求和公式可知:

分子部分可以直接用快速幂求解,而分母则需要用到逆元,模数是质数,可以直接用费马小定理求逆元。

复杂度是

\(log(n)\)

级别的。

其中需要注意的是,

\(m\)

是可以直接取模后计算的,但是

\(n\)

会出现在指数上,不能先取模。

在指数上的

\(n\)

要取模应该模以模数

\(mod\)

的欧拉函数,也就是

\(mod-1\)

code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=998244353;
ll qpow(ll x,ll y)
{
    ll ret=1ll;
    while(y)
    {
        if(y&1) ret=(ret*x)%mod;
        x=(x*x)%mod;
        y>>=1;
    }
    return ret;
}
string s;
ll n,m=1;
int main ()
{
    cin>>s;
    int len=s.size();
    for(int i=0;i<len;i++) n*=10,n+=s[i]-'0',m*=10,m%=mod;
    m%=mod;
    ll ans=n%mod;
    ans=(ans%mod*(qpow(m,n%(mod-1))-1))%mod;
    ans=(ans%mod*qpow(m-1,mod-2))%mod;
    cout<<ans;
    return 0;
}

E – Reachability in Functional Graph

翻译

有一个有向图,图中有

\(N\)

个顶点,编号为

\(1\)



\(N\)

,有

\(N\)

条边。

每个顶点的出度为

\(1\)

,顶点

\(i\)

的边指向顶点

\(a_i\)



计算有多少对顶点

\((u, v)\)

使得顶点

\(v\)

可以从顶点

\(u\)

出发到达。

这里,如果存在长度为

\(K+1\)

的顶点序列

\(w_0, w_1, \dots, w_K\)

且满足以下条件,则顶点

\(v\)

可以从顶点

\(u\)

到达。其中,如果

\(u = v\)

总是可达的。


  • \(w_0 = u\)

    .

  • \(w_K = v\)

    .
  • 每个

    \(0 \leq i \lt K\)

    都有一条从顶点

    \(w_i\)

    到顶点

    \(w_{i+1}\)

    的边。

分析

每个点出度都为

\(1\)

,共有

\(n\)

条边,可以发现这个图是一个基环树,而且是内向树。

从某一个节点出发的路径中,必定是包含了一个环的,或者说每条路径都是以环结束的,且第一个到达的环的节点就是环的初始点。

对于一个环来说,环中的每一个节点能到达的只有环中的点,每个点对答案的贡献就是环的节点数。



\(f[x]\)

为从点

\(x\)

出发能到达的节点数量。

例如

\(f[1]=3\)



\(f[2]=3\)



\(f[3]=3\)

而在环外部的点,它必然能且只能到达一个环。从环的起始点开始往回走,依次统计节点贡献,每次加上自己。

例如

\(f[4]=f[1]+1\)



\(f[5]=f[4]+1\)

也就是说,首先要找到环,将环内部节点的

\(f\)

初始化为环的节点数。再进行统计,用记忆化搜索或者拓扑排序都是可以的。

因为一条路径必然以环结尾,可以选一条路一直走下去,沿途标记路径上的节点。碰到已经标记了的节点时,判断是不是当前路径的标记。如果不是,说明这条路结尾的环已经被处理过了;如果是,说明找到了环的初始点,此时需要再绕着环走一圈,统计环的节点数,再走一圈,将环初始化。

最后记忆化搜索,进行统计。

code

#include <bits/stdc++.h>
using namespace std;
const int N=2e5+5;
typedef long long ll;
int n;
int a[N],f[N];
int vis[N];
int dfs(int u)
{
    if(f[u]) return f[u];
    return f[u]=dfs(a[u])+1;
}
int main ()
{
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++)
    {
        if(vis[i]) continue;
        int u=i;
        while(1)
        {
            if(vis[u])
            {
                if(vis[u]==i)
                {
                    int v=a[u],cnt=1;
                    while(v!=u)
                    {
                        cnt++;
                        v=a[v];
                    }          
                    f[u]=cnt;
                    v=a[u];
                    while(v!=u)
                    {
                        f[v]=cnt;
                        v=a[v];
                    }        
                }
                break;
            }
            vis[u]=i;
            u=a[u];
        }
    }
    ll ans=0;
    for(int i=1;i<=n;i++) ans+=dfs(i);
    cout<<ans;
    return 0;
}

F – Two Sequence Queries

翻译

给你长度为

\(N\)

的序列

\(A=(A_1,A_2,\ldots,A_N)\)



\(B=(B_1,B_2,\ldots,B_N)\)


您还得到了

\(Q\)

个查询,需要按顺序处理。

查询有三种类型:


  • 1 l r x

    : 将

    \(A_l, A_{l+1}, \ldots, A_r\)

    中的每一个元素加上

    \(x\)


  • 2 l r x

    : 将

    \(B_l, B_{l+1}, \ldots, B_r\)

    中的每一个元素加上

    \(x\)


  • 3 l r

    : 输出

    \(\displaystyle\sum_{i=l}^r (A_i\times B_i) \mod 998244353\)

分析

一道明显的线段树,需要维护两个数组,处理两个懒标记。

需要维护的元素是:

\(A\)

数组的和

\(x\)



\(B\)

数组的和

\(y\)

,还有

\(v=\displaystyle\sum_{i=l}^r (A_i\times B_i)\)

,其实都是和。



  • \(A\)

    中每个元素加上

    \(k\)



    \(x\)

    就要加上

    \(k \times len\)



    \(len\)

    为区间长度。

    \(y\)

    不会被改变,而

    \(v\)

    改变后的

    \(v’\)



  • \(B\)

    中每个元素加上

    \(k\)

    ,同理,

    \(y’=y+k \times len\)



    \(x\)

    不变。

code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+5,mod=998244353;
int n,q;
ll v1[N],v2[N];
struct node
{
    int l,r;
    ll x,y;
    ll t1,t2;
    ll v;
}a[N<<2];
#define L a[u].l
#define R a[u].r 
#define lc (u<<1)
#define rc (u<<1|1) 
#define mid (L+R>>1) 
void pushup(int u)
{
    a[u].x=(a[lc].x+a[rc].x)%mod;
    a[u].y=(a[lc].y+a[rc].y)%mod;
    a[u].v=(a[lc].v+a[rc].v)%mod;
}
void make(int u,ll t1,ll t2)
{
    if(t1)
    {
        t1%=mod;
        a[u].x=(a[u].x+(t1%mod*(R-L+1))%mod)%mod;
        a[u].v=(a[u].v+(a[u].y*t1%mod))%mod;
    }
    if(t2)
    {
        t2%=mod;
        a[u].y=(a[u].y+(t2%mod*(R-L+1))%mod)%mod;
        a[u].v=(a[u].v+(a[u].x*t2%mod))%mod;
    }
    a[u].t1+=t1,a[u].t2+=t2;
    a[u].t1%=mod,a[u].t2%=mod;
}
void pushdown(int u)
{
    make(lc,a[u].t1,a[u].t2);
    make(rc,a[u].t1,a[u].t2);
    a[u].t1=a[u].t2=0;
}
void build(int u,int l,int r)
{
    L=l,R=r;
    if(l==r) a[u].x=v1[l]%mod,a[u].y=v2[l]%mod,a[u].v=((v1[l]%mod)*(v2[l]%mod))%mod;
    else 
    {
        build(lc,l,mid);
        build(rc,mid+1,r);
        pushup(u);
    }
}
void update1(int u,int l,int r,int x)
{
    if(l<=L&&R<=r) make(u,x,0);
    else 
    {
        pushdown(u);
        if(mid>=l) update1(lc,l,r,x);
        if(mid<r) update1(rc,l,r,x);
        pushup(u);
    }
}
void update2(int u,int l,int r,int x)
{
    if(l<=L&&R<=r) make(u,0,x);
    else 
    {
        pushdown(u);
        if(mid>=l) update2(lc,l,r,x);
        if(mid<r) update2(rc,l,r,x);
        pushup(u);
    }
}
ll query(int u,int l,int r)
{
    if(l<=L&&R<=r) return a[u].v%mod;
    else 
    {
        pushdown(u);
        ll ret=0;
        if(mid>=l) ret+=query(lc,l,r);
        if(mid<r) ret+=query(rc,l,r);
        return ret%mod;
    }
}
void dfs(int u)
{
    cout<<L<<" "<<R<<"\n";
    cout<<a[u].x<<" "<<a[u].y<<" "<<a[u].v<<"\n";
    cout<<a[u].t1<<" "<<a[u].t2<<"\n";
    cout<<"___________________\n";
    if(L==R) return ;
    dfs(lc),dfs(rc);
}
int main ()
{
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    cin>>n>>q;
    for(int i=1;i<=n;i++) cin>>v1[i];
    for(int i=1;i<=n;i++) cin>>v2[i];
    build(1,1,n);
    int op,l,r,x;
    while(q--)
    {
        // dfs(1);
        cin>>op>>l>>r;
        if(op==1) 
        {
            cin>>x;
            update1(1,l,r,x%mod);
        }
        else if(op==2)
        {
            cin>>x;
            update2(1,l,r,x%mod);
        }
        else cout<<query(1,l,r)<<"\n";
    }
    return 0;
}

未经允许不得转载:大白鲨游戏网 » AtCoder Beginner Contest 357