Codeforce Round #457 & Round #458

Round #457 B. Jamie and Binary Sequence
题意:给定n和k,要求将n表示为k个2的幂之和,输出最高幂最小且字典序最大的结果。

题解:首先将n一位一位地拿出来,如果位数大于k肯定是无法合并了,无解。如果位数小于k就需要根据2^m=2^(m-1)+2^(m-1)将其中的某些项分解。每次将最大的幂分解成两倍数目的较小的幂,这样保证了最大的幂最小。如果此时的数目超过了k,就取消这位的分解,改为从最低位开始一个一个地向下分解,这样保证了字典序最大。

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<map>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxn 200050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;

ll n,k;
int a[maxn],*cnt=a+100000;

int main()
{
    cin>>n>>k;
    for(int i=0;i<60;i++)
    {
        cnt[i]+=n>>i&1;
        k-=cnt[i];
    }
    if(k<0)
    {
        printf("No\n");
        return 0;
    }
    for(int i=59;i>=-60;i--)
    {
        if(k>=cnt[i])
        {
            k-=cnt[i];
            cnt[i-1]+=(2*cnt[i]);
            cnt[i]=0;
        }
        else
        {
            int minn=-60;
            while(!cnt[minn])
                minn++;
            while(k--)
            {
                cnt[minn]--;
                cnt[--minn]+=2;
            }
            break;
        }
    }
    printf("Yes\n");
    for(int i=59;i>=-100000;i--)
    {
        while(cnt[i]--)
            printf("%d ",i);
    }
    printf("\n");
    return 0;
}

Round #457 D. Jamie and To-do List
题意:有若干个事件,每一个事件都有一个优先级,要求维护一个用于记录事件的清单,支持以下操作:
a.将优先级为x的事件t放入清单中;
b.将事件t移出清单;
c.查询清单中优先级高于事件t的事件数目;
d.撤销之前的k个操作(不含此操作)。

题解:支持撤销操作需要使用可持久化线段树。建立一棵树保存权值的数量,另一棵树保存事件的信息。实际上这两棵树可以合并起来,只需要保存两个root即可。

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<string>
#include<queue>
#include<map>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxn 100050
#define maxm 10000050
#define INF 1000000000
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod=1000000007;

char op[10];
string s;
map<string,int>m;
int n,cnt,tmp,tot,x,no;
int rot[maxn],newrt[maxn];
struct node
{
    int ls,rs;
    int sum;
    int rt;
}tree[maxm];

int getid(string s)
{
    if(m.count(s))return m[s];
    m[s]=++cnt;
    return cnt;
}

void update(int root,int &newrt,int l,int r,int aim,int v)
{
    newrt=++tot;
    tree[newrt].sum=tree[root].sum+v;
    tree[newrt].ls=tree[root].ls,tree[newrt].rs=tree[root].rs;
    if(l==r)return;
    int mid=(l+r)>>1;
    if(aim<=mid)update(tree[root].ls,tree[newrt].ls,l,mid,aim,v);
    else update(tree[root].rs,tree[newrt].rs,mid+1,r,aim,v);
}

int query(int root,int l,int r,int aiml,int aimr)
{
    if(!root)return 0;
    if(l>=aiml&&r<=aimr)return tree[root].sum;
    int mid=(l+r)>>1,ans=0;
    if(aiml<=mid)ans+=query(tree[root].ls,l,mid,aiml,aimr);
    if(aimr>mid)ans+=query(tree[root].rs,mid+1,r,aiml,aimr);
    return ans;
}

int main()
{
    scanf("%d",&n);
    cnt=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%s",op);
        rot[i]=rot[i-1];
        newrt[i]=newrt[i-1];
        if(op[0]=='s')
        {
            cin>>s;
            scanf("%d",&x);
            no=getid(s);
            int tmp=query(newrt[i],1,n,no,no);
            if(tmp)update(rot[i],rot[i],1,INF,tmp,-1);
            update(newrt[i],newrt[i],1,n,no,x-tmp);
            update(rot[i],rot[i],1,INF,x,1);
        }
        else if(op[0]=='r')
        {
            cin>>s;
            no=getid(s);
            int tmp=query(newrt[i],1,n,no,no);
            if(tmp)update(rot[i],rot[i],1,INF,tmp,-1);
            update(newrt[i],newrt[i],1,n,no,-tmp);
        }
        else if(op[0]=='q')
        {
            cin>>s;
            no=getid(s);
            int tmp=query(newrt[i],1,n,no,no);
            if(tmp==0)printf("-1\n");
            else if(tmp==0)printf("0\n");
            else printf("%d\n",query(rot[i],1,INF,1,tmp-1));
            fflush(stdout);
        }
        else if(op[0]=='u')
        {
            scanf("%d",&x);
            rot[i]=rot[i-x-1];
            newrt[i]=newrt[i-x-1];
        }
    }
    return 0;
}

Round #458 C. Travelling Salesman and Special Numbers
题意:定义一种操作的返回结果为原数字二进制中1的数目。现给定k和二进制数字n,问所有不大于n的正整数中恰好经过k次操作得到1的数字的数目。

题解:给定的n不超过2^1000,也就是说经过一次操作以后的结果必然在1000之内。处理给定范围所有的数字显然办不到,但处理1000以内的结果还是可以的。设dp[i][j]为s的长度为i的子串中,含有j个1的子串个数。经过推导可以得到:
这里写图片描述
而对于所要求的问题,设tmp为数字i中1的数目,cnt[i]为数字i变换到1所需的操作数,显然有cnt[i]=cnt[tmp]+1。先处理1-1000的cnt值,那么1-n中cnt值为k的数字数目即为cnt值为k-1的数目之和,即:
这里写图片描述

#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<string>
#include<queue>
#include<map>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxn 1050
#define INF 0x3f3f3f3f
#define eps 1e-8
using namespace std;
typedef long long ll;
const ll mod=1000000007;

int len,a[maxn];
ll k,cnt[maxn];
ll dp[maxn][maxn],com[maxn][maxn];
char s[maxn];
ll c(int a,int b){return a<b?0:com[a][b];}

void init()
{
    com[1][0]=com[1][1]=1;
    for(int i=2;i<=1000;i++)
    {
        com[i][0]=1;
        for(int j=1;j<=1000;j++)
            com[i][j]=(com[i-1][j]+com[i-1][j-1])%mod;
    }
}

int getpre(ll x)
{
    int sum=0;
    while(x)
    {
        if(x%2==1)sum++;
        x/=2;
    }
    return sum;
}

int main()
{
    scanf("%s",s);
    len=strlen(s);
    scanf("%d",&k);
    init();
    for(int i=len-1;i>=0;i--)
        a[len-i]=s[i]-'0';
    if(a[1])dp[1][1]=dp[1][0]=1;
    else dp[1][1]=0,dp[1][0]=1;
    for(int i=2;i<=len;i++)
    {
        dp[i][0]=1;
        for(int j=1;j<=len;j++)
        {
            if(a[i])dp[i][j]=(dp[i-1][j-1]+c(i-1,j))%mod;
            else dp[i][j]=dp[i-1][j];
        }
    }
    ll ans=0;
    for(int i=1;i<=1000;i++)
    {
        int tmp=getpre(i);
        if(i==1)cnt[i]=0;
        else cnt[i]=cnt[tmp]+1;
        if(cnt[i]==k-1)
            ans=(ans+dp[len][i])%mod;
    }
    if(k==0)ans=1;
    else if(k==1)ans=dp[len][1]-1;
    printf("%I64d\n",ans);
    return 0;
}

Round #458 D. Bash and a Tough Math Pazzle
题意:给定一个数列,要求维护这个数列支持两种操作:
a.将数列的第x项改为y;
b.判断对于数列的第l项至第r项,是否可以通过改变其中的不超过1个数字使得这段子序列的最大公约数为x。

题解:线段树单点更新可以很容易地实现更改操作。而对于查询判断操作,可得到引理:更改1个以内数字使得子序列最大公约数为x的充要条件是这个子序列中至多有1个元素不能被x整除。线段树维护区间的GCD,每次查询时返回子序列中不能被x整除的元素数目即可。
引理的证明:若数列的GCD为x,那么数列中每个元素必定能被x整除。
i).数列中有1个以上的元素不能被x整除,更改1个以内任何数字都使得更改后的数列至少有1个元素不被x整除,数列GCD必不为x。
ii).数列中有1个元素不被x整除,将此数更改为x即可使得数列GCD为x。
iii).数列所有元素均能被x整除,数列GCD必为x或x的倍数。GCD为x时无需更改,GCD为x的倍数时将其中任意一元素改为x即可保证数列GCD为x。

#include<cstdio>
#include<cstring>
#include<string>
#include<cmath>
#include<cstdlib>
#include<queue>
#include<map>
#include<vector>
#include<iostream>
#include<algorithm>
#define maxn 500050
#define INF 1000000000
#define eps 1e-8
using namespace std;
typedef long long ll;

int n,m,l,r,x,flag;
int tree[maxn*4],a[maxn];

int gcd(int a,int b)
{
    return b?gcd(b,a%b):a;
}

void build(int root,int l,int r)
{
    if(l==r)
    {
        tree[root]=a[l];
        return;
    }
    int mid=(l+r)>>1;
    build(root*2,l,mid);
    build(root*2+1,mid+1,r);
    tree[root]=gcd(tree[root*2],tree[root*2+1]);
}

void update(int root,int l,int r,int aim,int x)
{
    if(l==r&&l==aim)
    {
        tree[root]=x;
        return;
    }
    if(l>aim||r<aim)
        return;
    int mid=(l+r)>>1;
    update(root*2,l,mid,aim,x);
    update(root*2+1,mid+1,r,aim,x);
    tree[root]=gcd(tree[root*2],tree[root*2+1]);
}

int query(int root,int l,int r,int aiml,int aimr)
{
    if(l>=aiml&&r<=aimr)
    {
        if(tree[root]%x==0)
            return 0;
        if(l==r)
        {
            if(tree[root]%x==0)
                return 0;
            else return 1;
        }
    }
    if(l>aimr||r<aiml)
        return 0;
    int mid=(l+r)>>1;
    int ansx=query(root*2,l,mid,aiml,aimr);
    if(ansx>1)return 2;
    return ansx+query(root*2+1,mid+1,r,aiml,aimr);
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    build(1,1,n);
    scanf("%d",&m);
    for(int i=0;i<m;i++)
    {
        scanf("%d",&flag);
        if(flag==1)
        {
            scanf("%d%d%d",&l,&r,&x);
            if(query(1,1,n,l,r)<=1)
                printf("YES\n");
            else
                printf("NO\n");
        }
        else if(flag==2)
        {
            scanf("%d%d",&l,&x);
            update(1,1,n,l,x);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值