一.闲话
打了下比赛,把A-E做了后,看了下F,思考了一会儿,发现不可做,就来写题解辣,qwq
二.题解部分
A 打怪
签到题,我的思路是先判断-1的情况(如果你可以一刀999秒掉小怪,由于你先手,那你就可以砍无数小怪了)
然后,我们先计算出,砍完一只小怪,你扣的血,假设为x,这样,答案就是floor((n-1)/x)[n-1的原因是你至少要留1滴血,不然GG,2333]
代码
#include<bits/stdc++.h> using namespace std; int main(){ int t; scanf("%d",&t); while(t--){ int h,a,H,A; scanf("%d%d%d%d",&h,&a,&H,&A); if(a>=H){ puts("-1"); continue; } int kil=ceil(double(H)/a),ht=(kil-1)*A; printf("%d\n",(h-1)/ht); } return 0; }
B 吃水果
这个题我想的时间比后面三道题还久。。。老了老了,qwq
我的思路很简单,首先特判n=m的情况,然后,我们来倒推一下
最后,肯定是经过我们一系列神奇的操作后,n=m,然后我们再用n天吃掉就好了
以下为了好讲解,我们不妨设n<m
我们注意到,我们的操作只有三种——和和
我们仔细分析下,发现,因为n<m那么这个操作其实是完全没用的(只是无谓的增加了两者差距罢了),所以,我们实际有用的操作就只有n-1,m-1和
然后,因为上面我们说了,最后一定是变成了n=m这个情况,但是,很明显,n-1,m-1这个操作不能使得n=m,所以,使n=m的上一次操作一定是n*2,m
即此时一定满足n=m/2
那么,我们就来算算什么时候可以做到n=m/2
首先,我们假设我们现在不进行这个操作,那么设我们进行了x次n-1,m-1次操作后,使得n=m/2,即
2(n-x)=m-x
解得x=2n-m
当然,x必须大于等于0才有意义
然后,我们就可以直接枚举,我们一开始进行了i次n*2,m操作,这样,每次统计一下答案,取所有的最小即可
因为n*2,m这个操作会使得后面所需步骤几何级增大,所以,不难发现,我们枚举的i其实很小(我没仔细算,估计logn级别,所以,我就直接枚举到20)
代码
#include<bits/stdc++.h> #define int long long using namespace std; signed main(){ int T; scanf("%lld",&T); while(T--){ int n,m; scanf("%lld%lld",&n,&m); if(n==m){ printf("%lld\n",n); continue; } if(n>m){ swap(n,m); } int ans=2e9; for(int i=0;i<=20;++i){//乘i次2后,n是m的1/2 int now=2*n-m; if(now>=0){ ans=min(ans,now+i+1+m-now); } n*=2; if(n>m){ swap(n,m); } } printf("%lld\n",ans); } return 0; }
C 四个选项
这道题,看到总共n就12,所以,我们按下先不考虑其他限制条件
12个题的选择结果总共有,注意到,总情况非常小,明显爆搜可以跑出,所以我们可以直接打爆搜再验证,但是,验证的复杂度是O(m),所以,为了稳重起见,我们要对代码进行一些小优化
注意到,题目给的限制是要求x和y相同,那么,我们再假设有一限制要求y和z相同,那么,就等价于
x,y,z都相同,注意到这个特点后,我们就可以直接开个并查集,将必须相同的点放同一个并查集里面,
每次枚举时判断下,自身所属的并查集是否已经选择了选项,如果选择了,就跳过不枚举即可。
当然,我们也可以同时判断下我们每次选一个选项时,若选项剩余的个数小于并查集的大小,那么该并查集就不能选这个选项了。
这样,速度就很快了~
代码
#include<bits/stdc++.h> using namespace std; const int N=13; int fa[N],ans; int siz[N]; bool c[N]; inline int find(int x){ return fa[x]==x?x:fa[x]=find(fa[x]); } inline void merge(int x,int y){ int a=find(x),b=find(y); if(a!=b){ fa[a]=b,siz[b]+=siz[a]; } } inline void dfs(int now,int na,int nb,int nc,int nd){ if(now==13){ ++ans; return; } if(c[fa[now]]){ dfs(now+1,na,nb,nc,nd); return; } if(na>=siz[fa[now]]){ c[fa[now]]=1; dfs(now+1,na-siz[fa[now]],nb,nc,nd); c[fa[now]]=0; } if(nb>=siz[fa[now]]){ c[fa[now]]=1; dfs(now+1,na,nb-siz[fa[now]],nc,nd); c[fa[now]]=0; } if(nc>=siz[fa[now]]){ c[fa[now]]=1; dfs(now+1,na,nb,nc-siz[fa[now]],nd); c[fa[now]]=0; } if(nd>=siz[fa[now]]){ c[fa[now]]=1; dfs(now+1,na,nb,nc,nd-siz[fa[now]]); c[fa[now]]=0; } } int main(){ int na,nb,nc,nd,m; scanf("%d%d%d%d%d",&na,&nb,&nc,&nd,&m); int n=na+nb+nc+nd; for(int i=1;i<=n;++i){ fa[i]=i;siz[i]=1; } for(int i=1;i<=m;++i){ int x,y; scanf("%d%d",&x,&y); merge(x,y); } for(int i=1;i<=n;++i){ fa[i]=find(i); } dfs(1,na,nb,nc,nd); printf("%d",ans); return 0; }
D 最短路变了
这道题挺简单的,我们先分别算出1到所有点的最短路距离(正向建边,设为),再算出所有点到n的最短路距离(反向建边,设为)
然后,这里有个我猜的结论(滑稽,脑海中盲目证明了):
如果反向的边是最短路上的边,那么最短路长度一定不会变小(即答案为NO)
这个我们可以直接标记一下就可以处理了
那么,现在我们该考虑的就是如果反向的边不是最短路上的边的情况
我们假设,这条边反向后,最短路长度变短了,那么,不难发现,如果最短路长度变短了,那么现在必然要走这条反向后的边,那么现在的必须走反向边的最短路的长度就变成了:
我们把上面的值和原来的最短路长度比较一下,现在比原来小的话就输出YES,否则输出NO,即可
代码
#include<bits/stdc++.h> #define int long long using namespace std; const int N=2e5+1; struct node{ int v,w,nex,id; }t[N]; int U[N],V[N],W[N]; priority_queue<pair<int,int> >sp; bool init[N]; int dis[N][2];bool vis[N]; int las[N],len; inline void add(int u,int v,int w,int id){ t[++len]=(node){v,w,las[u],id},las[u]=len; } inline void dijkstra(int st,int num){ memset(vis,0,sizeof(vis)); dis[st][num]=0;sp.push(make_pair(0,st)); while(!sp.empty()){ int x=sp.top().second;sp.pop(); if(vis[x]){ continue; } vis[x]=1; for(int i=las[x];i;i=t[i].nex){ int v=t[i].v,w=t[i].w; if(dis[v][num]>dis[x][num]+w){ dis[v][num]=dis[x][num]+w; sp.push(make_pair(-dis[v][num],v)); } } } } signed main(){ memset(dis,0x3f3f,sizeof(dis)); int n,m; scanf("%lld%lld",&n,&m); for(int i=1;i<=m;++i){ int u,v,w; scanf("%lld%lld%lld",&u,&v,&w); U[i]=u,V[i]=v,W[i]=w; add(v,u,w,i); } dijkstra(n,1); memset(las,0,sizeof(las)),len=0; for(int i=1;i<=m;++i){ add(U[i],V[i],W[i],i); } dijkstra(1,0); int now=1; while(now!=n){ for(int i=las[now];i;i=t[i].nex){ int v=t[i].v,w=t[i].w; if(dis[now][0]+w+dis[v][1]==dis[n][0]){ now=v;init[t[i].id]=1; break; } } } int q; scanf("%lld",&q); while(q--){ int x; scanf("%lld",&x); if(init[x]){ puts("NO"); }else{ int now=dis[V[x]][0]+W[x]+dis[U[x]][1]; if(now<dis[n][0]){ puts("YES"); }else{ puts("NO"); } } } return 0; }
E 相似的子串
我们注意到,这个“公共前缀”其实没啥用,我们直接把公共前缀当一个串的话其实答案是一样的,所以我们可以把题目转化成,求至少出现k次,不重叠的最长子串
咋看一下像极了SA的一道模板题,然而,太久没打有点生疏了,所以,我打算直接hash暴力做(滑稽)
考虑二分答案
我们假设二分出,串的长度为x,那么我们就可以直接用hash判断串是否相等了(求hash可以直接O(n)递推出所有长度为x的串的hash,所以复杂度没问题)
现在问题就在于,如何使得串不重叠。
我们首先,假设有若干个相同的长度为的串,他们的起点分别为
a1,a2...ak
我们要尽量多的选出不重叠的串
定义:两个串i,j(i<j)不重叠,当且仅当(ai+x-1<aj)[即i串的末尾在j串开头的左边]
这个我们就可以直接贪心做了,我们每次尽量选靠前的串,能选就选,就可以了
关于这一步的实现,我一开始打算先把所有长度为x的串的hash值和起点存下来,再按照hash值为第一关键字,起点为第二关键字排序弄,想了下太麻烦了,就为了省事直接用map做了
复杂度其实一开始交的时候,我都以为可能会T,然后做好了卡常的准备了的,23333
代码
#include<bits/stdc++.h> using namespace std; const int N=2e5+1,base=23; string a; int n,k; unsigned long long S[N]; map<unsigned long long,pair<int,int> >s; inline bool check(int x){ s.clear(); unsigned long long now=0;int tot=0; for(int i=1;i<=x;++i){ now=now*base+(a[i-1]-'a'+1); } s[now]=make_pair(x,1); for(int i=x+1;i<=n;++i){ now=now-(S[x-1]*(a[i-x-1]-'a'+1)); now=now*base+(a[i-1]-'a'+1); if(s.find(now)!=s.end()){ if(s[now].first<i-x+1){ int fow=s[now].second; if(fow+1==k){ return true; } s[now]=make_pair(i,fow+1); } }else{ s[now]=make_pair(i,1); } } return false; } int main(){ S[0]=1; scanf("%d%d",&n,&k); if(k==1){ printf("%d",n); return 0; } for(int i=1;i<=n;++i){ S[i]=S[i-1]*base; } cin>>a; int l=1,r=n/k,ans=0; while(l<=r){ int mid=(l+r)>>1; if(check(mid)){ ans=mid,l=mid+1; }else{ r=mid-1; } } printf("%d",ans); return 0; }
qwq终于写完了,%%%%%%各位神仙