[Atcoder Grand Contest 003] Tutorial

Link:

AGC003 传送门

A:

判断如果一个方向有,其相反方向有没有即可

#include <bits/stdc++.h>

using namespace std;
char s[1005];
map<char,bool> mp;
int main()
{
    scanf("%s",s);
    for(int i=0;i<strlen(s);i++) mp[s[i]]=true;
    if(mp['S']==mp['N']&&mp['E']==mp['W']) puts("Yes");
    else puts("No");
    return 0;
}
Problem A

B:

贪心最大化利用即可

#include <bits/stdc++.h>

using namespace std;
int n,dat[100005];
long long res=0;
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&dat[i]);
    for(int i=1;i<=n-1;i++)
    {
        res+=dat[i]/2;dat[i]%=2;
        if(dat[i+1]>dat[i]) dat[i+1]-=dat[i],res+=dat[i];
    }
    res+=dat[n]/2;
    printf("%lld",res);
    return 0;
}
Problem B

C:

可以将两个操作转换为:

1、交换两个相邻的数

2、交换两个中间相间一个的数

由于只要求操作1的最小数,可以发现调整好所有数的序号的奇偶性就能用操作2完成任务

因此求出原序号为奇数,排序后序号为偶数的数的个数即可(原偶现奇的个数与其相同)

#include <bits/stdc++.h>

using namespace std;
int n,dat[100005],id[100005];

bool cmp(int a,int b)
{return dat[a]<dat[b];}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&dat[i]),id[i]=i;
    sort(id+1,id+n+1,cmp);
    int res=0;
    for(int i=1;i<=n;i++)
        if((id[i]&1)&&!(i&1)) res++;
    printf("%d",res);
    return 0;
}
Problem C

D:

思路类似于 [Codeforces#480 D] ,只不过将平方改成了立方

要判断两个数的积为立方,可以将每个数的各个质因数次幂$mod 3$化简后再判断

于是我们求出每个数化简后的结果,统计出现次数,同时求出化简后的数的补数

最后在一个数及其补数中选择个数更多的那一组即可(特判:如果化简后为1只能算一个)

实现的难点在质因数分解……

由于$nle 10^{10}$,肯定不能求出$n$以内的所有质数

于是要先将$10^{frac{10}{3}}$内的质因数分解掉,保证剩下的质因数的次幂不会超过2次

实际上,剩下的数只有3种情况,分类讨论就好了:

1、$p^2$ 2、$p$ 3、$p*q$  ($p,q$为质数)

这样就能在$O(1)$的时间内计算大于$10^{frac{10}{3}}$的质因数的化简结果及其补数了

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN=1e5+10;
int n,pri[3005],vis[3005],tot,res=0;
ll x[MAXN],y[MAXN],t;
map<ll,int> mp,avl;

ll cube(int a){return 1ll*a*a*a;}
ll sqr(int a){return 1ll*a*a;}
int main()
{
    for(int i=2;i<=3000;i++)
    {
        if(!vis[i]) pri[++tot]=i;
        for(int j=1;j<=tot&&i*pri[j]<=3000;j++)
        {
            vis[i*pri[j]]=1;
            if(i%pri[j]==0) break;
        }
    }
    
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&t);x[i]=y[i]=1;
        for(int j=1;cube(pri[j])<=t;j++)
        {
            int cnt=0;
            while(t%pri[j]==0) t/=pri[j],cnt++;
            if(cnt%3==1) x[i]*=pri[j],y[i]*=sqr(pri[j]);
            else if(cnt%3==2) x[i]*=sqr(pri[j]),y[i]*=pri[j];
        }
        x[i]*=t;mp[x[i]]++;
        y[i]*=sqr(sqrt(t))==t?sqrt(t):sqr(t);
    }
    
    for(int i=1;i<=n;i++)
        if(!avl[x[i]])
        {
            avl[x[i]]=avl[y[i]]=1;
            if(x[i]==1) res++;
            else res+=max(mp[x[i]],mp[y[i]]);
        }
    printf("%d",res);
    return 0;
}
Problem D

如果操作为除法/取模,且要枚举$n$个除数/模数时,

学会较小数暴力,较大数分类讨论的方式(如 [ARC060 D]

E:

首先可以将$dat$序列改为单调递增的,其他项明显是多余的

通过观察发现每一项可以由之前的项重复、组合而成(可能留有小于$len[1]$的后缀)

于是我们令$cnt[i]$为第$i$项被重复的总次数,对于从后往前每一个$i$:

每次二分找到最大的小于$dat[i]$的$dat[j]$,那么第$j$项可以多重复$dat[i]/dat[j]*cnt[i]$次

接下来将$dat[i]$mod$dat[j]$,寻找下一个重复项

由于一个数$mod$比自己小的数明显最多进行$log(n)$次,因此复杂度为$O(n*log(n))$

由于每项在整块整块分解后仍可能有小于$len[1]$的后缀,需要另外统计

为了保证时间复杂度,且由于该后缀一定连续,因此差分统计就好了

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MAXN=1e5+10;
int n,q,tot;
ll res[MAXN],dat[MAXN],cnt[MAXN];

int Query(ll x,int r)
{
    int ret=-1,l=0;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(dat[mid]<=x) ret=mid,l=mid+1;
        else r=mid-1;
    }
    return ret;
}
int main()
{
    scanf("%d%d",&n,&q);
    for(int i=1;i<=q;i++) scanf("%lld",&dat[i]);
    dat[0]=n;//如果最小值大于n要添加n 
    for(int i=1;i<=q;i++)
    {
        while(tot>=0&&dat[i]<dat[tot]) tot--;
        dat[++tot]=dat[i];
    }
    
    cnt[tot]=1;
    for(int i=tot;i>=0;i--)
    {
        ll k=dat[i],pos=Query(k,i-1);
        while(pos>=0)
        {
            cnt[pos]+=(k/dat[pos])*cnt[i];
            k=k%dat[pos];pos=Query(k,pos-1);
        }
        res[1]+=cnt[i];res[k+1]-=cnt[i];
    }
    for(int i=1;i<=n;i++)
        res[i]+=res[i-1],printf("%lld
",res[i]);
    return 0;
}
Problem E

F:

%%%LCA的题解:https://loj.ac/article/189

一眼看上去完全不可做的题(好像对我而言所有题都这样

但先进行大的分类后其实会发现明朗很多

令矩阵中有色点数为$cnt$,上下/左右相邻的有色点对数分别为$pv,ph$

令原矩阵上下/左右拼接后能连通的列数/行数分别为$sv,sh$

1、如果$sv,sh$都大于零,明显全部连通

2、如果$sv,sh$都为零,明显答案为$cnt^{k-1}$

3、如果$sv,sh$中恰有一个大于零,需要递推:

将最终矩阵中每一个初始矩阵视为一个点,两点相连通则视为有一条边

由于最终状态不可能有环,求连通块的个数,其实就是求点数与边数的差

根据上面求出的量,可以写出如下的递推式:

$V_k=V_{k-1}*cnt$

$E_k=E_{k-1}*cnt+pv(h)*sv(h)$

对于这样的式子矩乘优化就好了

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
const int MOD=1e9+7,MAXN=1005;
struct matrix
{
    ll p[5][5];
    matrix(){memset(p,0,sizeof(p));}
    void init(){p[0][0]=p[1][1]=1;}
    friend matrix operator * (const matrix &a,const matrix &b)
    {
        matrix ret;
        for(int i=0;i<2;i++)
            for(int j=0;j<2;j++)
                for(int k=0;k<2;k++)
                    (ret.p[i][j]+=a.p[i][k]*b.p[k][j]%MOD)%=MOD;
        return ret;
    }
    friend matrix quick_pow(matrix a,ll b)
    {
        matrix ret;ret.init();
        for(;b;b>>=1,a=a*a)
            if(b&1) ret=ret*a;
        return ret;
    }
};

char s[MAXN][MAXN];
ll k;
int n,m,cnt,pv,ph,sv,sh;
ll quick_pow(ll a,ll b)
{
    ll ret=1;
    for(;b;b>>=1,a=a*a%MOD)
        if(b&1) ret=ret*a%MOD;
    return ret;
}

int main()
{
    scanf("%d%d%lld",&n,&m,&k);
    if(k<=1) return puts("1"),0;
    for(int i=1;i<=n;i++) scanf("%s",s[i]+1);
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++)
        if(s[i][j]=='#')
        {
            cnt++;
            pv+=(s[i][j]==s[i+1][j]);
            ph+=(s[i][j]==s[i][j+1]);
        }
    for(int i=1;i<=m;i++) sv+=(s[1][i]=='#'&&s[n][i]=='#');
    for(int i=1;i<=n;i++) sh+=(s[i][1]=='#'&&s[i][m]=='#');
    
    if(sv&&sh) return puts("1"),0;
    else if(!sv&&!sh) return printf("%lld",quick_pow(cnt,k-1)),0;
    else
    {
        matrix t;t.p[0][0]=cnt;t.p[1][0]=(sv?pv:0)+(sh?ph:0);t.p[1][1]=sv+sh;
        t=quick_pow(t,k-1);printf("%lld",(t.p[0][0]-t.p[1][0]+MOD)%MOD);
    }
    return 0;
}
Problem F
原文地址:https://www.cnblogs.com/newera/p/9285356.html