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;
}