一.闲话

打了下比赛,把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终于写完了,%%%%%%各位神仙