牛客练习赛83

A-追求女神

签到题,直接模拟即可。
具体思路:
我们遍历每一个时间点,当前时间点与上一个时间点的差值就是从上一个点到当前点需要花费的时间。不难注意到,如果时间不够,那么一定不能准时到达;如果时间够了,也需要判断减去必要的步骤后的数是否为偶数(中间过程中的来回折返可以等价于在终点的折返),不是偶数也无法准时到达。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int T, n;
struct node{
    int t, x, y;
}a[100005];
void solve(){
    cin>>n;
    for(int i = 1; i <= n; ++i){
        int t,x,y;
        cin>>t>>x>>y;
        a[i] = {t,x,y};
    }
    int now = 0;
    a[0] = {0,0,0};
    bool f = 1;
    for(int i = 1; f && i <=n ;++i){
        int rest = a[i].t - a[i-1].t;
        int resl = abs(a[i].x - a[i-1].x) + abs(a[i].y - a[i-1].y);
        if(rest < resl || (rest - resl) % 2 != 0) f = 0;
    }
    if(f)cout<<"Yes"<<'\n';
    else cout<<"No"<<'\n';
}
int main(){
	ios::sync_with_stdio(0);//关闭同步流以加速
    cin>>T;
    while(T--){
        solve();
    }
}

B-计算几何

前缀和思想,二进制。
具体思路:
题目让我们求的是二进制中1的个数为奇数的数的个数,显然我们需要从二进制的角度来看这个问题 ,思考二进制位数,假设当前的位数是 k k k,我们要从 k k k位中取出奇数个位置来存放1,那么根据二项式定理,有情况 2 k − 1 2^{k-1} 2k1种,接着思考比一个数小的数中,满足二进制中1的个数为奇数个的数的个数,假设这个数是 x x x,对于当前数的二进制中,我们从高位向低位遍历,如果当前数是1,那么就会有当前位数-1的2次幂的贡献,即比这个数小的数的情况(即我们把一个数拆成二进制形式去寻找比这个数小的数),需要额外注意的是,如果是最后一位为1,需要特判,因为比一小的数只有0,而且,没有后续位可以替补1的个数,即如果这个数包含了偶数个1,那么最后一位变成0后也是满足条件的数。
(说的有点抽象,建议手动模拟)(绝不是懒得写数学公式 tao)

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int T;
ll l, r;
ll f[70];
inline ll lowit(ll x){//经典取1操作,不了解的萌新需要查一下
    return x & -x;
}
void init(){//预处理2^n-1
    f[0] = 0;
    f[1] = 1;
    ll x = 2;
    for(int i = 2; i <= 64; ++i){
        f[i] = x;
        x <<= 1;
    }
}
ll get_num(ll x){//获取二进制中1的个数
    ll i = 0;
    for(i = 0; x ; ++i){
        x -= lowit(x);
    }
    return i;
}
ll get(ll x){//获取小于x的数中,二进制中1的个数为奇数的数的个数。
    ll sum = 0;
    ll num = get_num(x);
    for(int i = 1; x ; ++i){
        if(x & 1){
            if(i == 1 && num % 2 == 0)++sum;
            sum += f[i-1];
        }
        x >>= 1;
    }
    return sum;
}
void solve(){
    cin>>l>>r;
    ll suml = get(l), sumr = get(r);
    ll ans = sumr - suml;
    if(get_num(r) & 1)++ans;//判断r本身
    cout<<ans<<'\n';
}
int main(){
    ios::sync_with_stdio(0);
    init();
    cin>>T;
    while(T--){
        solve();
    }
}

C-集合操作

堆,思维。
具体思路:
首先我们需要注意一下我们的每步操作是减去一个 p p p,而当我们要去减这个序列中某一个值时意味着比它大的所有数都已经小于了当前数,即当前数成为最大值。但是光知道这些,我们还是难以下手,我们还是很难简单的处理 k k k步操作。我们不妨思考一下,既然我们每次减去的值都是固定的,从数的角度来看,每个数也都可以写成 k ∗ p + b ( − 1 < b < p ) k*p+b(-1< b < p) kp+b(1<b<p)的形式,我们思考一下,如果当前数的 k = a k=a k=a,那么当我们要对当前数执行操作时,所有满足 k > a k > a k>a的数一定会减至 a / ( a − 1 ) ∗ p + b a/(a-1)*p+b a/(a1)p+b的形式,也就是说,在这个数之前比它大的数一定会先减至与当前数一样的 k k k,而如果这些数都包含在区间长度为 p p p的一个区间内,那么接下来的操作会沿着数的有序排列循环下去,即从 s 到 t s到t st,然后继续循环, s s s为序列最大值编号, t t t为最小值编号 。
证明如下:
设当前序列为{ k 1 ∗ p + b 1 , k 2 + b 2 , . . . , k n + b n k_1*p+b_1,k_2+b_2,...,k_n+b_n k1p+b1,k2+b2,...,kn+bn},序列单调递减,我们现在对第一个值进行操作,显然第一个值将会排到最后一个,然后对第二个值操作,第二值会排到倒数第二个,这个也显然(几个数的相对大小不变,即差值恒定),直到一轮减完,又回到最初的情况.
既然这样,我们只需要去遍历每一个数的 k k k,计算到达当前 k k k所需要的操作次数(即维护一个序列到一个长度为 p p p的区间中),直到我们在往一个 k k k转变时,操作次数超限,就拿出来单独模拟,对维护的序列减去最大完整的循环节,然后对剩余的操作用堆来模拟即可。复杂度 o ( n l o g ( n ) ) o(nlog(n)) o(nlog(n))

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n, k, p;
ll a[1000005];
ll ans[1000005];
ll get(ll x){
   return x/p; 
}
void solve(){
    cin>>n>>k>>p;
    for(int i = 1; i <= n; ++i)cin>>a[i];
    sort(a+1,a+1+n);
    if(!p){
        for(int i = 1; i <= n; ++i){
            if(i != n)cout<<a[i]<<" ";
            else cout<<a[i]<<'\n';
        }
        return;
    }
    int now = 1;
    ll sum = 0;
    for(int i = n; i >= 2; --i){
        if(get(a[i]) == get(a[i-1]))continue;
        ll d = get(a[i]) - get(a[i-1]);
        sum = d * (n - i + 1);
        if(k >= sum){
            k -= sum;
            continue;
        }
        now = i;break;
    }
    priority_queue<ll, vector<ll>, less<ll>>q;
    ll t = get(a[now]);
    for(int i = n; i >= now; --i){
        ll u = get(a[i]);
        a[i] -= (u-t) * p;
    }
    ll res = n - now + 1;
    ll v = k / res; k %= res;
    for(int i = 1; i <= n ;++i){
        if(get(a[i]) == t)a[i] -= v * p;
        q.push(a[i]);
    }
    while(k --){
        ll tmp = q.top();
        q.pop();
        q.push(tmp-p);
    }
    int cnt = 0;
    while(q.size()){
        ans[++cnt] = q.top();
        q.pop();
    }
    for(int i = n; i >= 1; --i)cout<<ans[i]<<" ";
    cout<<'\n';
}
int main(){
    ios::sync_with_stdio(0);
    solve();
}

D-数列递推

数论分块,前缀和
具体思路:
首先我们肯定需要考虑对 i i i m o d mod mod j j j进行转变,来降低我们的复杂度。考虑到 m o d mod mod的性质,我们将其转化为基本四则运算的形式。
即:
f i = ∑ j = 1 i f ( i   m o d   j ) = ∑ j = 1 i f ( i − ⌊ i j ⌋ ⋅ j ) \begin{aligned} f_i&=\sum_{j=1}^i f_{(i\bmod j)}\\&=\sum_{j=1}^i f_{(i-\lfloor\frac{i}{j}\rfloor\cdot j)} \end{aligned} fi=j=1if(imodj)=j=1if(ijij)
不难注意到这里存在着一个数论分块, ⌊ i j ⌋ \lfloor\frac{i}{j}\rfloor ji将在 j j j的一个区间内数值相同,记该区间为 ( l , r ) (l,r) (l,r),那么在该区间内,下标将呈一个等差数列的形式,等差为 ⌊ i j ⌋ \lfloor\frac{i}{j}\rfloor ji,注意到数论分块的复杂度是 o ( n n ) o(n\sqrt{n} ) o(nn ),我们考虑利用分块的性质对原递推式进行优化加速。这就意味着我们需要预处理出以某个值为末端点,等差为 ⌊ i j ⌋ \lfloor\frac{i}{j}\rfloor ji的区间和,我们可以在处理 f i f_i fi的同时去处理这样的前缀和,不难想到前缀和的递推式。但是还要注意的是我们的前缀和数组是二维的,为了防止爆内存,我们可以自己设定一个等差上限,超出部分直接暴力,不难证明,等差上限取值合理时(这里可取150左右),相对较快,复杂度依旧稳定在 o ( n n ) o(n\sqrt{n} ) o(nn )

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
const int maxn = 150;
ll f[100005], sum[maxn+1][100005];
ll n;
void solve(){
    cin>>n;
    cin>>f[0];
    for(int i  = 1; i <= maxn; ++i)sum[i][0] = f[0];
    for(int i = 1; i <= n; ++i){
        for(int l = 1, r = 1; l <= i; l = r + 1){
            ll k = i / l; r = i / k;
            if(k < maxn){//等差上限的判定
                ll L = i - k * r, R = i - k * l;
                f[i] = (f[i] + sum[k][R]) % mod;//加上前缀和
                if(L > k)f[i] = (f[i] - sum[k][L-k]) % mod;//特判L是否大于了一个分块
                continue;
            }
            for(int j = l; j <= r; ++j)f[i] = (f[i] + f[i - k*j]) % mod;//暴力
        }
        for(int j = 1; j < maxn; ++j){
            if(i >= j)sum[j][i] = (f[i] + sum[j][i-j]) % mod;//预处理前缀和
            else sum[j][i] = f[i];
        }
    }
    for(int i = 1; i <=n ; ++i){
        if(i != n)cout<<f[i]<<" ";
        else cout<<f[i]<<'\n';
    }
}
int main(){
    ios::sync_with_stdio(0);
    solve();
}

E-小L的疑惑

数学,思维,结论,双指针
具体思路:
两个结论
1.对于不定方程 a x + b y = k ( g c d ( a , b ) = 1 ) ax + by = k(gcd(a,b) = 1) ax+by=k(gcd(a,b)=1),对于任意的 x > = 0 , y > = 0 x>=0,y>=0 x>=0,y>=0,最大的不能被表示的数为 a ∗ b − a − b a*b-a-b abab
2.上述方程中任意不能被表示的数一定能表示成 a ∗ b − a x − b y a*b-ax-by abaxby的形式。
结论证明放在最后。
先来思考一下在知道这两个结论后该怎么做,首先这题的数据范围是 k < 1 e 7 k<1e7 k<1e7,如果我们用优先队列维护,显然,铁T,思考单调性来优化,我们从最大值开始,每次去得到下一个第一个比它小的且无法被表示的数,那么它一定是比它大的数减去 a / b a/b a/b来的,并且只会减去一次,如果减去多次,那么中间减去的值会是下一个较大值,矛盾。那么我只需要维护两个指针,去指向第一个减去 a / b a/b a/b后能够成为较大值的两个已经获得的数即可。
复杂度 o ( n ) o(n) o(n)
结论1的证明:
思考最大的不能被表示的数,假设最大的不能被表示的数是 k − 1 k-1 k1,那么 k k k以后的值都可以被表示,即我们要寻找最后一组相邻的数使得较小数不能被表示,而较大数可以被表示。
建立不定方程:
a x + b y = k ( 1 ) ax+by=k(1) ax+by=k(1)
a x 1 + b y 1 = 1 ( x 1 是 最 小 正 整 数 解 ) ( 2 ) ax_1+by_1=1(x_1是最小正整数解)(2) ax1+by1=1(x1)(2)
a x 2 + b y 2 = 1 ( y 2 是 最 小 正 整 数 解 ) ( 3 ) ax_2+by_2=1(y_2是最小正整数解)(3) ax2+by2=1(y2)(3)
则有
a ( x − x i ) + b ( x − y i ) = k − 1 a(x-x_i)+b(x-y_i) = k-1 a(xxi)+b(xyi)=k1
不存在一组解使得该组解均为正整数。
那么我们就有 x − x 1 < 0 x-x_1 < 0 xx1<0 y − y 2 < 0 y-y_2<0 yy2<0
那么我们就能得到最大的不能被表示的数为 a ( x 1 − 1 ) + b ( y 2 − 1 ) − 1 ( 4 ) a(x_1-1)+b(y_2-1)-1(4) a(x11)+b(y21)1(4)
思考 ( 2 ) ( 3 ) (2)(3) (2)(3)两组解的关系,不难得到 x 1 = x 2 + b , y 1 = y 2 − a x_1 = x_2 + b,y_1 = y_2 - a x1=x2+by1=y2a y 2 = y 1 + a , x 2 = x 1 − b y_2 = y_1 + a,x_2 = x_1 - b y2=y1+a,x2=x1b。带入 ( 4 ) (4) (4)得到 a ∗ b − a − b a*b-a-b abab
结论1证毕。
结论2证明:
先证充分性,已知, a ∗ b − a − b a*b-a-b abab为不能被表示的最大数,那么这个数不论减去多少 a / b a/b a/b后都不能被表示,若能表示,同样能通过加 a / b a/b a/b来得到这个值,矛盾。故 a ∗ b − a x − b y a*b-ax-by abaxby一定不能被表示。
再证必要性:
如果一个不能被表示的数能写成 a ∗ b − a x − b y + t a*b-ax-by+t abaxby+t的形式,那么对这个数进行加 a / b a/b a/b操作,一定能加到一个数比 a ∗ b − a − b a*b-a-b abab大,矛盾。
结论2证毕。

AC代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7+5;
ll res[maxn];
void solve(){
    ll a, b, k;
    cin>>a>>b>>k;
    res[1] = a * b - a - b;
    int cnta = 1, cntb = 1;
    for(int i = 2; i <= k; ++i){
        if(res[cnta] - a > res[cntb] - b){
            res[i] = res[cnta++] - a;
        } else if(res[cnta] - a < res[cntb] - b) {
            res[i] = res[cntb++] - b;
        } else {
            res[i] = res[cnta] - a;
            ++cnta, ++cntb;
        }
    }
    cout<<res[k]<<'\n';
}
int main(){
    ios::sync_with_stdio(0);
    solve();
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值