前面的碎碎念:
本次比赛题目比较对胃口,一个半小时就A了前五题,尤其是第二题,我正好做过它的加强版(笑)。现在比赛rating还在计算,希望能借此上个绿名吧。
题解部分:
A-打怪
签到题。当我们的攻击力大于等于怪物的总血量,每次我们都可以不耗血秒杀怪物,从而可以杀死无数只怪物,对此输出-1。当我们的攻击力小于怪物的总血量时,我们设打死一只怪物需要round个回合,而每打死一只怪物,我们将会减少(round-1)A的血量(因为最后一回合我们杀死了怪物,因而少受一回合怪物的攻击)。设答案为ans,那么我们就是要寻找满足ans(round-1)A<h最大的那个ans,对此我们二分查找这个ans即可。
时间复杂度:O(tlog(h))。
代码部分:
#include<bits/stdc++.h> using namespace std; int main() { long long T,h,a,H,A,ans,round,l,r,m; scanf("%lld",&T); while(T--) { scanf("%lld%lld%lld%lld",&h,&a,&H,&A); if(a>=H)printf("-1\n"); else { round=H/a+(H%a?1:0); for(l=0,r=1e9;l<=r;) { m=(l+r)>>1; if(m*(round-1)*A<h)ans=m,l=m+1; else r=m-1; } printf("%lld\n",ans); } } return 0; }
B-吃水果
运气好做过这个题的加强版本2333,需要找规律。首先我们令ans等于n与m中较大的那个数,对于较小的那个数,使其不断乘2,直到其大于等于较大的那个数。令ans加上这个数乘二的次数,就是最终的答案。为什么呢?比如对于数字3 4,我们先减两次1,变成1 2,再使1乘2变成2,再减两次1使其同时为0。所以说,我们在减的过程中,总有机会会使较小的那个数乘2正好等于较大的那个数,最后就能同时使其为0。
时间复杂度:O(Tlog(n))。
代码部分:
#include<bits/stdc++.h> using namespace std; int main() { int i,T,n,m,ans; scanf("%d",&T); while(T--) { scanf("%d%d",&n,&m); if(n>m)swap(n,m); ans=m; for(i=n;i<m;i<<=1)ans++; printf("%d\n",ans); } return 0; }
C-四个选项
看到12个题目,每个题目才4个选项,很容易想到爆搜4^12种结果,加上每个选项有固定数量的限制,实际要搜索的结果数会更少。至于m个额外条件,我们先利用并查集预处理出每个题目的根节点题目,在最后统计时验证每个题目是否与其根节点题目的答案相同即可。
时间复杂度:O(124^12)。
代码部分:
#include<bits/stdc++.h> using namespace std; int na,nb,nc,nd,m,ans=0,V[15],T[15]; int find(int a) { if(a==V[a])return a; return V[a]=find(V[a]); } void DFS(int L,int a,int b,int c,int d) { int i; if(L==12) { if(a!=na||b!=nb||c!=nc||d!=nd)return; for(i=1;i<=12;i++)if(T[i]!=T[V[i]])break; if(i<=12)return; ans++; return; } T[L+1]=1,DFS(L+1,a+1,b,c,d); T[L+1]=2,DFS(L+1,a,b+1,c,d); T[L+1]=3,DFS(L+1,a,b,c+1,d); T[L+1]=4,DFS(L+1,a,b,c,d+1); } int main() { int i,j,k,x,y; scanf("%d%d%d%d%d",&na,&nb,&nc,&nd,&m); for(i=1;i<=12;i++)V[i]=i; while(m--) { scanf("%d%d",&x,&y); j=find(x),k=find(y); if(j!=k)V[j]=k; } DFS(0,0,0,0,0); printf("%d\n",ans); return 0; }
D-最短路变短了
我们先利用堆优化的dij求出从1号节点到各点的最短距离,把所有边反向再跑一次dij,计算出从n号节点到各点的最短距离,这时我们可以得到原始情况下1号节点到n号节点的最短距离。那么对于任意一条边(u,v,w),若把边的方向反向能够使最短路变短,那么这条变短后的最短路肯定是经过了这条边的,所以这条最短路的长度肯定是1号节点到v号节点的距离+w+u号节点到n号节点的距离。我们把这个距离和原始的1号节点到n号节点的最短距离比较,若其小于原始的最短距离输出YES,否则输出NO。
时间复杂度:O(nlog(n)+q)。
代码部分:
#include<bits/stdc++.h> using namespace std; struct node { long long x,h; bool operator<(const node &a)const { return a.h<h; } }r,f; struct node2 { int x,y,w; }E[200005]; priority_queue<node>Q; int n,m,q; long long ans,D[2][100005]; vector<int>R[2][100005],W[2][100005]; void dij(bool a,int sx) { int i,j; for(i=1;i<=n;i++)D[a][i]=2e18; r.h=D[a][sx]=0,r.x=sx,Q.push(r); while(!Q.empty()) { f=Q.top(),Q.pop(); if(D[a][f.x]<f.h)continue; for(i=0;i<R[a][f.x].size();i++) { j=R[a][f.x][i]; if(D[a][j]<=f.h+W[a][f.x][i])continue; r.h=D[a][j]=f.h+W[a][f.x][i],r.x=j,Q.push(r); } } } int main() { int i; scanf("%d%d",&n,&m); for(i=1;i<=m;i++) { scanf("%d%d%d",&E[i].x,&E[i].y,&E[i].w); R[0][E[i].x].push_back(E[i].y),R[1][E[i].y].push_back(E[i].x); W[0][E[i].x].push_back(E[i].w),W[1][E[i].y].push_back(E[i].w); } dij(0,1),dij(1,n),ans=D[0][n]; scanf("%d",&q); while(q--) { scanf("%d",&i); if(D[0][E[i].y]+D[1][E[i].x]+E[i].w<ans)printf("YES\n"); else printf("NO\n"); } return 0; }
E-相似的子串
题目本质上就是要我们求,在主串中选出k个不相交且相同的子串,这个子串的最长长度是多少。很显然,这个长度越长,我们就越难在主串中选出这k个不相交且相同的子串,其具有单调性可以二分。那么我们先利用字符串哈希预处理出Hash和Power数组,接下来二分答案ans,每次判断ans是否合法,也就是判断我们能否在主串中选出k个不相交且相同的子串,并且这个子串长度为ans。
至于判断部分,我们从前往后扫描主串,利用哈希表V记录长度为len的子串的位置,用哈希表T记录长度为len的子串的个数。每次判断当前长度为len的子串,其V记录的上一个位置是否合法(也就是会不会相交),若合法更新V和T即可。若T记录的某子串个数大于等于k了,返回1表示这个长度len合法。若扫描完整个字符串也未能返回1,我们就返回0表示这个长度len非法。
时间复杂度:O(nlog(n))。
代码部分:
#include<bits/stdc++.h> using namespace std; int n,k; unordered_map<unsigned long long,int>V,T; unsigned long long Hash[200005]={0},Power[200005],base=1e9+7; char R[200005]; bool judge(int len) { int i; unsigned long long t; V.clear(),T.clear(); for(i=len;i<=n;i++) { t=Hash[i]-Hash[i-len]*Power[len]; if(!V[t])V[t]=i,T[t]++; else if(V[t]<=i-len)V[t]=i,T[t]++; if(T[t]>=k)return 1; } return 0; } int main() { int i,l,r,mid,ans; scanf("%d%d%s",&n,&k,R+1); for(Power[0]=i=1;i<=n;i++) { Hash[i]=Hash[i-1]*base+R[i]-'a'+1; Power[i]=Power[i-1]*base; } for(l=0,r=n/k;l<=r;) { mid=(l+r)>>1; if(judge(mid))ans=mid,l=mid+1; else r=mid-1; } printf("%d\n",ans); return 0; }
若有疏漏之处,还望大佬轻喷。