2023杭电多校3-1012「辗转相减编码排序二分前缀串」

Noblesse Code - HDU 7311 - Virtual Judge (vjudge.net)

题意:

给定n个数对(a,b),有q个询问(A,B),每次可以对(A,B)操作为(A+B,B)或者(A,A+B),对于每个询问,输出当前的(A,B)能够变换到多少个给定的(a,b)。

思路:

可以发现操作过程是辗转相减的逆过程,我们将(a+b,b)记录为左儿子(L),(a,a+b)记录为右儿子(R),(a,b)是父节点,一直往上找最终找到的根节点一定是gcd(a,b),此时每个点可以得到一个字符串作为从根节点到达该点的路径,于是问题转变为有多少个给定的点满足他的根节点为(gcd(a,b),gcd(a,b))且他的路径以(A,B)的路径字符串为前缀。
重点在于排序字符串,优先以字符串字典序排,然后对于每一个相同的字符查询在当前位置减了几次,即在树的哪一段位置开辟了新节点。
当前询问的字符串为t,二分出最大的小于t的位置为pre,然后给t在末尾加一个大于LR的字符,二分最大的小于新t的位置为l,l-pre就是以t为前缀的字符串个数。

AC代码:
#include <bits/stdc++.h>
using namespace std;
#define io ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
typedef long long ll;
#define int ll
#define pb push_back
#define eb emplace_back
#define m_p make_pair
const int mod = 998244353;
#define mem(a,b) memset(a,b,sizeof a)
#define pii pair<int,int>
#define fi first
#define se second
const int inf = 0x3f3f3f3f;
const int N = 3e5 + 50;
//__builtin_ctzll(x);后导0的个数
//__builtin_popcount计算二进制中1的个数
#define SVL pair<string,vector<ll>>
vector<SVL> s[N];//存树,s[i]表示gcd(a,b)为i的树,fi是字符串,se是在fi的方向上走了多少步
map<ll,int>mp;//相当于链式前向星的head
int tot=0;

void get(ll a,ll b,SVL &s){
    if(a==b)return;
    else if(a>b){
        ll k=(a-b-1)/b+1;//防止a=k*b一剪梅
        s.fi+='L';s.se.pb(k);
        get(a-k*b,b,s);
    }
    else{
        ll k=(b-a-1)/a+1;
        s.fi+='R';s.se.pb(k);
        get(a,b-a*k,s);
    }
}

bool cmp(SVL &a,SVL &b){//按树排序
    int i=0,j=0;
    ll li=0,lj=0;
    int n=a.fi.size(),m=b.fi.size();
    string &s=a.fi,&t=b.fi;
    vector<ll>&nums=a.se,&numt=b.se;
    if(i<n&&j<m){
        li=nums[i];lj=numt[j];
    }
    while(i<n&&j<m){
        if(s[i]<t[j])return 1;
        else if(s[i]>t[j])return 0;
        if(li==lj){
            i++;j++;
            if(i<n&&j<m){
                li=nums[i];lj=numt[j];
            }
        }else if(li<lj){
            lj-=li;i++;
            if(i<n)li=nums[i];
        }else{
            li-=lj;j++;
            if(j<m)lj=numt[j];
        }
    }
    if(j==m)return 0;
    else return 1;
}

void work() {
    int n,q;cin>>n>>q;
    mp.clear();
    tot=0;
    for(int i=1;i<=n;++i){
        int a,b;cin>>a>>b;
        SVL t;
        get(a,b,t);
        reverse(t.fi.begin(),t.fi.end());
        reverse(t.se.begin(),t.se.end());
        ll d = __gcd(a, b);
        if (!mp.count(d)) {
            mp[d] = ++tot;
            s[tot].clear();
        }
        s[mp[d]].pb(t);
    }
    for(int i=1;i<=tot;++i){
        sort(s[i].begin(),s[i].end(),cmp);
    }
    while(q--){
        int a,b;cin>>a>>b;
        SVL t;
        get(a,b,t);
        reverse(t.fi.begin(),t.fi.end());
        reverse(t.se.begin(),t.se.end());
        ll d = __gcd(a, b);
        int id=mp[d];

        int l=0,r=s[id].size();
        while(l<r){
            int mid=(l+r+1)>>1;
            if(cmp(s[id][mid-1],t))l=mid;
            else r=mid-1;
        }
        int pre=l;

        l=0,r=s[id].size();t.fi+='Z';t.se.pb(0);
        while(l<r){
            int mid=(l+r+1)>>1;
            if(cmp(s[id][mid-1],t))l=mid;
            else r=mid-1;
        }
        cout<<l-pre<<'\n';
    }
}

signed main() {
    io;
    int t;
    cin >> t;
    while (t--) {
        work();
    }
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值