浙江理工大学zstu2016新生赛题解

20 篇文章 0 订阅
10 篇文章 0 订阅

A:Save the Princess

题目:点击打开链接

题意:n个人横向排好队,其中左数第k个是公主,LYF和BH分别可以杀掉队列最左边的或者最右边的人,直到某个人拯救公主,两个人都会选择最佳杀人方案,LYF先杀,问谁能拯救公主。

分析:

当公主在队列两端时,LYF先行动,所以LYF是赢家,其他情况时,当某个人正好杀掉与公主相邻的人时,另一个人肯定是赢家,所以不能先杀与公主相邻的人,因为两人交互行动,且LYF先行动,在杀与公主相邻的人之前可以任选最左或者最右的人杀,因此只要确定n-3的奇偶性,就可以判断谁是赢家了。

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll unsigned long long
const ll INF = 1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=100;
const int MOD=1000000007;
const double eps=1e-10;
const double pi=acos(-1.0);
using namespace std;
int main()
{
    int T,n,k;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&k);
        if(k==1||k==n) puts("LYF");
        else
        {
            if(n&1) puts("BH");
            else puts("LYF");
        }
    }
}

B:巴比伦花园

题目:点击打开链接

题意:有n个阶梯高度分别为h_i(1<=i<=n),有q个询问,每次询问区间[l,r],可以将h_i(l<=i<=r)改成f_i,满足f_i<=h_i,f_i>=1,f_(i+1)-f_i=k,求每次询问满足以上条件的最长长度的连续数列长度。

分析:

因为要求出区间内满足条件的最长数列,所以可以先处理出从i出发,最长可以连续增长到哪个点。我们发现一旦i可以到达j(j>i),i+1就也一定能达到j,因此到达的最长位置是不递减的,因为最左端高度从1开始,得到的是最长的长度。我们可以先dp一下,dp[i]表示i能到的最远位置,len表示当前的阶梯的高度,cur表示每次达到的最远位置。

处理完dp后,对于区间[l,r],可以先考虑左端点在[l,r]内,最远距离超过r的点,因为这可能有很多点,要取最长距离,就是求最左端的点的坐标pos,这个可以通过二分dp值得到,如果pos<=l的话,那说明有一条最长的阶梯能穿过这个区间,那么最长的长度就是这个区间的长度。否则的话区间的最长长度还可能是从[l,pos-1]中的某一个点出发,走过的最远距离的长度,这些点的右端点是不会超过r的,因此我们需要将区间内的最长长度(dp[i]-i+1)可以用线段树或者st表保存起来,在这里我用了线段树,因为还可以保存区间内的点可以达到的最远距离,之前就不用二分dp值,只需要线段树查询一下区间内最远距离超过r的最近的点就行了,保存其长度,然后查询左端点在[l,pos-1]内最长的长度,比较两者的大小即可

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll long long
const ll INF = 1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=100100;
const int MOD=1000000007;
const double eps=1e-9;
const double pi=acos(-1.0);
using namespace std;
int dp[N];
ll h[N],k;
int n,q,l,r;
int mxlen[N<<2],mxp[N<<2];
void pushup(int rt)
{
    mxlen[rt]=max(mxlen[rt<<1],mxlen[rt<<1|1]);
    mxp[rt]=max(mxp[rt<<1],mxp[rt<<1|1]);
}
void build(int l,int r,int rt)
{
    if(l==r)
    {
        mxp[rt]=dp[l];
        mxlen[rt]=dp[l]-l+1;
        return;
    }
    int m=l+r>>1;
    build(lson);
    build(rson);
    pushup(rt);
}
int query_p(int p,int l,int r,int rt)
{
    if(l==r) return l;
    int m=l+r>>1;
    if(mxp[rt<<1]>=p) return query_p(p,lson);
    else return query_p(p,rson);
}
int query_s(int L,int R,int l,int r,int rt)
{
    if(L<=l&&r<=R) return mxlen[rt];
    int m=l+r>>1;
    int ans=0;
    if(L<=m) ans=max(ans,query_s(L,R,lson));
    if(m<R) ans=max(ans,query_s(L,R,rson));
    return ans;
}
int main()
{
    int T,i,j;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%lld%d",&n,&k,&q);
        for(i=1;i<=n;i++)scanf("%lld",&h[i]);
        int cur=1;
        ll len=1;
        for(i=1;i<=n;i++)
        {
            dp[i]=cur;
            if(cur==n) dp[i]=n;
            else
            {
                while(h[cur+1]>=len+k)
                {
                    len+=k;
                    dp[i]=++cur;
                }
                len-=k;
            }
        }
        build(1,n,1);
        while(q--)
        {
            scanf("%d%d",&l,&r);
            int pos=query_p(r,1,n,1);
            if(pos<=l) {printf("%d\n",r-l+1);continue;}
            int ans=r-pos+1;
            if(pos-1>=l) ans=max(ans,query_s(l,pos-1,1,n,1));
            printf("%d\n",ans);
        }
    }
}

C:极差

题目:点击打开链接

题意:有n个数,求这些数的最大值与最小值之间的差距

分析:

先定义最小值为正无穷,最大值为零,每次输入一个数都判断最小值是不是大于它,如果最小值大于它,则把该值赋值给最小值,再判断最大值是不是小于它,如果最大值小于它,就把该值赋值给最大值,最后将最大值与最小值相减

#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll long long
const ll INF = (1ULL<<63)-1;
const int inf=0x3f3f3f3f;
const int M=100010;
const int N=100100;
const ll MOD=1000000007;
const double eps=1e-8;
const double pi=acos(-1.0);
using namespace std;
int a[N],n;
int main()
{
    int T,i;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        int mx=0,mn=inf;
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            mx=max(mx,a[i]);
            mn=min(mn,a[i]);
        }
        printf("%d\n",mx-mn);
    }
}
D:圣杯战争

题目:点击打开链接
题意:KI为了参加圣杯战争,决定向魔术师CS请教,CS为了检测KI身为御主的潜力,决定进行一场模拟圣杯战争:
   CS召唤了n个实验怪兽,第i号怪兽在i这个位置出。并把KI召唤出的第i位从者安排在pos(i)处,总共有m位从者。
   第i只怪兽有战斗力atk(i), 而i号从者的体力为AP(i)。如果从者想要移动,他必须战胜他当前位置处的怪兽,战胜的条件为AP>=atk, 然后该从者的AP会减少atk, 注意从者只能从i移动到i+1,或移动到i-1处,注意一旦开始移动就只能向一个方向移动了。
   作为优秀的御主,必须要有杰出的判断力,所以CS会给出Q次询问,每次给出一个区间[l, r]: 要求KI只能派出一个从者,并能打败这个区间中尽可能多的怪物,求能打败的最大怪物数。强制在线。
分析:
一个从者可以往左或者往右走,直至走到体力为零,因此最多形成两条线段,因为求的是给定区间内的最多杀敌数,且只能使用一个从者,因此可以把题意转化为,求询问的一条线段与给定的多条线段中交集最大的值。
我们可以先对至少可以杀掉一个敌人的从者,向左和向右能杀敌的区间。我们可以先预处理出atk的前缀和以及后缀和,进行两次二分,确定从者最远达到的两个坐标,并根据两条距离的端点坐标,求出在区间[1,n]上点i最远达到的坐标mxR_i,对此我们再用pair记录所有线段的右端点和对应的左端点,排序。
接下来用主席树,把区间[1,n]每一个点建立一棵线段树,维护当前的点中形成的最长的线段长度mxlen和最右端点mxr,其中root_0这棵树的mxlen和mxr都为-inf,当点i不是某个线段的端点时,root_i这颗线段树直接复制前一棵,即root[i]=root[i-1],当点i是某个线段的左端点l时,因为此时线段不完整,所以取mxlen=-inf,mxr=r,r是该线段的右端点。当i是某个线段的右端点时,因为之前的线段已经被排序,右端点相同时第一个一定是左端点最小的,所以我们直接把该线段的长度和右端点记录到root_i这棵线段树中。update每一个点时,比较两子节点的mxlen和mxr值,取大值为当前节点的mxlen和mxr值。这个和一般线段树的pushup相同。
记录完[1,n]上的线段后,开始询问与[pl,pr]的最大交集。有以下四个步骤:
①求出左端点在[1,pl-1]内的线段的最大右端点值res,ans=max(ans,res-pl+1)
②二分求出右端点在[pr+1,n]内的线段的最小左端点值res,ans=max(ans,r-res+1)
③求出线段在[pl,pr]内的最长长度res,ans=max(ans,res)
④最后注意ans不会超过pr-pl+1且不会小于0,ans=max(min(ans,pr-pl+1),0)。
在第二步时,二分步骤为,判断当前区间是否被询问区间包含,若包含,则判断其mxr是否大于pr,若小于等于pr,返回无效值inf,若此时已经是叶子节点了,则返回坐标l,若不包含,则先向左节点判断,如果得到坐标l,则直接返回,否则向右判断。

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll long long
const ll INF=1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=101000;
const int MOD=1000000007;
const double eps=1e-9;
const double pi=acos(-1.0);
using namespace std;
struct node{int l,r,mxlen,mxr;}T[N*30];
int n,m,q,atk[N*2],pos[N/2],ap[N/2],mxR[N*2],l,r,ans,cnt;
int Tcnt,root[N*2];
ll pre[N*2],repre[N*2];
PR a[N];
void update(int l,int r,int &x,int y,int L,int R,int len)
{
    x=++Tcnt;T[x]=T[y];
    if(l==r)
    {
        T[x].mxlen=len;
        T[x].mxr=R;
        return;
    }
    int m=l+r>>1;
    if(L<=m) update(l,m,T[x].l,T[y].l,L,R,len);
    else update(m+1,r,T[x].r,T[y].r,L,R,len);
    T[x].mxlen=max(T[T[x].l].mxlen,T[T[x].r].mxlen);
    T[x].mxr=max(T[T[x].l].mxr,T[T[x].r].mxr);
}
int query_l(int l,int r,int x,int L,int R)
{
    if(L>R) return -1;
    if(L<=l&&r<=R) return T[x].mxr;
    int m=l+r>>1,res=-inf;
    if(L<=m) res=max(res,query_l(l,m,T[x].l,L,R));
    if(m<R) res=max(res,query_l(m+1,r,T[x].r,L,R));
    return res;
}
int query_r(int l,int r,int x,int L,int R)
{
    if(L<=l&&r<=R)
    {
        if(T[x].mxr<=R) return inf;
        if(l==r) return l;
    }
    int m=l+r>>1,res=inf;
    if(L<=m) res=query_r(l,m,T[x].l,L,R);
    if(res!=inf) return res;
    if(m<R) res=query_r(m+1,r,T[x].r,L,R);
    return res;
}
int query_mid(int l,int r,int x,int L,int R)
{
    if(L<=l&&r<=R) return T[x].mxlen;
    int m=l+r>>1,res=-inf;
    if(L<=m) res=max(res,query_mid(l,m,T[x].l,L,R));
    if(m<R) res=max(res,query_mid(m+1,r,T[x].r,L,R));
    return res;
}
int main()
{
    int casT,i,j;
    scanf("%d",&casT);
    while(casT--)
    {
        scanf("%d%d%d",&n,&m,&q);
        T[0].mxlen=-inf,T[0].mxr=-inf;
        memset(mxR,0,sizeof(mxR));
        memset(repre,0,sizeof(repre));
        cnt=0;Tcnt=0;
        for(i=1;i<=n;i++)scanf("%d",&atk[i]),pre[i]=pre[i-1]+atk[i];
        for(i=n;i>=1;i--)repre[i]=repre[i+1]+atk[i];
        for(i=1;i<=m;i++)scanf("%d",&pos[i]);
        for(i=1;i<=m;i++)scanf("%d",&ap[i]);
        for(i=1;i<=m;i++)
        {
            if(ap[i]<atk[pos[i]]) continue;
            int L=1,R=pos[i],res1=pos[i],res2=pos[i],mid;
            while(L<=R)
            {
                mid=L+R>>1;
                if(pre[pos[i]]-pre[mid-1]<=ap[i]) res1=mid,R=mid-1;
                else L=mid+1;
            }
            mxR[res1]=max(mxR[res1],pos[i]);
            L=pos[i],R=n;
            while(L<=R)
            {
                mid=L+R>>1;
                if(repre[pos[i]]-repre[mid+1]<=ap[i]) res2=mid,L=mid+1;
                else R=mid-1;
            }
            mxR[pos[i]]=max(mxR[pos[i]],res2);
        }
        for(i=1;i<=n;i++)
            if(mxR[i]) a[++cnt]=MP(mxR[i],i);
        sort(a+1,a+1+cnt);
        int cur=0;
        for(i=1;i<=n;i++)
        {
            root[i]=root[i-1];
            if(mxR[i]) update(1,n,root[i],root[i-1],i,mxR[i],-inf);
            if(cur+1<=cnt&&a[cur+1].fi==i)
            {
                cur++;
                update(1,n,root[i],root[i],a[cur].se,a[cur].fi,a[cur].fi-a[cur].se+1);
                while(cur+1<=cnt&&a[cur+1].fi==i) cur++;
            }
        }
        ans=0;
        while(q--)
        {
            scanf("%d%d",&l,&r);
            l^=ans,r^=ans;if(l>r) swap(l,r);
            int mx=0;
            mx=max(mx,query_l(1,n,root[r],1,l-1)-l+1);
            mx=max(mx,r-query_r(1,n,root[r],l,r)+1);
            mx=max(mx,query_mid(1,n,root[r],l,r));
            ans=max(min(mx,r-l+1),0);
            printf("%d\n",ans);
        }
    }
}

E:校庆

题目:点击打开链接

题意:输入一个日期,判断它离2017-10-28还差几天,保证日期在1900-1-1和2017-10-28之间

分析:可以先写一个函数,算出从1900-1-1开始算起,到指定日期一共有多少天,年份从1900开始枚举到改年的前一年,一共有多少年,先默认每年都是365天,在枚举年份的时候判断它是不是闰年,然后再总天数里面加上闰年的天数,把平年各月的天数存到一个数组中,下标表示月份,然后从一月开始枚举到该月的前一个月,将天数加到总天数中,最后再加上几号就行了,还要判断该年是不是闰年,如果是闰年且年份大于2月,总天数还要加一。然后就可以将2017-10-28带入函数中算出总天数再减去输入日期带入函数的总天数,即为答案。

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll unsigned long long
const ll INF = 1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=100;
const int MOD=1000000007;
const double eps=1e-10;
const double pi=acos(-1.0);
using namespace std;
int y[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};
bool judge(int x)
{
    if(x%400==0||(x%4==0&&x%100!=0)) return true;
    return false;
}
int calc(int a,int b,int c)
{
    int ans=0,tmp1=0,tmp2=0,i;
    for(i=1899;i<=a-1;i++)
        if(judge(i)) tmp1++;
    for(i=1;i<=b-1;i++)
        tmp2+=y[i];
    ans=365*(a-1)+tmp1+tmp2+c;
    if(judge(a)&&b>2) ans++;
    return ans;
}
int main()
{
    int T,a,b,c;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%d",&a,&b,&c);
        printf("%d\n",calc(2017,10,28)-calc(a,b,c));
    }
}
F:牛吃草

题目:点击打开链接

题意:农夫有一个长满草的(x0, y0)为圆心,r为半径的圆形牛栏,他要将一头牛栓在坐标(x1, y1)栏桩上,但只让牛吃到一半草,问栓牛鼻的绳子应为多长?

分析:

绳笔直绕一圈可以形成一个圆,这个圆就是牛可以吃的范围,令该圆为圆一,牛栏的圆为圆二,圆一与圆二的覆盖面积是圆一的一半,设圆一半径为x,两圆心距离为d。

如果圆一完全被圆二包含,那么圆一的面积就是圆二面积的一半,答案就是r的根号二分之一。

如果圆一不被圆二覆盖,即圆一和圆二存在两个公共点,那么我们需要计算出覆盖面积,覆盖面积可以分为类似两部分计算,相交的两点与一个圆心分别形成一个扇形和一个三角形,那么覆盖面积的一部分就是扇形面积减去三角形面积了,另一部分同样,两部分面积相加就是覆盖面积了,至于计算过程用到一些三角函数和余弦定理,详细看代码。接下来可以用二分的思想,因为d,x,r一定形成一个三角形,令左端点是l=d-r,右端点是r=d+r,每次判断x=(l+r)/2,判断圆二半径为x时,形成的覆盖面积是否大于圆一半径的一半,如果大于,l=m,如果小于,r=m。因为只要保留四位小数,所以循环一百次就绝对够了,最后得到的l值就是答案。

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll unsigned long long
const ll INF = 1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=100100;
const int MOD=1000000007;
const double eps=1e-9;
const double pi=acos(-1.0);
using namespace std;
double r,s,d,tmp;
double dis(double x0,double y0,double x1,double y1) {return sqrt(sqr(x0-x1)+sqr(y0-y1));}
double calc(double x)
{
    double angle1=acos((d*d+x*x-r*r)/(d*x*2))*2;
    double tri1=x*x*sin(angle1)/2;
    double sec1=angle1*x*x/2;
    double sq1=sec1-tri1;
    double angle2=acos((d*d+r*r-x*x)/(d*r*2))*2;
    double tri2=r*r*sin(angle2)/2;
    double sec2=angle2*r*r/2;
    double sq2=sec2-tri2;
    double ans=sq1+sq2;
    return ans-s;
}
int main()
{
    double x0,y0,x1,y1;
    int T,i;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lf%lf%lf%lf%lf",&x0,&y0,&x1,&y1,&r);
        s=pi*r*r/2;
        d=dis(x0,y0,x1,y1);
        tmp=r/sqrt(2);
        if(tmp+d>r)
        {
            double L=d-r,R=d+r,m;
            for(i=0;i<100;i++)
            {
                m=(L+R)/2;
                if(calc(m)+eps>0) R=m;
                else L=m;
            }
            printf("%.4f\n",L);
        }
        else printf("%.4f\n",tmp);
    }
}
G:众数

题目:点击打开链接

题意:求出n个数中出现次数最多的数字,如果存在数量相同的数,从小到大输出

分析:

因为这些数不超过1000,所以用一个vis数组保存下标为该数,出现的次数,每次输入x时,vis[x]++,然后从1到1000,找数组的哪个vis值最大,令最大值为mx,然后再从1到1000遍历一遍,将vis[x]等于mx的x保存到ans答案数组中,cnt记录答案的个数,最后将ans数组输出,注意空格和变量的初始化。

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll long long
const ll INF = (1ULL<<63)-1;
const int inf=0x3f3f3f3f;
const int M=100010;
const int N=100100;
const ll MOD=1000000007;
const double eps=1e-8;
const double pi=acos(-1.0);
using namespace std;
int a[N],n,vis[N],ans[N],cnt;
int main()
{
    int T,i;
    scanf("%d",&T);
    while(T--)
    {
        memset(vis,0,sizeof(vis));
        cnt=0;
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            vis[a[i]]++;
        }
        int mx=0;
        for(i=1;i<=1000;i++)
        {
            mx=max(mx,vis[i]);
        }
        for(i=1;i<=1000;i++)
        {
            if(vis[i]==mx) ans[cnt++]=i;
        }
        for(i=0;i<cnt;i++)
        {
            printf("%d",ans[i]);
            if(i==cnt-1) puts("");
            else printf(" ");
        }
    }
}
H:KI的斐波那契

题目:点击打开链接

题意:f (0) = b, f (1) = a,
f (2) = f (1) + f (0) = ab,
f (3) = f (2) + f (1) = aba,
f (4) = f (3) + f (2) = abaab,
......
求 f (n) 中的第 m 位是哪个字母

分析:

对于f[n],字符串的长度就是n对应的斐波那契值,我们可以先预处理出n<=90的斐波那契数列为fb,fb都是在64位整数范围内的。

对于n>=2时,对应的字符串都是前者连接上前前者,因此如果m的大小大于fb[n-1],下标为n-2的字符串中也有对应的位置,对应的位置是m=m-f[n-1]。如果m的大小小于等于fb[n-1],那么下标为n-1的字符串也有对应的位置,对应的位置是m,因此可以这样逆推下去,直到m等于1或者0,如果是1,那么它就是a,如果是0就是b。

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll unsigned long long
const ll INF = 1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=100;
const int MOD=1000000007;
const double eps=1e-10;
const double pi=acos(-1.0);
using namespace std;
ll f[N],m;
int n;
void init()
{
    f[0]=f[1]=1;
    for(int i=2;i<=90;i++) f[i]=f[i-1]+f[i-2];
}
int main()
{
    init();
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%llu",&n,&m);
        if(n==0) {puts("b");continue;}
        for(int i=n;i>=3;i--)
        {
            if(m>f[i-1]) m-=f[i-1];
        }
        if(m==1) puts("a");
        else puts("b");
    }
}
I:萌新吃果果

题目:点击打开链接

题意:开学第一天,萌新要排排坐吃果果啦,KI要求萌新们坐成一排。
现在萌新们必须要按KI的秩序表一个一个地就坐。
萌新中包括男孩子、女孩子和扶她。
男孩子会毫不犹豫地坐到当前已经坐好的人的后一个座位,女孩子在入座时会和前面的**男孩子**的隔一个座位坐,而扶她会观察前面连续坐的人数,若人数大于等于心理容忍度$k$,那么扶她会隔一个座位坐,否则直接坐到当前的后一个座位。
那么问题来了,KI想知道至少需要多少把椅子,才能让这些萌新正好坐成一排。

分析:

直接枚举字符串的字符,ans表示一共需要多少把椅子,cnt表示在当前人坐之前已经有多少人连坐了,如果字符是a,那么直接ans++,cnt++,如果字符是b,判断他前面有没有人,如果有人且是男的,需要多加一把椅子空一格cnt=1,ans+=2,否则直接ans++,cnt++,如果字符是c,判断cnt的个数是否大于等于k,如果大于等于k,则需要多加一把椅子空一格cnt=1,ans+=2,否则直接ans++,cnt++。最后输出ans即可

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll unsigned long long
const ll INF = 1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=100100;
const int MOD=1000000007;
const double eps=1e-10;
const double pi=acos(-1.0);
using namespace std;
char a[N];
int main()
{
    int T,n,k,i;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d%s",&n,&k,a);
        int ans=0,cnt=0;
        for(i=0;a[i];i++)
        {
            if(a[i]=='a') ans++,cnt++;
            else if(a[i]=='b')
            {
                if(i&&a[i-1]=='a') ans+=2,cnt=1;
                else ans++,cnt++;
            }
            else
            {
                if(cnt>=k) ans+=2,cnt=1;
                else ans++,cnt++;
            }
        }
        printf("%d\n",ans);
    }
}

J:萌新的旅行

题目:点击打开链接

题意: zstu的萌新们准备去自助旅行,他们租了一辆吉普车,然后选择了n个城市作为游览地点。然后他们惊喜的发现他们选择的城市刚好绕城一个环。
   也就是说如果给所有城市按照0,1,2,……,n-1编号,0号城市和n-1号城市是相邻的,并且只能从i号城市去(i+1)%n号城市。
已知每个城市可以充油gas(i),从 i 到 (i+1)%n 城市耗油 cost(i)。
   假设这辆吉普车没有的油箱一开始是空的,并且没有上限。
   没有油的话自然就不能继续旅行了,这个问题让萌新们非常困扰。作为优秀的acmer,请你帮他们找到一个出发城市,使得萌新们能游览尽可能多的城市(注意最多游览n个城市)。如果有多个可选择的出发城市,那么请把他们按照编号从小到大输出。

分析:

因为要连续的经过城市,gas必须大于cost,且运动是单向的,所以可以把一个点和右边的边缩成一个值,表示在这个到这个城市到下一个城市还有多少gas,下标从一开始,令a_i=gas_i-cost_i,因为是环装城市分布,所以数组都开2*n,保证可以有一次循环。注意a_i表示的是城市i到达城市(i+1)时剩余的油量,不是城市i的剩余油量!

当所有的a_i都是小于等于零,即任何一点都不能到达下一个点,直接输出所有的点即可。

其他情况时,一段路的花费可以用前缀和相减快速计算,所以把前缀和pre也记下来。从一个城市出发时,假设能到达一个城市时,油量小于等于原先的城市,两者相减的油量小于等于零,故假设不成立,因此只要将点i向右遍历,遇到一个pre_j<=pre_(i-1)(pre_j表示到达城市(j+1)的剩余油量,pre_(i-1)表示从城市i出发时的初始油量),则说明已经走了尽可能远的路了,其中走过的城市数目是为(j-i)。

然后我们要考虑如何求出哪个最大的j,满足pre_j>pre_(i-1)。因为我们通过前缀和,求出了每达到一个城市的剩余油量,我们可以用倍增法,预处理出从点i开始算起,走2^k个城市的过程中的油量最低值,然后可以枚举每个点i,对路程长度进行二分,因为路程最长为n,所以令l=i,r=i+n,m=(l+r)/2,每次O(1)的算出从点i到点m的最低油量值,令最终能走到res城市,如果油量大于pre_(i-1),则说明还可以往右走,res=m,l=m+1,否则r=m-1,最后得到的(res-i+1)就是从点i出发,经过的城市数目,将一些个数和i保存一下,最后输出最大个数的i即可。

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll long long
const ll INF = 1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=100100;
const int MOD=1000000007;
const double eps=1e-9;
const double pi=acos(-1.0);
using namespace std;
int g[N*2],c[N*2],a[N*2];
int dp[N*2][18];
int pre[N*2];
int ans[N];
int vt[N];
int n,cnt;
int calc(int l,int r)
{
    int k=log2(r-l+1);
    return min(dp[l][k],dp[r-(1<<k)+1][k]);
}
int main()
{
    int T,i,j;
    scanf("%d",&T);
    while(T--)
    {
        cnt=0;
        memset(ans,0,sizeof(ans));
        scanf("%d",&n);
        for(i=1;i<=n;i++)scanf("%d",&g[i]),g[i+n]=g[i];
        for(i=1;i<=n;i++)scanf("%d",&c[i]),c[i+n]=c[i];
        int ok=0;
        for(i=1;i<=2*n;i++)
        {
            a[i]=g[i]-c[i];
            dp[i][0]=dp[i-1][0]+a[i];
            if(a[i]>0) ok=1;
        }
        if(ok==0)
        {
            for(i=0;i<n;i++)
            {
                printf("%d",i);
                if(i==n-1) puts("");
                else printf(" ");
            }
            continue;
        }
        for(j=1;j<18;j++)
            for(i=1;i+(1<<j)-1<=n*2;i++)
                dp[i][j]=min(dp[i][j-1],dp[i+(1<<(j-1))][j-1]);
        int mx=0;
        for(i=1;i<=n;i++)
        {
            if(a[i]<=0) continue;
            int l=i,r=i+n,res,mid;
            while(l<=r)
            {
                mid=(l+r)>>1;
                if(calc(i,mid)<=dp[i-1][0]) r=mid-1;
                else res=mid,l=mid+1;
            }
            ans[i]=res-i+1;
        }
        for(i=1;i<=n;i++) mx=max(mx,ans[i]);
        for(i=1;i<=n;i++)
        {
            if(mx==ans[i]) vt[cnt++]=i-1;
        }
        for(i=0;i<cnt;i++)
        {
            printf("%d",vt[i]);
            if(i==cnt-1) puts("");
            else printf(" ");
        }
    }
}

K:KI的目标

题目:点击打开链接

题意:KI给自己制定了最近制定了一些学习目标,因为有些大目标的达到要先完成一些小目标,所以KI就下意识的把这些目标连成了一棵树,以1号目标为根。
   KI是个很谨慎的人,于是他请他的朋友们对这棵树上的每条边评估了一个努力值cost(i),并对每个目标评估了一个
价值val(i)。
   然后KI决定去掉树上的一些不可行的目标,他判断的依据是:
   假设目标v属于以u为根的子树,如果dis(u,v)<val(u)-val(v),那么以v为根的整棵子树都会被去掉。(dis(u,v)从节点u到节点v所有边的边权和)
   请帮KI计算一下最后他还剩下几个目标。

分析:

首先可以用链式前向星保存每条边的两节点编号和权值cost,因为输入时不知道是父节点还是子节点,所以要存两条边。

我们可以计算发现,如果存在一个u,满足dis(u,v)<val(u)-val(v),val(v)<val(u)-dis(u,v)就要要删掉v的子树,因此可以进行贪心,val[v]要满足大于等于val(u)-dis(u,v)的最大值。遍历每一个节点时保存最大的“val(u)”,这个val(u)并不是真正的节点的val值,而是从根节点开始遍历下来直到v的父节点时,路径上val的值减去cost的最大值,即val(u)-dis(u,v)的最大值。

因此建完树以后,从1开始深搜,dfs保存当前节点u,父节点fa和当前的“val(u)”记为sum。每次遍历到一个节点时,判断sum-cost和val[v]哪个大,每次都取大值为sum,就保证了sum是路径上val的值减去cost的最大值。如果sum<=val[u],那么这个点就是合法的,答案个数ans++,否则返回上一层。遍历完所有可能的点后,最后输出ans即可

//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include <stdio.h>
#include <cmath>
#include <cstring>
#include <algorithm>
#include <queue>
#include <set>
#include <vector>
#include <map>
#define PR pair<int,int>
#define MP make_pair
#define fi first
#define se second
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
#define sqr(x) ((x)*(x))
#define ll long long
const ll INF = 1e18;
const int inf=0x3f3f3f3f;
const int M=18;
const int N=101000;
const int MOD=1000000007;
const double eps=1e-9;
const double pi=acos(-1.0);
using namespace std;
struct Edge
{
    int v,nxt;
    ll cost;
}edge[N*2];
int head[N],cnt;
void addedge(int u,int v,ll w)
{
    edge[cnt].v=v,edge[cnt].cost=w,edge[cnt].nxt=head[u],head[u]=cnt++;
    edge[cnt].v=u,edge[cnt].cost=w,edge[cnt].nxt=head[v],head[v]=cnt++;
}
int n,u,v,ans;
ll w,val[N];
void dfs(int u,int fa,ll sum)
{
    if(sum<=val[u]) ans++;
    else return;
    for(int i=head[u];i!=-1;i=edge[i].nxt)
    {
        int v=edge[i].v;
        if(fa!=v)
        {
            ll tmp=sum-edge[i].cost;
            dfs(v,u,max(tmp,val[v]));
        }
    }
}
int main()
{
    int T,i;
    scanf("%d",&T);
    while(T--)
    {
        memset(head,-1,sizeof(head));
        cnt=0;
        scanf("%d",&n);
        for(i=1;i<n;i++)
        {
            scanf("%d%d%lld",&u,&v,&w);
            addedge(u,v,w);
        }
        for(i=1;i<=n;i++)
        {
            scanf("%lld",&val[i]);
        }
        ans=0;
        dfs(1,-1,val[1]);
        printf("%d\n",ans);
    }
}




评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值