题目链接:http://acm.ayit.edu.cn/contest/1
自我感觉这一套题的难度梯度还是很科学的。我的预期是绝大部分人做出来简单题,少量人ac中等题。E题之前出过类似的题目,G题是一个明显的二分,如果这两道题都没思路,可以肝B题。做题情况还是不容乐观的。
分析原因可能还是大家见题比较少,尤其是新题。不能只做专题,可以经常做一下牛客网的比赛或者codeforces锻炼一下思维。
借助本次比赛修复了网站很多bug还是很好的。
就在我写这篇博客的时候居然有人用搜索过了E?并且耗时极少。一道构造题就这样变成了搜索。。。
还好有人用构造过了E。
难度分布:
简单题:DF
中等题:BEG
难题:AC
A.VN的五杀
思路:
经分析得只有敌方周围的最多5*8个点为有效点,我们可以在这最多40个点的范围内进行搜索。
可以将这些点分为5个集合,我们先得出一个攻击序列,然后每次找一个集合中的两个点,入点和出点。
可以发现入点可以取集合中距离上一个出点最近的点,并且vn在每一个集合中停留的时间一定恰好为5。
这样我们只需要在每个集合中搜索一个出点即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 1005
#define inf 0x3f3f3f3f3f3f3f3f
int dir[8][2]= {1,0,0,1,-1,0,0,-1,1,-1,-1,1,1,1,-1,-1};
int all[300][5],path[5];
ll stx,sty;
struct node
{
ll x,y;
} p[6],e;
vector<node> vec[5];
ll ans;
ll distance(ll x1,ll y1,ll x2,ll y2)
{
return abs(x1-x2)+abs(y1-y2);
}
void dfs(int step,ll lasty,ll lastx,ll s)
{
if(s>ans) return;
if(step==5)
{
ans=s;
return;
}
//printf(">>%d %lld %lld %lld\n",step,lasty,lastx,s);
int n=path[step];
ll minn=inf;
for(int i=0; i<vec[n].size(); i++)
{
e.y=vec[n][i].y;
e.x=vec[n][i].x;
if(distance(e.x,e.y,lastx,lasty)<minn)
minn=distance(e.x,e.y,lastx,lasty);
}
for(int i=0; i<vec[n].size(); i++)
{
e=vec[n][i];
dfs(step+1,e.y,e.x,s+minn+5);
}
}
int main()
{
scanf("%lld%lld",&stx,&sty);
for(int i=0; i<5; i++)
scanf("%lld%lld",&p[i].x,&p[i].y);
for(int i=0; i<5; i++)
{
for(int j=0; j<8; j++) //找五个点周围的点
{
ll ty=p[i].y+dir[j][0];
ll tx=p[i].x+dir[j][1];
int flag=0;
for(int k=0; k<5; k++) //判断是否重合
{
if(ty==p[k].y && tx==p[k].x)
{
flag=1;
break;
}
}
e.y=ty;
e.x=tx;
if(!flag) vec[i].push_back(e);
}
}
ans=inf;
for(int i=0; i<5; i++) path[i]=i;
do
{
dfs(0,sty,stx,0);
}while(next_permutation(path,path+5));
printf("%lld\n",ans);
return 0;
}
B.化简方程式
思路:
可以定义一个结构体表示含x的标准项。重定义运算符。用栈先处理出乘法运算,加法运算可以直接用一个数组记录x对应次幂下的系数,减法运算可以转化为加法。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 105
#define inf 0x3f3f3f3f
struct node
{
int xi,mi;
node(){}
node(int xx,int mm)
{
xi=xx;
mi=mm;
}
} p;
bool cmp(node a,node b)
{
return a.mi>b.mi;
}
int xx[10005];
char str[MAXN];
node add(node a,node b)
{
return node(a.xi+b.xi,a.mi);
}
node mul(node a,node b)
{
return node(a.xi*b.xi,a.mi+b.mi);
}
stack<node> st;
stack<char> fu;
int main()
{
scanf("%s",str);
for(int i=0; str[i]; i++)
{
int d=0;
int flag=0;
int j=i;
if(str[i]=='-')
{
flag=1;
j++;
}
for(j; isdigit(str[j]); j++)
{
d=d*10+str[j]-'0';
}
if(d==0) p.xi=1;
else p.xi=d;
if(str[j]!='x') p.mi=0;
else
{
j++;
if(str[j]!='^') p.mi=1;
else
{
d=0;
for(j=j+1; isdigit(str[j]); j++)
{
d=d*10+str[j]-'0';
}
p.mi=d;
}
}
if(flag) p.xi=-p.xi;
//printf("%dx^%d %d\n",p.xi,p.mi,j);
st.push(p);
if(!fu.empty() && fu.top()=='*')
{
node a=st.top(); st.pop();
node b=st.top(); st.pop();
fu.pop();
st.push(mul(a,b));
}
if(str[j]=='-') j--,fu.push('+');
else if(str[j]=='+') fu.push('+');
else if(str[j]=='*') fu.push('*');
i=j;
}
memset(xx,0,sizeof xx);
int maxx=0;
while(!st.empty())
{
node top=st.top(); st.pop();
xx[top.mi]+=top.xi;
maxx=max(maxx,top.mi);
}
int out=0;
for(int i=maxx;i>=1;i--)
{
if(xx[i]==0) continue;
out=1;
if(xx[i]<0)
{
if(xx[i]==-1) printf("-");
else printf("%d",xx[i]);
}
else
{
if(i<maxx) printf("+");
if(xx[i]>1) printf("%d",xx[i]);
}
printf("x");
if(i>1) printf("^%d",i);
}
if(xx[0]!=0)
{
if(maxx==0 || xx[0]<0) printf("%d",xx[0]),out=1;
else printf("+%d",xx[0]),out=1;
}
if(!out) printf("0");
printf("\n");
return 0;
}
C.取数游戏
思路:
可以用dpa[i]表示当前数为i时,轮到Alice取数时,Alice最终能取到的最大值。
dpb[i]表示当前数为i时,轮到Bob取数时,Alice最终能取到的最小值。
转移方程:
dpa[i]=max( dpb[i/k]+k , max( dpb[p]+p | i-m<=p<=i-1 ) , dpb[i] ); k为i的素因子
dpb[i]=min( dpa[i/k] , min( dpa[p] | i-m<=p<=i-1 ), dpa[i]);
操作一我们可以对当前数进行分解,直接暴力枚举质因子会超时。
操作二我们可以用两个单调队列维护[i-m,i-1]区间内的最小dpa和最大dpb。
更新dpa,维护que2的时候需要考虑下标的影响,下标的影响为负。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 100005
#define inf 0x3f3f3f3f
int n,m,dpa[MAXN],dpb[MAXN],que1[MAXN],tmp1[MAXN],que2[MAXN],tmp2[MAXN];
int prime[MAXN];
bool is_prime[MAXN];
//返回n以内素数的个数
int sieve(int n)
{
int p = 0;
for(int i = 0; i<= n; i++) is_prime[i] = true;
is_prime[0] = is_prime[1] = false;
for(int i = 2; i <= n; i++)
{
if(is_prime[i])
{
prime[p++] = i;
for(int j = 2 * i; j <= n; j += i) is_prime[j] = false;
}
}
return p;
}
int main()
{
scanf("%d%d",&n,&m);
dpa[0]=0;
dpb[0]=0;
int p=sieve(n);
int head1=1,tail1=0,head2=1,tail2=0;
for(int i=1; i<=n; i++)
{
int last;
dpb[i]=inf;
dpa[i]=0;
int d=i;
for(int j=0; j<p && prime[j]<=d && prime[j]*prime[j]<=i; j++)
{
int num=prime[j];
if(d%num==0)
{
dpb[i]=min(dpb[i],dpa[i/num]);
dpa[i]=max(dpa[i],dpb[i/num]+num);
while(d%num==0) d/=num;
}
}
if(d>1)//压缩时间
{
dpb[i]=min(dpb[i],dpa[i/d]);
dpa[i]=max(dpa[i],dpb[i/d]+d);
}
while(head1<=tail1 && tmp1[head1]<i-m) head1++;
while(head1<=tail1 && que1[tail1]>=dpa[i-1]) tail1--;
que1[++tail1]=dpa[i-1];
tmp1[tail1]=i-1;
dpb[i]=min(dpb[i],que1[head1]);
while(head2<=tail2 && tmp2[head2]<i-m) head2++;
while(head2<=tail2 && que2[tail2]<=dpb[i-1]-i+1) tail2--;
que2[++tail2]=dpb[i-1]-i+1;
tmp2[tail2]=i-1;
dpa[i]=max(dpa[i],que2[head2]+i);
}
printf("%d\n",dpa[n]);
return 0;
}
D.贪吃鱼
思路:
从后往前扫,维护最大值。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 100005
int n,a[MAXN];
int main()
{
scanf("%d",&n);
for(int i=0;i<n;i++)
scanf("%d",a+i);
int maxx=0,cnt=0;
for(int i=n-1;i>=0;i--)
{
if(a[i]>=maxx) maxx=a[i];
else cnt++;
}
printf("%d\n",n-cnt);
return 0;
}
E.挑战数独
思路:
可以先用错位排列构造出一个全空的数独的解,然后3行3列的交换即可,可以发现一定会构造出解。
错位排列的结果可以参考maze1矩阵。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
int maze1[9][9]= {{1,2,3,4,5,6,7,8,9},
{4,5,6,7,8,9,1,2,3},
{7,8,9,1,2,3,4,5,6},
{2,3,4,5,6,7,8,9,1},
{5,6,7,8,9,1,2,3,4},
{8,9,1,2,3,4,5,6,7},
{3,4,5,6,7,8,9,1,2},
{6,7,8,9,1,2,3,4,5},
{9,1,2,3,4,5,6,7,8}
};
int maze[9][9];
int x,y,d;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&y,&x,&d);
x--;
y--;
memcpy(maze,maze1,sizeof maze1);
int tt=-1;
for(int i=0; i<3; i++)
{
for(int j=0; j<3; j++)
{
if(maze[i*3+y%3][j*3+x%3]==d)
{
tt=i*3+j;
break;
}
}
if(tt!=-1) break;
}
for(int i=y/3*3; i<y/3*3+3; i++)
for(int j=0; j<9; j++)
swap(maze[i][j],maze[tt/3*3+i-y/3*3][j]);
for(int i=x/3*3; i<x/3*3+3; i++)
for(int j=0; j<9; j++)
swap(maze[j][i],maze[j][tt%3*3+i-x/3*3]);
for(int i=0; i<9; i++)
{
for(int j=0; j<9; j++)
{
if(j) printf(" ");
printf("%d",maze[i][j]);
}
printf("\n");
}
}
return 0;
}
F.亵渎
思路:
判断是否连续。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define MAXN 105
#define inf 0x3f3f3f3f
int n,a[MAXN];
bool vis[MAXN];
int main()
{
scanf("%d",&n);
memset(vis,0,sizeof vis);
int maxx=0;
for(int i=0;i<n;i++)
{
scanf("%d",a+i);
maxx=max(maxx,a[i]);
vis[a[i]]=1;
}
int flag=0;
for(int i=1;i<=maxx;i++)
{
if(!vis[i])
{
flag=1;
break;
}
}
if(flag) puts("No");
else puts("Yes");
return 0;
}
G.字母
思路:
对26个字母二分最小间隔。
check时可以用vector存字母出现的位置,然后lower_bound扫一遍即可。
代码:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define MAXN 1000005
int n,k,ans;
char str[MAXN];
vector<int> vec[30];
int check(int len)
{
for(int i=0;i<26;i++)
{
if(vec[i].size()<=k) continue;
int last=vec[i][0];
for(int j=1;j<k;j++)
{
int it=lower_bound(vec[i].begin(),vec[i].end(),last+len+1)-vec[i].begin();
last=vec[i][it];
if(last==inf) break;
if(j==k-1) return 1;
}
}
return 0;
}
void find(int L,int R)
{
if(L>=R) return;
int mid=(L+R)/2;
if(check(mid))
{
ans=mid;
find(mid+1,R);
}
else find(L,mid);
}
int main()
{
scanf("%d%d",&n,&k);
scanf("%s",str);
for(int i=0;str[i];i++)
vec[str[i]-'a'].push_back(i);
for(int i=0;i<26;i++)
vec[i].push_back(inf);
ans=-1;
find(0,n+1);
printf("%d\n",ans);
return 0;
}