zoukankan      html  css  js  c++  java
  • 第八届“图灵杯”NEUQ-ACM程序设计竞赛个人赛(同步赛)全题解

    此文转载自:https://blog.csdn.net/StandNotAlone/article/details/113439051

    先在开头吐槽一下这场比赛修改了n次题面甚至改了数据,题面的糟糕程度实属第一次见。
    这场比赛难的不是题目,是出题人,这场比赛可能是没有经过严格的验题。

    A题
    相关tag:数学

    如果我们把1划分成x份,那么每份就是1/x。
    我们希望最后的k个人得到的尽可能平均,那么每个人必然是拿到[x/k]份的1/x或者[x/k]+1份的1/x。
    那么每个人得到的值,与平均值x/k的差值是不可能大于1/x的。

    题目要求差值不超过1/210,那么我们把1切成x=1024份,每份的值为1/1024,再平均分配打包给所有人就是了。

    切割成1024份需要的次数为20+21+22+…+29=1023次,在加上打包k次,次数必定在6000次内。
    (当然你切成2048份也行,仍然在6000次内)

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=30+7;
    const int mod=998244353;
    
    int k;
    int ans=1023;
    
    int main()
    {
        scanf("%d",&k);
        printf("%d
    ",ans+k);
        int now=1;
        for(int i=0;i<=9;i++)
        {
            for(int j=0;j<now;j++)
                printf("1 %d
    ",i);
            now*=2;
        }
        int num=1024;
        int a=num/k,b=num%k;
        for(int i=1;i<=k;i++)
        {
            printf("2");
            if(i<=b)
            {
                printf(" %d",a+1);
                for(int i=0;i<=a;i++) printf(" 10");
                printf("
    ");
            }
            else
            {
                printf(" %d",a);
                for(int i=0;i<a;i++) printf(" 10");
                printf("
    ");
            }
        }
    }
    

    B题
    相关tag:前缀和

    使用num[i]记录第i个数字为多少。
    使用sum[i]记录前缀和,也就是前i个数字值的和。

    如果某一个区间[l,r]的数字加起来为k的整数倍。
    那么必定有(num[r]-num[l-1])%k=0。
    也等价于num[r]%k=num[l-1]%k

    我们可以依靠前缀和对k取模的结果。
    cas[i]记录前缀和对k取模为i的前缀和,第一次出现在哪里。
    每次出现取模为i的前缀和时,用当前位置减去第一次出现的位置,即为该位置为右边界可以找到的最长区间了。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=30+7;
    
    ll ans,n,k;
    ll num[100007];
    ll cas[100007];
    
    int main()
    {
        int t;scanf("%d",&t);
        while(t--)
        {
            ans=-1;
            for(int i=1;i<100007;i++) cas[i]=-1;
            scanf("%lld%lld",&n,&k);
            for(int i=1;i<=n;i++)
            {
                scanf("%lld",&num[i]);
                num[i]=(num[i]+num[i-1])%k;
                if(cas[num[i]]==-1) cas[num[i]]=i;
                else ans=max(ans,i-cas[num[i]]);
            }
            printf("%lld
    ",ans);
        }
    }
    

    C题
    相关tag:数学

    首先从左往右看,我们可以按照连续的不下降区间,把整个数组划分成各块区间。
    由于相邻两个区间之间,相邻的那两个数的值必定是下降的。
    因此我们选择满足条件的子数组,只能在每块区间里找。

    对于一个长度为x的区间。
    长度为1的连续子数组有x种取法。
    长度为2的连续子数组有x-1种取法。

    长度为x的连续子数组用1种取法。
    该区间的总取法为(1+2+…+x)=x × imes × (x+1)/2种。

    所有区间的取法加起来即可。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=30+7;
    
    ll ans=0,n;
    ll num[100007];
    ll cas=0;
    
    int main()
    {
        scanf("%lld",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%lld",&num[i]);
            if(num[i]>=num[i-1]) cas++;
            else
            {
                ans+=cas*(cas+1)/2;
                cas=1;
            }
        }
        ans+=cas*(cas+1)/2;
        printf("%lld
    ",ans);
    }
    

    D题
    相关tag:简单博弈

    只有1张牌的时候,先手必输。
    有x=[2,k+1]张牌的时候,先手的人可以拿走x-1张牌,剩下1张,所以先手必胜。
    有x=k+2张牌的时候,先手的人不管拿走[1,k]的任意张数,剩下的数量必定落在[2,k+1]的区间里,先手必输。
    当x=[k+3,2k+2]张牌的时候,同上,先手必胜
    当x=2k+3张牌的时候,同上,先手必输。

    归纳后得到,当x=d × imes ×(k+1)+1时候(d为常数),先手必输,其他情况先手必胜。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=1e5+7;
    
    
    int main()
    {
        int t;scanf("%d",&t);
        while(t--)
        {
            int n,k;scanf("%d%d",&n,&k);
            if((n-1)%(k+1)==0) printf("ma la se mi no.1!
    ");
            else printf("yo xi no forever!
    ");
        }
    }
    

    E题
    相关tag:博弈
    (出题人题面的输出和实际样例的输出都能不一样的,一个是no.1!,一个是no1.!。一个题就算了,好几个题的题面都有问题,是真的绝活)

    首先我们算出在乌龟上方和下方分别有a和b张牌。
    如果a>b的话,我们交换一下a,b的值,保证a<=b。

    接下来,按照a的值为0,1,2,…15e5分类讨论。

    a=0的情况:
    a=0,b=0的时候,先手负。
    a=0,b>0的时候,先手可以一次拿光b,先手胜。

    a=1的情况:
    a=1,b=1的时候,先手a和b都拿掉1,先手胜。
    a=1,b=2的时候,先手如果想赢,那么就必须要让当前的状态变为先手负(操作之后当前后手的人变为下次操作的先手),而之前的先手负的状态只有a=0,b=0,我们从a=1,b=2的状态怎么取都是得不到a=0,b=0的。因此此时先手负。
    a=1,b>2的时候,我们可以把b拿掉b-2的部分,使得变成a=1,b=2的先手负(当前的后手)状态。因此先手胜。

    a=2的情况:
    a=2,b>=2的所有状况,都可以从b取走b-1个,使得变为a=2,b=1也就是a=1,b=2的情况。先手必胜。

    a=3的情况:
    a=3,b=3或等于4的时候,可以同时从a和b中拿掉3或者2,得到a=0,b=0或者a=1,b=2。因此先手胜。
    a=3,b=5的时候,前面的先手负状态只有a=0,b=0或者a=1,b=2,我们不论如何操作都无法得到这两种状态。因此先手负。
    a=3,b>5的时候,先手可以从b拿走b-5个,使得剩下a=3,b=5个。因此先手胜

    归纳后实际上会发现,当a>0的情况下,
    对于某一类a=x,先手如果想赢,当b还不是很大的状态下,只能通过同时取走a和b一部分值,得到a<x的某个先手负状态来取胜。当前面没有a和b差值等于当前状态的情况下,那么此时的先手玩家只能把当前状态移动到先手胜的状态,因此当前的先手必败。那么对应的必败状态的a和b的差值,会比前面已经出现过的大1。

    最开始的时候先手负状态只有a=0,b=0,a和b差值为0,
    后续有a=1,b=2,a和b差值为1。
    再后续a=3,b=5,a和b差值为2。
    再后续a=4,b=7,a和b差值为3。

    注意到这里跳过了a=2的状态。这是因为在a<2的状态中有一个a=1,b=2的状态是先手负。
    我们可以把b取b-2个,变为上述状态来获胜。

    也就是说,对于我们当前a=x的状态,如果在前面的a<x的某一个状态中存在b=x使得先手负的,那么a=x的所有情况都是必胜。

    由此综上,用一个dis记录下一个先手负状态a和b的差值应该为多少。
    使用cas[i]记录对于a=i,b=cas[i]时先手负,cas[i]=-1时代表此时b不论取什么值先手必胜。

    那么们可以一路从小到大去循环a的值,
    如果当前的a值,没有在前面计算的b中出现过,代表当前a的值存在一种先手负的状态,当b=a+dis时必败,并且更新cas[b]=-1。
    如果当前的a值已经在前面计算的b中出现过,也就是说cas[a]=-1,那么当前a的值必胜。

    以上。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=3e6+7;
    
    ll cas[maxn];
    ll dis=1;
    
    int main()
    {
        for(int i=1;i<maxn;i++)
        {
            if(cas[i]!=-1)
            {
                cas[i]=i+dis;
                if(i+dis<maxn) cas[i+dis]=-1;
                dis++;
            }
        }
        int t;scanf("%d",&t);
        while(t--)
        {
            ll n,x;scanf("%lld%lld",&n,&x);
            ll a=x-1,b=n-x;
            if(a>b) swap(a,b);
            if(cas[a]==-1||cas[a]!=b) printf("yo xi no forever!
    ");
            else printf("ma la se mi no.1!
    ");
        }
    }
    

    F题
    相关tag:模拟,哈希,卡时间

    数据很大,一开始交了一发试一下,果然tle了。(那你为什么要交)
    加了个快读,用了unordered_map这个O(1)的哈希map就过掉了。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=30+7;
    const int mod=998244353;
    
    int read()      //整数读入挂
    {
        int x=0,f=1;
        char c=getchar();
        while(c<'0'||c>'9')
        {
            if(c=='-') f=-1;
            c=getchar();
        }
        while(c>='0'&&c<='9')
        {
            x=x*10+c-'0';
            c=getchar();
        }
        return x*f;
    }
    
    vector<string>grade[107];
    int n;
    
    struct Node
    {
        int grade,num,sex;
    };
    
    unordered_map<string,Node>MAP;
    char temp[10007];
    
    int main()
    {
        n=read();
        for(int i=0;i<n;i++)
        {
            string name;
            Node a;
            scanf("%s",temp);
            a.grade=read();
            a.sex=read();
            a.num=read();
            int len=strlen(temp);
            for(int i=0;i<len;i++)
             name+=temp[i];
            MAP[name]=a;
            grade[a.grade].push_back(name);
        }
        for(int i=0;i<=100;i++) sort(grade[i].begin(),grade[i].end());
        int k;cin>>k;
        while(k--)
        {
            int ope;cin>>ope;
            if(ope==1)
            {
                string name;
                scanf("%s",temp);
                int len=strlen(temp);
                for(int i=0;i<len;i++)
                 name+=temp[i];
                Node a=MAP[name];
                printf("%d %d %d
    ",a.grade,a.num,a.sex);
            }
            else
            {
                int x;cin>>x;
                for(int i=0;i<grade[x].size();i++)
                {
                    for(int j=0;j<grade[x][i].size();j++)
                        printf("%c",grade[x][i][j]);
                    printf("
    ");
                }
            }
        }
    }
    

    G题
    相关tag:贪心

    看注释吧

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=1e5+7;
    const int mod=998244353;
    
    ll num[maxn];
    
    int main()
    {
        int t;scanf("%d",&t);
        while(t--)
        {
            int n;
            ll k;
            scanf("%d%lld",&n,&k);
            ll M=0;
            int tar=0;//记录下派蒙在第几个
            for(int i=1;i<=n;i++)
            {
                scanf("%lld",&num[i]);
                if(num[i]>M)
                {
                    tar=i;
                    M=num[i];
                }
                num[i]+=num[i-1];//前缀和
            }
            ll yici=M+n-1;//代表一次循环最少要吃掉多少个
            if(k<tar-1) printf("NO
    ");//前面的tar个人最少每个人要吃一个
            else
            {
                ll rest=(k-tar+1)%(yici);//代表除了派蒙的人每人都只吃1个的情况,最后剩下的部分
                ll cas=(k-tar+1)/yici;//在上述情况下总共有多少轮循环(这里的循环,派蒙是第一个人)
                cas*=(num[n]-M);//每轮循环我们最多还可以再多吃总的个数减去派蒙的个数
                cas+=num[tar-1]-tar+1;//在开始循环前,一开始就排在派蒙前面的tar-1个人,除了最基本的每人吃一个外,最多还能吃几个
                if(cas>=rest) printf("YES
    ");//如果能多吃的部分大于剩余的部分,代表我们能有一种安排,使得某次循环开始时,派蒙去吃东西的时候k已经为0
                else printf("NO
    ");
            }
        }
    }
    

    H题
    相关tag:简单规律,快速幂

    m只有0,1,2三种状态,稿纸上推演一下,总结规律即可。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=30+7;
    const int mod=998244353;
    
    ll qpow(ll x,ll p)
    {
        ll ret=1;
        while(p)
        {
            if(p&1) ret=ret*x%mod;
            x=x*x%mod;
            p>>=1;
        }
        return ret;
    }
    
    int main()
    {
        int t;scanf("%d",&t);
        while(t--)
        {
            ll n,m;scanf("%lld%lld",&n,&m);
            if(m==2) printf("%lld
    ",qpow(2,n));
            else if(m==1)
            {
                if(n==0) printf("1
    ");
                else printf("%lld
    ",n*2);
            }
            else
            {
                if(n<2) printf("%lld
    ",n+1);
                else printf("%lld
    ",n+2);
            }
        }
    }
    

    I题
    相关tag:数学,暴力

    如果我们选择了k天,第一天选择了d朵。
    那么总的花的数量就是d × imes × (20+21+…+2k-1
    也就是说对于选择第k天的情况来说,如果存在满足的整数d,那么总的花数量n需要满足n能整除 (20+21+…+2k-1
    因此我们直接处理出k=2到15这些天数的 (20+21+…+2k-1),用n一一去暴力尝试整除即可。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=30+7;
    
    ll cas[20];
    
    int main()
    {
        cas[1]=1;
        for(int i=2;i<=15;i++) cas[i]=cas[i-1]*2;
        for(int i=2;i<=15;i++) cas[i]+=cas[i-1];
        int t;scanf("%d",&t);
        while(t--)
        {
            ll n;scanf("%lld",&n);
            bool flag=0;
            for(int i=2;i<=15;i++) if(n%cas[i]==0) flag=1;
            if(flag) printf("YE5
    ");
            else printf("N0
    ");
        }
    }
    

    J题
    相关tag:模拟

    模拟,没什么好说的。大一同学去学一下如何存图。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=30+7;
    const int mod=998244353;
    
    int n,m;
    int field[307][307];
    bool flag[307];
    
    int main()
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<m;i++)
        {
            int a,b,w;scanf("%d%d%d",&a,&b,&w);
            if(field[a][b]==0) field[a][b]=field[b][a]=w;
            else field[a][b]=field[b][a]=min(field[a][b],w);
        }
        ll ans=llINF;
        int k;scanf("%d",&k);
        while(k--)
        {
            for(int i=1;i<=n;i++) flag[i]=0;
            int cnt=0;
            ll sum=0;
            bool f=1;
            int x;scanf("%d",&x);
            int now=0;
            while(x--)
            {
                int to;scanf("%d",&to);
                if(field[now][to]==0) f=0;
                sum+=field[now][to];
                now=to;
                if(flag[to]==0) {flag[to]=1;cnt++;}
            }
            if(field[now][0]==0||cnt!=n) f=0;
            sum+=field[now][0];
            if(f) ans=min(ans,sum);
    
        }
        if(ans==llINF) printf("-1
    ");
        else printf("%lld
    ",ans);
    }
    

    K题
    相关tag:模拟

    注意一下字符为z或者Z的特殊情况即可。
    另外出题人爬

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    
    vector<char>c[4];
    vector<int>num[4];
    int cntc=0,cntn=0;
    
    char change(char c)
    {
        if(c=='Z') return 'b';
        if(c=='z') return 'B';
        return c+1;
    }
    
    int main()
    {
        string s;cin>>s;
        for(int i=0;i<32;i++)
        {
            if(s[i]>='0'&&s[i]<='9')
            {
                num[cntn].push_back(s[i]-'0');
                cntn=(cntn+1)%4;
            }
            else
            {
                c[cntc].push_back(s[i]);
                cntc=(cntc+1)%4;
            }
        }
        for(int i=0;i<4;i++)
        {
            for(int j=0;j<4;j++)
            {
                for(int k=0;k<num[i][j];k++)
                    c[i][j]=change(c[i][j]);
            }
        }
        for(int i=0;i<4;i++)
        {
            for(int j=3;j>=0;j--)
                printf("%c",c[j][i]);
        }
        printf("
    ");
    }
    

    L题
    相关tag:二分答案

    很好的一个二分答案例题。
    对于我们最后站台之间相邻距离的最大值L,随着L的增大,我们需要设置的站台数量只可能变少不可能变多。满足二分条件。存在某一个值x,使得当L>=x时,站台数量<=k。
    可以借此写出一个二分。

    对于每对相邻的车站,他们的距离如果为dis,我们check的距离为x。
    那么这段距离之中除了左右两侧已经有的城市外,需要的站台数量就是(dis/x+dis%x?1:0)-1。
    由此算出距离x对应需要的最少站台数量sum,用sum与题目要求k对比即可check正确性。

    #include<bits/stdc++.h>
    #define ll long long
    #define INF 0x7f7f7f7f //2139062143
    #define llINF 9223372036854775807
    #define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    using namespace std;
    const int maxn=1e5+7;
    
    ll num[maxn];
    ll dis[maxn];
    int n,k;
    
    bool check(ll x)
    {
        ll sum=0;
        for(int i=1;i<n;i++)
        {
            if(dis[i])
            {
                ll temp=dis[i]/x;
                if(dis[i]%x) temp++;
                sum+=temp-1;
            }
        }
        return sum<=k;
    }
    
    int main()
    {
        scanf("%d%d",&n,&k);
        for(int i=0;i<n;i++) scanf("%lld",&num[i]);
        sort(num,num+n);
        bool flag=1;
        for(int i=1;i<n;i++)
        {
            dis[i]=num[i]-num[i-1];
            if(dis[i]!=0) flag=0;
        }
        if(flag) printf("0
    ");
        else
        {
            ll l=1,r=1e12;
            while(l<r)
            {
                ll mid=(l+r)>>1;
                if(check(mid)) r=mid;
                else l=mid+1;
            }
            printf("%lld
    ",l);
        }
    }
    
       

    更多内容详见微信公众号:Python测试和开发

    Python测试和开发

  • 相关阅读:
    JS项目快速压缩(windows平台)
    Maven工程的POM继承
    Docker构建一个node镜像
    win10家庭版安装Docker for Windows
    linux,vim和bash命令小册
    vue文档阅读笔记——计算属性和侦听器
    nodejs的jekins部署
    `vue-router`的`History`模式下的项目发布
    花式图表,炫技时刻!PPT技能
    在创业之路上不断创新
  • 原文地址:https://www.cnblogs.com/phyger/p/14357327.html
Copyright © 2011-2022 走看看