[ARC086]F - Shift and Decrement 位运算+数论+DP

30 篇文章 0 订阅
6 篇文章 0 订阅

题面
可以证明,在最后一次 ÷2 操作之前,不必要进行连续的两次 1 操作。因为连续的 1 操作可以通过先 ÷2 来减少操作次数。
我们定义 popcount(x) 来表示 x 二进制表示中1的个数,那么假设做了 k ÷2操作(可以发现 k 不会超过60),那么可以把所有操作等价为:
1. p (p<2k) ,耗费 popcount(p) 次操作。
2. ÷2k ,耗费 k 次操作。
3. d,耗费 d 次操作。
可以发现,经过前两次操作,一些数x变成了x÷2k,而另一些变成了 x÷2k1 。这取决于 xmod2k p 的大小关系。
设这些余数为Ci。那么对于每一个区间 (Ci1,Ci] ,p取这个区间的任意值得出的序列都是等价的。
那我们会得到 kn 个序列,每一个序列都有一个最少已经消耗的操作次数 k+minpopcount(Ci1,Ci] ,但是两个差分之后相同的序列是可以通过操作3相互转化的。
那么我们把差分序列相同的分成一类,那么一类中首项相同也就序列相同,每个序列的首项有一个取值范围 [a1K+k+minpopcount(Ci1,Ci]a1] ,那么就相当于给定若干区间求并集长度的问题了,每一类DP一下即可。
总复杂度 O(60n2log(n60))
代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int mod=1000000007;
int n,top,num;
ll K,a[210],c[210],d[210];
pair<ll,ll> qj[12010];
struct node
{
    ll t[210],a1;
    int k;
}s[12010];
bool operator <(const node &a,const node &b)
{
    for(int i=1;i<n;i++)
        if(a.t[i]<b.t[i]) return 1;
        else if(a.t[i]>b.t[i]) return 0;
    return 0; 
}
bool check(node &a,node &b)
{
    for(int i=1;i<n;i++)
        if(a.t[i]!=b.t[i]) return 0;
    return 1;   
}

int maxpop(ll x,ll y)
{
    if(x==-1) return 0;
    int re=0;
    for(int i=60;i>=0;i--)
        if(((x>>i)&1)==((y>>i)&1)) re+=((x>>i)&1);
        else {re++;break;}
    return re;  
}
ll dp()
{
    sort(qj+1,qj+num+1);
    ll re=0,l=0,r=-1;
    for(int i=1;i<=num;i++)
    {
        if(qj[i].first>r) {re+=(r-l+1);l=qj[i].first;}
        r=qj[i].second;
    }
    re+=(r-l+1);
    return re;  
}
int main()
{
    scanf("%d%lld",&n,&K);
    for(int i=1;i<=n;i++)
        scanf("%lld",&a[i]);
    sort(a+1,a+n+1);    
    for(int i=0;i<=60;i++)
    {
        for(int j=1;j<=n;j++)
            c[j]=(a[j]&((1ll<<i)-1));   
        sort(c+1,c+n+1);
        c[0]=-1;
        int sz=unique(c+1,c+n+1)-c-1;
        for(int j=1;j<=sz;j++)
            if(a[1]>=c[j])
            {
                int P=maxpop(c[j-1],c[j]);
                for(int l=1;l<=n;l++)
                    d[l]=((a[l]-c[j])>>i);  
                s[++top].a1=d[1];s[top].k=i+P;
                for(int l=1;l<n;l++)
                    s[top].t[l]=d[l+1]-d[l];        
            }
    }
    sort(s+1,s+top+1);
    num=0;
    ll ans=0;
    for(int i=1;i<=top;i++)
    {
        if(i!=1&&!check(s[i],s[i-1])) ans=(ans+dp())%mod,num=0; 
        if(s[i].k<=K)qj[++num]=make_pair(max(s[i].a1-K+s[i].k,0ll),s[i].a1);
    }
    ans=(ans+dp())%mod;
    printf("%lld",ans);
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值