前面的碎碎念:
有点可惜没有AK呀,D题思想江化陷入到BFS的思路里出不来了,还是太菜QAQ。

题解部分:

A-胖胖的牛牛
搜索题的关键其实也是定义状态,在这我们定义一个V数组,V[x][y][d]表示到达(x,y)处方向是d的最小转弯次数,之后我们进行BFS并不断更新这个V数组就行了。
时间复杂度:O(n^2)。
代码部分:

#include<bits/stdc++.h>
using namespace std;

struct node
{
    int x,y,h,d;
}Q[100005];
int r=0,f=0,flag=0,dx[]={1,-1,0,0},dy[]={0,0,1,-1};
int main()
{
    int i,j,k,n,x,y,h,d,X,Y,H,ans=1e9,V[105][105][4];
    char R[105][105];
    scanf("%d",&n);
    memset(V,0x3f,sizeof(V));
    for(i=0;i<n;i++)
        for(j=0;j<n;j++)
        {
            scanf(" %c",&R[i][j]);
            if(R[i][j]!='A')continue;
            for(k=0;k<4;k++)Q[r].x=i,Q[r].y=j,Q[r].h=0,Q[r++].d=k,V[i][j][k]=0;
        }
    while(r!=f)
    {
        x=Q[f].x,y=Q[f].y,h=Q[f].h,d=Q[f++].d;
        if(R[x][y]=='B')flag=1,ans=min(ans,h);
        for(i=0;i<4;i++)
        {
            X=x+dx[i],Y=y+dy[i],H=h+(i==d?0:1);
            if(X<0||X>=n||Y<0||Y>=n||R[X][Y]=='x'||H>=V[X][Y][i])continue;
            Q[r].h=V[X][Y][i]=H,Q[r].x=X,Q[r].y=Y,Q[r++].d=i;
        }
    }
    if(!flag)ans=-1;
    printf("%d\n",ans);
    return 0;
}

B-牛牛的零食
我们求出[1,a-1]区间能被8整除但是不能被那些数整除的数字个数,再求出[1,b]区间能被8整除但是不能被那些数整除的数字个数,用后者减去前者即可得到最终答案。
至于求取的方法,使用容斥定理即可,比如:求[1,n]区间能被8整除,但是不能被3和5整除的数字个数,那就是n/8-n/lcm(8,3)-n/lcm(8,5)+n/lcm(8,3,5),显然这个过程我们可以用DFS实现。
时间复杂度:O(2^n)。
代码部分:

#include<bits/stdc++.h>
using namespace std;

long long gcd(long long a,long long b)
{
    return b?gcd(b,a%b):a;
}
int i,a,b,n,x[20],ans;
void DFS(int t,int L,long long s,bool flag)
{
    if(L==n)
    {
        if(flag)ans-=t/s;
        else ans+=t/s;
        return;
    }
    DFS(t,L+1,s,flag);
    DFS(t,L+1,s*x[L]/gcd(s,x[L]),flag^1);
}
int main()
{
    scanf("%d",&n);
    for(i=0;i<n;i++)scanf("%d",&x[i]);
    scanf("%d%d",&a,&b);
    ans=0,DFS(a-1,0,8,0),ans=-ans,DFS(b,0,8,0);
    printf("%d\n",ans);
    return 0;
}

C-牛牛的最美味和最不美味的零食
很显然,维护区间最大值和最小值使用线段树即可,关键是如何处理一操作造成的数字左移。我们可以反着思考,对于某次操作给出的下标,可能需要右移才是原数组的真实下标。举个例子,原始序列为1 2 3 4 5,我们一开始对下标为2的地方进行一操作,那么序列变成了1 3 4 5。再次询问下标2,对应的数字应该是3。那么在原始序列上,因为2一开始进行过一操作,此时询问的下标2应该+1变成3,而真实下标3在原始序列对应的数字正就是3。
那么如何求取这个真实下标呢。我们对每次一操作给出的下标id,设其真实下标为x。那么有x=id+sum(x),其中sum(x)为前缀和,求出这个x后我们就在x处置1,然后更新前缀和。这个前缀和可以由树状数组实现,而找x的过程可以二分实现。或者也可以用线段树维护区间和,然后树上二分找这个x,相比于二分+树状数组可以省去一个log的时间复杂度。此处我使用前者的写法。
时间复杂度:O(n图片说明log(n)+m图片说明log(n)图片说明log(n))。
代码部分:

#include<bits/stdc++.h>
using namespace std;

inline int read()
{
    int n=0,w=0;
    char c=0;
    while(!isdigit(c))w|=c=='-',c=getchar();
    while (isdigit(c))n=(n<<3)+(n<<1)+(c^48),c=getchar();
    return w?-n:n;
}
int ans1,ans2,n,S[1000005]={0},Max[4000005],Min[4000005];
int lowbit(int x)
{
    return x&(-x);
}
void update(int i,int x)
{
    while(i<=n)S[i]+=x,i+=lowbit(i);
}
int getsum(int i)
{
    int s=0;
    while(i)s+=S[i],i-=lowbit(i);
    return s;
}
void build(int L,int R,int x)
{
    if(L==R)
    {
        Max[x]=Min[x]=read();
        return;
    }
    int M=(L+R)>>1;
    build(L,M,2*x),build(M+1,R,2*x+1);
    Max[x]=max(Max[2*x],Max[2*x+1]);
    Min[x]=min(Min[2*x],Min[2*x+1]);
}
void update2(int L,int R,int l,int r,int x)
{
    if(l<=L&&R<=r)
    {
        Max[x]=-2e9,Min[x]=2e9;
        return;
    }
    int M=(L+R)>>1;
    if(M>=l)update2(L,M,l,r,2*x);
    if(M<r)update2(M+1,R,l,r,2*x+1);
    Max[x]=max(Max[2*x],Max[2*x+1]);
    Min[x]=min(Min[2*x],Min[2*x+1]);
}
void search(int L,int R,int l,int r,int x)
{
    if(l<=L&&R<=r)
    {
        ans1=min(ans1,Min[x]),ans2=max(ans2,Max[x]);
        return;
    }
    int M=(L+R)>>1;
    if(M>=l)search(L,M,l,r,2*x);
    if(M<r)search(M+1,R,l,r,2*x+1);
}
int find(int id)
{
    int l=1,r=n,mid,ans=n;
    while(l<=r)
    {
        mid=(l+r)>>1;
        if(mid-getsum(mid)<id)l=mid+1;
        else ans=mid,r=mid-1;
    }
    return ans;
}
int main()
{
    int i,m,op,l,r,x;
    n=read(),m=read();
    build(1,n,1);
    while(m--)
    {
        op=read();
        if(op==1)
        {
            x=read(),x=find(x);
            update2(1,n,x,x,1),update(x,1);
        }
        else
        {
            l=read(),r=read(),l=find(l),r=find(r);
            ans1=1e9,ans2=-1e9,search(1,n,l,r,1);
            printf("%d %d\n",ans1,ans2);
        }
    }
    return 0;
}

D-瘦了的牛牛去旅游
此处我们设DP[i][j][k]为从i到j,经过k条边的最短路径,设原始地图由R数组保存,那么我们可以通过弗洛伊德算法的思想递推求出这个DP数组,转移方程如下:

    for(l=2;l<=n;l++)
        for(k=1;k<=n;k++)
            for(i=1;i<=n;i++)
                for(j=1;j<=n;j++)DP[i][j][l]=min(DP[i][j][l],DP[i][k][l-1]+R[k][j]);

求出这个DP数组后,我们设ans[i][j]为i到j的最小路径密度,很显然这个ans数组可以由DP数组求出。预处理出ans数组之后,对于每次询问,根据ans数组的值输出相应的解即可。
时间复杂度:O(n^4)。
代码部分:

#include<bits/stdc++.h>
using namespace std;

int R[55][55],DP[55][55][55];
double ans[55][55];
int main()
{
    int i,j,k,l,n,m,q;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
            for(k=0;k<=n;k++)ans[i][j]=R[i][j]=DP[i][j][k]=1e9;
    while(m--)
    {
        scanf("%d%d%d",&i,&j,&k);
        R[i][j]=min(R[i][j],k),DP[i][j][1]=R[i][j];
    }
    for(i=1;i<=n;i++)DP[i][i][0]=0;
    for(l=2;l<=n;l++)
        for(k=1;k<=n;k++)
            for(i=1;i<=n;i++)
                for(j=1;j<=n;j++)DP[i][j][l]=min(DP[i][j][l],DP[i][k][l-1]+R[k][j]);
    for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
           for(k=1;k<=n;k++)
               if(DP[i][j][k]<1e9)ans[i][j]=min(ans[i][j],1.0*DP[i][j][k]/k);
    scanf("%d",&q);
    while(q--)
    {
        scanf("%d%d",&i,&j);
        if(ans[i][j]<1e9)printf("%.3lf\n",ans[i][j]);
        else printf("OMG!\n");
    }
    return 0;
}

E-只能吃土豆的牛牛
这题我是找规律解的,建议去看其他正规解法...
和1组合能生成的数字是1,有1个;和3组合能生成的数字是3/3+1,有2个;和9组合能生成的数字是9/1+9/3+9/1+3+9,有4个,此处可以看出规律1,2,4,...,2^(n-1)。根据等比数列求和公式我们可以知道1+2+4+...+2^(n-1)=2^n-1。那么我们找到2^n-1>=k最小的那个n,然后从n遍历到1贪心地去取数字就行了。
时间复杂度:O(T图片说明log(k))。
代码部分:

#include<bits/stdc++.h>
using namespace std;

long long fastpow(long long a,int b)
{
    long long s=1;
    for(;b;b>>=1,a=a*a)if(b&1)s*=a;
    return s;
}
int main()
{
    int i,x,c=0,T;
    long long one=1,ans;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&x),ans=0;
        for(i=1;(one<<i)-1<x;i++);
        while(i--)if(x>=(one<<i))x-=(one<<i),ans+=fastpow(3,i);
        printf("Case #%d: %lld\n",++c,ans);
    }
    return 0;
}

完结撒花,若有疏漏之处,还望大佬轻喷。