Luogu5629 【AFOI-19】区间与除法

这篇博客讨论了一道编程竞赛题目,涉及到区间和数的除法操作。题目要求确定最少需要多少个特定的“原数”来消灭一个数列中的数,通过预处理和状态压缩,结合ST表解决区间查询问题。代码实现中出现了long long类型导致的溢出问题。

原题链接:https://www.luogu.com.cn/problem/P5629

区间与除法

题目背景

SY 好不容易才解出QM给她的数学题,在恰午饭的时候,QM 向她的脑洞里塞了个幻想的泡泡……SY 戳开一看,又是长长的一串数字!

SY 实在是不想思考了,她决定用小学的除法消灭她脑洞里的数字.

题目描述

定义 o p op op 操作意义为将当前数除以 d d d 并向下取整.

SY 现在有 m m m 个“原数”,若一个数经过若干次 o p op op 操作(包括 0 0 0 次)后能变为这个“原数”,那么这个数是可以被这个“原数”所消灭的。注意,“原数”是不会被消耗的.

现在 SY 想问你,对于一个区间 [ l , r ] [l,r] [l,r],在消灭最多个数的前提下最少需要多少个“原数”?

输入格式

第一行 4 4 4 个数,分别是 n , m , d , q n,m,d,q n,m,d,q,分别表示数列 { a } \{a\} {a} 元素个数,SY 拥有的 “原数” 个数, o p op op 操作参数,询问个数。

第二行为 { a } \{a\} {a} 数列,即需要被消灭的数列。

第三行为 m m m 个“原数”。

接下来 q q q 行,每行两个数 l l l r r r,表示询问区间为 [ l , r ] [l,r] [l,r]

输出格式

按照询问顺序,每一行输出一个整数表示答案.

输入输出样例

输入 #1
2 3 3 3
0 20
6 6 6
1 1
2 2
1 2
输出 #1
0
1
1
输入 #2
6 3 3 3
6 5 10 15 19 7
2 5 10
1 6
1 4
4 6
输出 #2
3
3
2

说明/提示

样例解释:

#样例1 20 20 20 经过一次 o p op op 操作(除以 3 3 3 向下取整)可以变成 6 6 6,而 0 0 0 不能经过若干次 o p op op 操作变成 6 6 6

所以区间 [ 1 , 1 ] [1,1] [1,1] 最多消灭 0 0 0 个数,消灭最多数前提下最少需要 0 0 0 个 “原数”,区间 [ 1 , 2 ] , [ 2 , 2 ] [1,2],[2,2] [1,2],[2,2] 最多消灭 1 1 1 个数,消灭最多数前提下最少需要 1 1 1 个 “原数” 。

#样例2 2 2 2 能消灭 { 6 , 19 , 7 } \{6,19,7\} {6,19,7} 5 5 5 能消灭 { 5 , 15 } \{5,15\} {5,15} 10 10 10 能消灭 { 10 } \{10\} {10} , 所以区间 [ 1 , 6 ] , [ 1 , 4 ] [1,6],[1,4] [1,6],[1,4] 最少能用所有 “原数” 全部消灭,区间 [ 4 , 6 ] [4,6] [4,6] 能用 2 , 5 2,5 2,5 全部消灭。

数据范围:

对于 30 % 30\% 30% 的数据: n ≤ 100 , m ≤ 10 , d = 2 , q ≤ 10 n\le100,m\leq10, d=2, q\le 10 n100,m10,d=2,q10

对于 100 % 100\% 100% 的数据: n ≤ 5 × 1 0 5 , m ≤ 60 , 2 ≤ d ≤ 10 , q ≤ 1 0 6 , 0 ≤ a i , b i ≤ 2 63 n\le5\times 10^{5},m\leq60,2\leq d\leq10,q\le10^{6},0\le a_i,b_i\le 2^{63} n5×105,m60,2d10,q106,0ai,bi263

在这里插入图片描述
特殊性质:数据经过构造。

题解

比较好想,因为 d d d是固定的,所以一个数能不能被原数消灭是可以预处理出来的。同时原数也有可能被另一个原数消灭,所以我们需要先“化简”一下原数集合,把能被其他原数消灭的原数删去,再将原数排好序,方便后续的查找。

因为一个原数可以消灭多个数,需要合并区间的信息,看到 m ≤ 60 m\le 60 m60,直接明示状态压缩,用一个long long 63 63 63个二进制位就可以完美表示出整个区间需要的原数状态,并且能做到 O ( 1 ) O(1) O(1)合并。再看到询问 1 0 6 10^6 106,明示卡 O ( l o g 2 n ) O(log_2n) O(log2n)算法,就自然想到 S T \mathcal{ST} ST表,于是这题就做完了。

代码

思路简单,但是为什么没有一次过呢,又双叒叕是因为™的long long,👴吐了。

#include<bits/stdc++.h>
#define MAX LONG_LONG_MAX
using namespace std;
const int M=5e5+5;
int n,m,d,q,lg2[M];
long long st[M][20],que[M],ori[65],pow2[65];
void deal(int v)
{
    long long x=que[v];
    for(int p;x;x/=d)
    {
        p=lower_bound(ori+1,ori+1+m,x)-ori;
        if(ori[p]==x){st[v][0]=pow2[p-1];break;}
    }
}
int div(long long x){int r=0;for(;x;x>>=1)if(x&1)++r;return r;}
int ask(int le,int ri)
{
    int lg=lg2[ri-le+1];
    return div(st[le][lg]|st[ri-pow2[lg]+1][lg]);
}
void in()
{
    scanf("%d%d%d%d",&n,&m,&d,&q);
    for(int i=1;i<=n;++i)scanf("%lld",&que[i]);
    for(int i=1;i<=m;++i)scanf("%lld",&ori[i]);
}
void ac()
{
    sort(ori+1,ori+1+m);
    m=unique(ori+1,ori+1+m)-1-ori;
    int tmp=0;
    for(int i=m;i>1;--i)
    for(long long j=ori[i];j;j/=d)if(binary_search(ori+1,ori+i,j)){ori[i]=MAX;++tmp;break;}
    sort(ori+1,ori+1+m);
    m-=tmp;
    lg2[1]=0;for(int i=2;i<=n;++i)lg2[i]=lg2[i>>1]+1;
    pow2[0]=1;for(int i=1;i<=63;++i)pow2[i]=pow2[i-1]<<1;
    for(int i=1;i<=n;++i)deal(i);
    for(int i=1;i<=lg2[n];++i)for(int j=n-pow2[i]+1;j;--j)st[j][i]=st[j][i-1]|st[j+pow2[i-1]][i-1];
    for(int i=1,a,b;i<=q;++i)scanf("%d%d",&a,&b),printf("%d\n",ask(a,b));
}
int main()
{
    in(),ac();
    //system("pause");
}
# P5629AFOI-19区间除法 ## 题目背景 SY 好不容易解出QM给她的数学题,在午饭的时候,QM 向她的里塞了个幻想……SY 戳开一看,又是长长的一串数字! SY 实在是不想思考了,她决定用小学的除法消灭她里的数字. ## 题目描述 定义 $op$ 操作意义为将当前数除以 $d$ 并向下取整. SY 现在有 $m$ 个“原数”,若一个数经过若干次 $op$ 操作(包括 $0$ 次)后能变为这个“原数”,那么这个数是可以被这个“原数”所消灭的。注意,“原数”是不会被消耗的. 现在 SY 想问你,对于一个区间 $[l,r]$,在消灭最多个数的前提下最少需要多少个“原数”? ## 输入格式 第一行 $4$ 个数,分别是 $n,m,d,q$,分别表示数列 $\{a\}$ 元素个数,SY 拥有的 “原数” 个数,$op$ 操作参数,询问个数。 第二行为 $\{a\}$ 数列,即需要被消灭的数列。 第三行为 $m$ 个“原数”。 接下来 $q$ 行,每行两个数 $l$ 和 $r$,表示询问区间为 $[l,r]$。 ## 输出格式 按照询问顺序,每一行输出一个整数表示答案. ## 输入输出样例 #1 ### 输入 #1 ``` 2 3 3 3 0 20 6 6 6 1 1 2 2 1 2 ``` ### 输出 #1 ``` 0 1 1 ``` ## 输入输出样例 #2 ### 输入 #2 ``` 6 3 3 3 6 5 10 15 19 7 2 5 10 1 6 1 4 4 6 ``` ### 输出 #2 ``` 3 3 2 ``` ## 说明/提示 #### 样例解释: **#样例1** : $20$ 经过一次 $op$ 操作(除以 $3$ 向下取整)可以变成 $6$,而 $0$ 不能经过若干次 $op$ 操作变成 $6$ 。 所以区间 $[1,1]$ 最多消灭 $0$ 个数,消灭最多数前提下最少需要 $0$ 个 "原数",区间 $[1,2],[2,2]$ 最多消灭 $1$ 个数,消灭最多数前提下最少需要 $1$ 个 "原数" 。 **#样例2** : $2$ 能消灭 $\{6,19,7\}$ , $5$ 能消灭 $\{5,15\}$ , $10$ 能消灭 $\{10\}$ , 所以区间 $[1,6],[1,4]$ 最少能用所有 "原数" 全部消灭,区间 $[4,6]$ 能用 $2,5$ 全部消灭。 #### 数据范围: 对于 $30\%$ 的数据:$n\le100,m\leq10, d=2, q\le 10$ 对于 $100\%$ 的数据:$n\le5\times 10^{5},m\leq60,2\leq d\leq10,q\le10^{6},0\le a_i,b_i\le 2^{63}$ ![](https://cdn.luogu.com.cn/upload/image_hosting/t7pn0p1n.png) 特殊性质:数据经过构造。 #include <bits/stdc++.h> #define ll long long using namespace std; const ll N=1e6; struct node{ ll l,r,sum; }tr[4*N+5]; struct xd{ ll l,r,f; }b[N+5]; ll n,m,d,q; ll a[N+5],ans[N+5]; ll pre[N+5],lst[N+5]; ll ys[N+5],dy[N+5]; map<ll,bool> mp; void updata(ll p){ tr[p].l=tr[2*p].l; tr[p].r=tr[2*p+1].r; tr[p].sum=tr[2*p].sum+tr[2*p+1].sum; return; } void build(ll p,ll l,ll r){ if(l==r){ tr[p].l=l; tr[p].r=r; return; } ll mid=(l+r)>>1; build(2*p,l,mid); build(2*p+1,mid+1,r); updata(p); return; } void change(ll p,ll x,ll y){ if(tr[p].l==x && tr[p].r==x){ tr[p].sum+=y; return; } if(x<=tr[2*p].r) change(2*p,x,y); else change(2*p+1,x,y); updata(p); return; } ll searchh(ll p,ll l,ll r){ if(r<l) return 0; ll s=0; if(tr[p].l==l && tr[p].r==r) return tr[p].sum; if(tr[2*p].r>=r) s+=searchh(2*p,l,r); else if(tr[2*p+1].l<=l) s+=searchh(2*p+1,l,r); else{ s+=searchh(2*p,l,tr[2*p].r); s+=searchh(2*p+1,tr[2*p+1].l,r); } return s; } bool cmp(xd l1,xd l2){ return l1.r<l2.r; } int main(){ memset(dy,-1,sizeof(dy)); scanf("%lld%lld%lld%lld",&n,&m,&d,&q); for(ll i=1;i<=n;i++) scanf("%lld",&a[i]); for(ll i=1;i<=m;i++) scanf("%lld",&ys[i]),mp[ys[i]]=1; for(ll i=1;i<=n;i++){ ll num=a[i],w=-1; if(mp[num]) w=num; while(num){ num/=d; if(mp[num]) w=num; } dy[i]=w; } // for(ll i=1;i<=n;i++) cout<<dy[i]<<" "; // return 0; for(ll i=1;i<=q;i++){ scanf("%lld%lld",&b[i].l,&b[i].r); b[i].f=i; // cout<<i<<endl; } for(ll i=1;i<=n;i++){ if(dy[i]==-1) continue; pre[i]=lst[dy[i]]; lst[dy[i]]=i; } build(1,1,n); sort(b+1,b+1+q,cmp); ll nxt=1; for(ll i=1;i<=q;i++){ while(nxt<=b[i].r){ if(dy[nxt]==-1){ nxt++; continue; } if(pre[nxt]) change(1,pre[nxt],-1); change(1,nxt,1); nxt++; } ans[b[i].f]=searchh(1,1,b[i].r)-searchh(1,1,b[i].l-1); } for(ll i=1;i<=q;i++) printf("%lld\n",ans[i]); return 0; } 时间限制3s,评测结果RE+TLE,找错误,并在不改变代码原本写法的情况下修正
最新发布
08-01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值