第八届“图灵杯”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);
}
}
标签:int,题解,ll,ACM,先手,long,tie,NEUQ,define 来源: https://www.cnblogs.com/phyger/p/14357327.html