第八届“图灵杯”NEUQ-ACM程序设计竞赛个人赛(同步赛)全题解

先在开头吐槽一下这场比赛修改了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\n",ans+k);
    int now=1;
    for(int i=0;i<=9;i++)
    {
        for(int j=0;j<now;j++)
            printf("1 %d\n",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("\n");
        }
        else
        {
            printf(" %d",a);
            for(int i=0;i<a;i++) printf(" 10");
            printf("\n");
        }
    }
}

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\n",ans);
    }
}

C题
相关tag:数学

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

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

长度为x的连续子数组用1种取法。
该区间的总取法为(1+2+…+x)=x × \times × (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\n",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 × \times ×(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!\n");
        else printf("yo xi no forever!\n");
    }
}

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!\n");
        else printf("ma la se mi no.1!\n");
    }
}

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\n",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("\n");
            }
        }
    }
}

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\n");//前面的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\n");//如果能多吃的部分大于剩余的部分,代表我们能有一种安排,使得某次循环开始时,派蒙去吃东西的时候k已经为0
            else printf("NO\n");
        }
    }
}

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\n",qpow(2,n));
        else if(m==1)
        {
            if(n==0) printf("1\n");
            else printf("%lld\n",n*2);
        }
        else
        {
            if(n<2) printf("%lld\n",n+1);
            else printf("%lld\n",n+2);
        }
    }
}

I题
相关tag:数学,暴力

如果我们选择了k天,第一天选择了d朵。
那么总的花的数量就是d × \times × (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\n");
        else printf("N0\n");
    }
}

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\n");
    else printf("%lld\n",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("\n");
}

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\n");
    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\n",l);
    }
}
  • 16
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值