2021CCPC网络预选赛

比赛链接
A,B,F,I,K。前面过得比较顺,B读懂题就做了;K很快写了DP,也注意了打完最后一颗子弹后面就不能要了,但一直WA;最后才发现还要注意在哪里结束最后一颗子弹(不一定是打过的最后一列),加了一维过了。

A

分析:

签到题。偶数奇数分别算即可。

代码如下
#include<iostream>
using namespace std;
int T,n;
int even(int l,int r)
{
    int len=r-l+1,ret=len/2;
    if((len%2)&&!(l%2))ret++;
    return ret;
}
int odd(int l,int r)
{
    int len=r-l+1,ret=len/2;
    if((len%2)&&(l%2))ret++;
    return ret;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        int ans=n-((n+2)/2)+1+odd((n+2)/3,n);
        printf("%d
",ans);
    }
    return 0;
}

B

分析:

由题得到一个循环节长度为lcm的字符串。要找最短的包含所有字符的子串,用双指针即可。注意找的范围应该是循环节的两倍长度,因为可以在两节之间取答案。

代码如下
#include<iostream>
#include<cstring>
using namespace std;
int const N=105,M=6e6+5;
int T,n,a[M],l[N],L,p[N],cnt[30];
char s[N][15];
bool vis[30];
int gcd(int x,int y){return (!y)?x:gcd(y,x%y);}
int lcm(int x,int y)
{
    int g=gcd(x,y);
    return x/g*y;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n); L=1;
        memset(cnt,0,sizeof cnt); memset(vis,0,sizeof vis);
        for(int i=1;i<=n;i++)
        {
            scanf("%s",s[i]+1);
            l[i]=strlen(s[i]+1); L=lcm(L,l[i]); p[i]=1;
        }
        // printf("lcm=%d
",L);
        L=L*2*n; int nw=1,all=0;
        for(int i=1;i<=L;i++)
        {
            a[i]=s[nw][p[nw]]-'a'; 
            p[nw]++; if(p[nw]>l[nw])p[nw]=1;
            nw++; if(nw>n)nw=1;
            if(!vis[a[i]])all++,vis[a[i]]=1;
        }
        // for(int i=1;i<=L;i++)printf("%c",a[i]+'a'); puts("");
        int pl=1,pr=1,num=1; cnt[a[1]]++;
        while(num<all)
        {
            pr++;
            if(!cnt[a[pr]])num++;
            cnt[a[pr]]++;
        }
        int ans=pr;
        for(;pr<=L;pr++,cnt[a[pr]]++,num+=(cnt[a[pr]]==1))
        {
            while(pl<=pr&&num==all)
            {
                cnt[a[pl]]--;
                if(!cnt[a[pl]])num--;
                pl++;
            }
            ans=min(ans,pr-pl+2);
            // printf("pr=%d pl=%d ans=%d
",pr,pl,ans);
        }
        printf("%d
",ans);
    }
    return 0;
}

F

分析:

看到许多平方加加减减,自然会想到用几个相近的减一减看看。
于是就能发现四个连续的数可以构造出一个 $ 4 $ : $ i^2 - (i-1)^2 = 2i-1, (i-2)^2 - (i-3)^2 = 2i-5 $
然后用前四个数可以分别构造出 $ 1,2,3,4 $ ,所以前面搭配后面的一堆 $ 4 $ 就可以组成 $ n $ 了。
输出量很大,必须用cout才不会TLE。在这里cout竟然比printf快,长见识了……

代码如下
#include<iostream>
using namespace std;
int const N=1e6+5;
int T,n;
char out[N];
void prt(int l,int r)
{
    for(int i=l;i<=r;i+=4)//printf("1001");
        // putchar('1'),putchar('0'),putchar('0'),putchar('1');
        cout<<"1001";
    puts("");
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        if((n%4)==0)printf("%d
",n),prt(1,n);
        else if((n%4)==1)printf("%d
1",n),prt(2,n);
        else if((n%4)==2)printf("%d
0001",n+2),prt(5,n+2);
        else if((n%4)==3)printf("%d
01",n-1),prt(3,n-1);
    }
    return 0;
}

G

分析:

原来的式子不太好看。但是, $ g(x) $ 的取值实际上只有54种!可以枚举。
固定了 $ g $ 以后,原式就是一个二次函数;把 $ g $ 相同的数放在同一个vector里,就可以二分了。
二分时注意范围;而且因为是找最靠近对称轴的,所以找到一个值,再算一算它前后的值。

代码如下
#include<iostream>
#include<vector>
#include<algorithm>
#include<cstring>
#define ll long long
#define db double
#define pb push_back
using namespace std;
int const N=1e6;
int T;
ll A,B,C,D,n;
ll const inf=1e17;
vector<int>v[55];
int cal(int x)
{
    int ret=0;
    while(x)ret+=x%10,x/=10;
    return ret;
}
void init()
{
    for(int x=1;x<=N;x++)
        v[cal(x)].pb(x);
}
int main()
{
    init();
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld%lld%lld%lld",&A,&B,&C,&D,&n); ll a,b; db p;
        ll ans=inf;
        for(int g=1;g<=54;g++)
        {
            // printf("g=%d
",g);
            if(!v[g].size()||v[g][0]>n)continue;
            a=A*g+B; b=C*g*g+D*g;
            p=1.0*(-b)/(2*a);
            // printf("a=%lld b=%lld p=%.2lf
",a,b,p);
            if(a>0)
            {
                int l=0,r=upper_bound(v[g].begin(),v[g].end(),n)-v[g].begin()-1,R=r,mid;//
                while(l<=r)
                {
                    mid=((l+r)>>1); 
                    int nw=v[g][mid]; 
                    ans=min(ans,a*nw*nw+b*nw);

                    ll tmp;
                    if(mid)tmp=v[g][mid-1],ans=min(ans,a*tmp*tmp+b*tmp);
                    if(mid<R)tmp=v[g][mid+1],ans=min(ans,a*tmp*tmp+b*tmp);

                    if(nw>p)r=mid-1;
                    else l=mid+1;
                }
            }
            else
            {
                int r=upper_bound(v[g].begin(),v[g].end(),n)-v[g].begin()-1;//
                ll tmp=v[g][0];
                ans=min(ans,a*tmp*tmp+b*tmp);
                if(r<0)continue;
                tmp=v[g][r];
                ans=min(ans,a*tmp*tmp+b*tmp);
            }
        }
        printf("%lld
",ans);
    }
    return 0;
}

I

分析:

求L和R、U和D数量相同的子串个数。
我一开始还是没想清楚……其实可以看作是 $ L-R=0, U-D=0 $ 的子串;也就是左右端点 $ L-R $ 的前缀和、 $ U-D $ 的前缀和都对应相等的子串。所以计算前缀和,开一个map存当前两个前缀和的状态出现过的次数,就可以统计答案了。
比赛时G没有开long long,WA了一次。我看了一会才看出来。

代码如下
#include<iostream>
#include<cstdio>
#include<map>
#define ll long long
#define mkp make_pair
#define fst first
#define scd second
using namespace std;
int const N=1e5+5;
int T,n;
char st[N];
map<pair<int,int>,int>mp;
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%s",&n,st+1); int lr=0,ud=0;
        mp.clear(); ll ans=0; mp[mkp(0,0)]++;
        for(int i=1;i<=n;i++)
        {
            char x=st[i];
            if(x=='U')ud++; else if(x=='D')ud--;
            if(x=='L')lr++; else if(x=='R')lr--;
            ans+=mp[mkp(ud,lr)]; mp[mkp(ud,lr)]++;
        }
        printf("%lld
",ans);
    }
    return 0;
}

K

分析:

很直观的DP。唯一要注意的是打完最后一发子弹以后,后面那些虽然不损失子弹但是要透支一颗子弹才能获取的分数也不能获取了。所以要注意一下在哪列打完最后一发子弹,也就是DP增加一维 $ 0/1 $ 表示是否已选择了打最后一发子弹的列。

代码如下
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int const N=205,inf=2e9;
int T,n,m,K,g[N][N],g2[N][N],f[N][N][2],d[N][N],mx[N];
char c[N][N];
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&n,&m,&K);
        memset(g,0,sizeof g); memset(g2,0,sizeof g2);
        for(int i=0;i<=m;i++)
            for(int j=0;j<=K;j++)f[i][j][0]=f[i][j][1]=-inf;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                scanf("%d %c",&d[i][j],&c[i][j]);
        for(int j=1,w=0;j<=m;j++,w=0)
            for(int i=n;i;i--)
            {
                if(c[i][j]=='N')w++,g[j][w]=g[j][w-1]+d[i][j],g2[j][w]+=d[i][j],g2[j][w]+=g2[j][w-1];
                else g[j][w]+=d[i][j],g2[j][w+1]+=d[i][j];
                if(i==1)mx[j]=w;
            }
        // for(int i=1;i<=m;i++)
        //     for(int j=0;j<=K;j++)printf("g[%d][%d]=%d g2[%d][%d]=%d
",i,j,g[i][j],i,j,g2[i][j]);
        f[0][K][0]=0;
        for(int i=1;i<=m;i++)
        {
            for(int j=0;j<=K;j++)
            {
                for(int k=0;j+k<=K&&k<=mx[i];k++)
                {
                    f[i][j][0]=max(f[i][j][0],f[i-1][j+k][0]+g[i][k]);
                    f[i][j][1]=max(f[i][j][1],f[i-1][j+k][1]+g[i][k]);
                    if(k)f[i][j][1]=max(f[i][j][1],f[i-1][j+k][0]+g2[i][k]);//k!=0!!
                    // printf("f[%d][%d][0]=%d f[%d][%d][1]=%d k=%d
",i,j,f[i][j][0],i,j,f[i][j][1],k);
                }
            }
        }
        int ans=0;
        for(int i=1;i<=m;i++)
            for(int j=0;j<=K;j++)
                ans=max(ans,f[i][j][1]);
        // printf("%d
",f[m][0][1]);
        printf("%d
",ans);
    }
    return 0;
}

L

分析:

首先,需要意识到这一点:对于一个数 $ x $ , 取了一个质数 $ p $ ,进行了题中的那一番操作以后,实际上是把 $ x $ 变成了小于等于 $ x $ 的最大的 $ p $ 的倍数(包括 $ 0 $ )。
所以,无解的情况就是大于等于给出的质数的 $ lcm $ 的那些 $ x $ 。它们最终会停在 $ lcm $ 上无法再变小了。
我们设 $ x $ 变成 $ 0 $ 的最小次数是 $ ans[x] $ ,转移就是枚举 $ p $ ,从 ans[x-x%p] 中取最小值再 $ +1 $ 。(用latex怎么打不出来百分号啊……)
这里我们要再发现一个关键的性质: $ ans[i] $ 是一个不减的序列。可以用归纳法证明:
初始 $ ans[x]=1 (1 leq x < maxpri) $ ,这里 $ maxpri $ 表示题中给出的那些质数中最大的那个。
假设 $ ans[i] $ 不减,结合转移方程可知, $ ans[i] $ 会从 ans[ min { i-i%p_j } ] 转移而来。当 $ i $ 增大时, min { i-i%p_j } 这个东西是不减的;所以 $ ans[i] $ 不减。(用latex怎么打不出来大括号哇TAT)
那么,考虑 $ ans[x] $ 可以更新后面的哪些 $ ans[i] $ 。对于它的每个属于 $ p $ 的质因子 $ p_i $ , $ ans[x] $ 都可以去更新 $ x+1 leq i leq x+p_i-1 $ 的所有 $ ans[i] $ 。容易看出,直接找到这些 $ p_i $ 中最大的 $ mx[x] $ , $ ans[x] $ 可以去更新 $ x+1 leq i leq x+mx[x]-1 $ 。当然这个范围里有已经被小于 $ x $ 的位置更新过的,那些就用不着 $ ans[x] $ 来更新了。根据这一点,来个指针记录更新到哪了,整个过程是 $ O(n) $ 的。
当然,我们还要处理 $ mx[x] $ 。看到一个不错的方法:用 $ O(n) $ 的线性筛,我们可以顺便求出每个数 $ x $ 的最小质因子 $ mn $ ,而 $ x $ 的最大因子就是 $ x/mn $ ,而最大质因子就是最大因子的最大质因子。就可以递推了。
于是我就写了,WA了。因为这里要注意,我们要找的质因子必须在题中给出的那些质数中。所以符合要求的质因子还可能是最小质因子——本质上是把所有质因子都排查一遍。
一开始还TLE了几次。能优化的地方都尽量优化一下,比如 $ 23333 $ 的幂可以预处理。常数略大,主要是因为 $ mx[x] $ 每次都得处理,因为和题中给出的质数列表有关。还看到有人存的是最小质因子,也许可以更快一点。
最初还开了__int128……原来模 $ 2^{64} $ 的意思就是用 unsigned long long 自然溢出啊,以前对这些一直不太熟呢。

代码如下
#include<iostream>
#include<cstring>
#include<algorithm>
#define ll unsigned long long
using namespace std;
int const N=2e6+5,M=1e5+5;
int T,n,m,pri[M],p[N],pn,mx[N];
ll a[N],lcm,bs[N];
bool vis[N],in[N];
ll gcd(ll x,ll y){return (y==0)?x:gcd(y,x%y);}
ll getl(ll x,ll y){return x/gcd(x,y)*y;}
// ll pw(ll x,int y)
// {
//     ll ret=1,nx=x;
//     while(y)
//     {
//         if(y&1)ret=ret*nx%md;
//         nx=nx*nx%md;
//         y>>=1;
//     }
//     return ret;
// }
// int ch[25];
// void out(ll x)
// {    
//    if(x<0){putchar('-'); x=-x;}
//    if(x>9) out(x/10);
//    putchar(x%10+'0');
// }
void out(ll x){cout<<x<<endl;}
void init()
{
    for(int i=1;i<=n;i++)vis[i]=0,mx[i]=-1;
    pn=0;
    for(int i=2;i<=n;i++)
    {
        if(!vis[i])
        {
            p[++pn]=i;
            if(in[i])mx[i]=i;
        }
        for(int j=1;j<=pn&&(ll)i*p[j]<=n;j++)
        {
            vis[i*p[j]]=1; mx[i*p[j]]=mx[i];
            if(in[p[j]])mx[i*p[j]]=max(mx[i*p[j]],p[j]);//!!!
            if(i%p[j]==0)break;
        }
    }
    lcm=pri[1];
    for(int i=2;i<=m;i++)lcm=getl(lcm,pri[i]);
}
int main()
{
    // md=1; for(int i=1;i<=64;i++)md=md*2;
    bs[0]=1;
    for(int i=1;i<=2e6;i++)bs[i]=bs[i-1]*23333;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);// for(int i=1;i<=n;i++)in[i]=0;
        int mxp=0;
        for(int i=1;i<=m;i++)
            scanf("%d",&pri[i]),in[pri[i]]=1,mxp=max(mxp,pri[i]);
        // sort(pri+1,pri+m+1); 
        init();
        for(int i=1;i<mxp;i++)a[i]=1;
        for(int i=mxp;i<=n;i++)a[i]=0;
        int nw=mxp;
        // for(int i=1;i<=n;i++)printf("mx[%d]=%d
",i,mx[i]);
        for(int i=1;i<=n;i++)
        {
            // printf("mx[%d]=%d
",i,mx[i]);
            if(mx[i]==-1)continue;
            while(nw<=i+mx[i]-1&&nw<=n&&nw<lcm)
                a[nw]=a[i]+1,nw++;
            // printf("i=%d nw=%d n=%d a[%d]=",i,nw,n,nw); out(a[nw]);
            if(nw>n||nw>=lcm)break;
        }
        ll ans=0;
        for(int i=1;i<=n&&i<lcm;i++)
            // ans=(ans+a[i]*pw(23333,n-i)%md)%md;//,printf("a[%d]=",i),out(a[i]),puts("");
            ans=ans+a[i]*bs[n-i];//,printf("a[%d]=%lld
",i,a[i]);
        out(ans);
        for(int i=1;i<=m;i++)in[pri[i]]=0;
    }
    return 0;
}
原文地址:https://www.cnblogs.com/Zinn/p/15209287.html