[20180815]校内模拟赛

T1 游戏(game)


问题描述

Alice准备和Bob玩一个游戏,他们先拿出若干堆石子,每一堆里面都有一定数量的石子。

Alice和Bob轮流操作,Alice先手,每次操作需要选择一堆石子数量大等于2的石子,把这堆石子分成两堆。

假设这堆石子中有x个石子,那么可以分成一堆y(1≤y<x)个石子和一堆x-y个石子。

如果轮到一个人操作时没有可选的石子堆,这个人就输了。

Alice有n堆石子,其中第i堆有\(a_i\)个石子,他打算选出其中连续一段石子跟Bob玩。

你需要回答m次询问,每次查询取出第l堆到第r堆石子进行游戏,双方都选择最优策略时谁会获胜。


输入格式

第一行两个正整数n,m。

第二行n个正整数,表示\(a_i\)

接下来m行,每行两个正整数l,r,表示一个询问。


输出格式

对于每个询问输出一行“Alice”或“Bob” ,表示答案。


样例

样例输入


2 3
1 2
1 1
2 2
1 2

样例输出


Bob
Alice
Alice

数据范围

对于 100%的数据,n,m,\(a_i\) ≤ 10^5 ,l ≤ r。


Solution

一个大小为n的石子堆可以被分(n-1)次。

对于一段石子,如果能分k次:

  1. k是奇数,Alice胜
  2. k是偶数,Bob胜

前缀和处理。


#include<iostream>
#include<cstdio>
inline long long read(){
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')ch=getchar();ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
#define MN 100005
long long n,m,a[MN];
int main(){
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    n=read(),m=read();
    register int i,x,y;
    for(i=1;i<=n;i++) a[i]=read()-1+a[i-1];
    while(m--){
        x=read();y=read();
        if((a[y]-a[x-1])&1) puts("Alice");
        else puts("Bob");
    }
    return 0;
}




T2 数字(number)


问题描述

小 D 最近在研究 A+B 问题,可是这个问题对他来说太棘手了,因为他连读入都不会。

小 D 开了两个变量a和b,并且把它们的初值设为1.

接下来他可以添加若干行代码, 每一行可以是\(a = a + b\)\(b = a + b\)

已知A+B 问题的样例输出是n,小D 想知道自己至少需要添加多少行代码才能让a和b中至少有一个等于n以通过样例呢?


输入格式

一行一个正整数n。


输出格式

输出一个整数,表示答案。


样例


样例输入


5


样例输出


3


数据范围

对于 100%的数据,n≤ 10^6 。


Solution

枚举最后一步是由那两个数相加得到的,设为A和B。

可以发现倒推回去的过程类似求gcd的过程。

显然A和B必须要互质。

在倒推的过程中计算步数,最后取个min。


#include<iostream>
#include<cstdio>
inline long long read(){
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')ch=getchar();ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
#define MN 1000005
int n,m;
int ans=MN,N;
inline int gcd1(int x,int y){
    if(y==0) return 0;
    return gcd1(y,x%y)+(x/y);
}
inline int gcd2(int x,int y){
    if(y==0) return x;
    return gcd2(y,x%y);
}
int main(){
    freopen("number.in","r",stdin);
    freopen("number.out","w",stdout);
    n=read();register int i;
    for(i=1;i<=n-i;i++){
        m=gcd2(n-i,i);if(m!=1) continue;
        ans=std::min(ans,gcd1(n-i,i));
    }
    printf("%d\n",ans);
    return 0;
}




T3 旅行(travel)


问题描述

旅行家小C今天在一条数轴上旅游,一开始他位于x。

数轴王国接下来会依次举行n次活动,每次在区间\([l_i,r_i]\)内举行。

在每个活动开始前,小C可以移动任意的距离,从a移动到b会让他积攒|a-b|的疲劳值。

如果一个活动开始时,小C不在活动范围内,他就会不开心,并且如果离活动范围越远他就越不开心

具体地说,如果小C当前位置到活动范围的最短距离为k,小C就会积攒k的疲劳值。

请你求出所有活动结束后小C最小的疲劳值之和。


输入格式

第一行两个正整数n,x。

接下来n行,每行两个正整数\(l_i\) ,\(r_i\)


输出格式

输出一个整数,表示答案。


样例

样例输入


5 4
2 7
9 16
8 10
9 17
1 6


样例输出


8


数据范围

对于 100%的数据,n ≤ 5* 10^5 ,x,\(l_i\) ,\(r_i\) ≤ 10^9 。


Solution

维护使答案最优的区间[L,R],初始L=R=x。

设d(X,i)为X位置到活动[li,ri]的最小距离。

假设当前的位置为pos,对于一个活动[li,ri]

(1) pos不在活动区间内

只要不往远离这个活动区间的方向走,或者的走到区间里面去,疲劳值总是一定的,就是d(pos,i)。

  1. 如果往远离这个活动 区间的方向走,会使疲劳值d(pos,i)的基础上在增加走的距离,可以把它看作先原地不动,活动i结束后再走相应的距离,疲劳值不变。
  2. 如果走到区间里面去,同样也可以先走到活动的边界,剩下的距离留到活动结束后再走。

所以使答案最优的区间:[pos,li][ri,pos]

同样的,如果当前pos的区间为[L,R],且[L,R][li,ri]无交,[L,R]应更新为[min(R,ri),max(L,li)]

(2)pos在活动区间内

原地不动会是最优的,这应该比较显然。

同样的,如果当前pos的区间为[L,R],且[L,R][li,ri]相交,[L,R]应更新为原先两个区间的交集,即

[max(L,li),min(R,ri)].

以下是学长的题解:

f[i][j]表示第i个活动后在j的最小疲劳值,对于每个i,先从i-1复制DP值,接下来有两部分计算,第一部分算活动的疲劳值,j<li的加上li-j,j>ri的加上j-ri。第二部分移动,用f[i][j]+1更新f[i][j-1]f[i][j+1]

事实上,对于每个i,把f[i][j]看成关于j的函数,这个函数会由最多三部分组成,第一部分形如y=-x+a,第二部分y=b,第三部分y=x+c,也就是差分恰好形成-1,0,1三段.

考虑用归纳法证明:i=0 f[i][j]=|j-x|,显然满足。i增大时,第一部分计算会让函数再加上一个差分为-1,0,1的函数,差分会变成-2,-1,0,1,2。第二部分计算会让函数的差分绝对值不超过1,差分又会变成-1,0,1。差分为0的那一段就是最小的答案,维护这一段的位置并顺便计算答案即可。

时间复杂度O(n)


#include<iostream>
#include<cstdio>
inline long long read(){
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')ch=getchar();ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return x*f;
}
#define MN 500005
int n,pos,l,r,L,R;
long long ans;
bool cross(int x,int y,int a,int b){
    long long len=(y-x)+(b-a);
    x=std::min(x,a);
    y=std::max(y,b);
    return len>(y-x);
}
int main(){
    freopen("travel.in","r",stdin);
    freopen("travel.out","w",stdout);
    int n=read();
    pos=L=R=read();ans=0;
    for(int i=1;i<=n;i++){
        l=read(),r=read();
        if(cross(l,r,L,R)){
            L=std::max(l,L);
            R=std::min(r,R);
        }
        else{
            R=std::min(R,r);
            L=std::max(L,l);
            std::swap(L,R);
            ans+=R-L;
        }
    }
    printf("%lld\n",ans);
    return 0;
}





Blog来自PaperCloud,未经允许,请勿转载,TKS!

转载于:https://www.cnblogs.com/PaperCloud/p/9481119.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值