AYITOJ ROUND #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] | i-m<=p<=i-1 )+p , 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;
}