zoukankan      html  css  js  c++  java
  • 【洛谷P6747/贪心】Teleport 转自自己的洛谷博客

    洛谷P6747 Teleport

    这道题有一些思维,同时又不是那么繁琐,还比较好想

    (毕竟我这种蒟蒻都想出来了)

    记得我想这道题的时候是两个星期前了差不多,当时一会儿想出来了一个假的做法 到今天尝试实现的时候,发现除了预处理的部分都错了。 所以先介绍预处理

    思考算法的时候我比较习惯先看数据范围,看到n为1e5,q为1e5,显然不能用q * n的做法,也就是说不能够每次都扫描一遍序列,所以直接就没想扫描序列怎么做。 显然要对序列a先做些什么

    考虑异或的性质

    当a的j位异或1的时候,必然改变原来的数字 原来是1就变成0,原来是0就变为1

    当a的j位异或0的时候,什么影响都没有。

    再考虑a的和,可以变化为所有a在二进制下每一位的和,也就是第j位的和,是可以线性处理出来的,所有j位的和的和,就是原序列的和(虽然我们未必用到原序列的和)。

    举个例子

    3+5用刚刚的方法来想 就是 (11)2+(101)2 

    两个数第零位的和sum[0]是1+1=2

    两个数第1位的和sum[1]是1*21+0*21=2+0=2

    两个数第三位的和就是   022+122=4

    所有的sum的和依然是8,但是我们相当于直接把最后的和给二进制拆分了并存进了sum里,方便我们考虑这一位的k如果是1是个什么情况。

    结合异或的性质 如果k的第j位是1,显然sum[j]会进行一定的改变,比如如果序列是3+5的话,即二进制下的11 和 101,


    3的原二进制表示0113的每一位都异或1100
    5的原二进制表示 1 0 1 5的每一位都异或1 0 1 0
    每一位和的十进制表示 4 2 2 如上得到的十进制和 4 2 0

    我们发现,每一位的二进制异或1之后,序列的0和1是翻过来的,如果把原序列的j位之和记做sum[j],异或1后记做sum1[j],再记j位上全是1的和为sum2[j] (即2^j n),存在sum2[j]=sum[j]+sum1[j],知2求1,sum2和sum都可以在输入a序列的时候直接处理出来,之后再多一个for,求出所有sum1,预处理结束*


    n=qr();
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=qr();
    		for(int j=0;j<=50;j++)
    		{
    		sum[j]+=(a[i]&((ll)1<<j)); //累加所有a的j位。k的j位为0时的结果。
    		sum2[j]+=((ll)1<<j);//累加每一位都为1的结果。 
    		}
    	}
    	for(int j=0;j<=50;j++)
    	{
    		sum1[j]=sum2[j]-sum[j];//k的j位为1时a的j位的累加和。 
    	}
    

    然后考虑如何找到最大的k,首先把k拆分成二进制的话,一定那么k中的1所在位越高越好,我原先想的是每次二分查找找到第一个比m小的sum1,找不到再找sum之类的,然后发现找到sum1是有后效性的,也就是说可能找到了一个比较大的sum1,然而后面的数不管怎么凑都不能让其余位上sum或sum1的和小于m,结果就找不到了。

    接着想到了类似估价函数的方法,即设计一个minj,表示j位及以下的a[j]最小总和是多少。这个预处理只需要在原来的预处理加一点(最后两行)


    	for(int i=1;i<=n;i++)
    	{
    		a[i]=qr();
    		for(int j=0;j<=50;j++)
    		{
    		sum[j]+=(a[i]&((ll)1<<j)); //累加所有a的j位。k的j位为0时的结果。
    		sum2[j]+=((ll)1<<j);//累加每一位都为1的结果。 
    		}
    	}
    	for(int j=0;j<=50;j++)
    	{
    		sum1[j]=sum2[j]-sum[j];//k的j位为1时a的j位的累加和。 
    		if(!j) minj[j]=min(sum1[j],sum[j]); 
    		else minj[j]=minj[j-1]+min(sum1[j],sum[j]);//选择二者更小的那个 
    	}
    

    这样我们就可以在每次查询中对所有的j位进行处理。

    **如果处理到某一步m-sum1[j]-minj[j-1]>=0, 就代表如果k的第j位选择了1之后,下面不管取了多少个1,总能有合法的结果(即保证a序列的处理后的和小于等于m),这个时候k+=2^j,m-=sum[j],并且直接continue,因为我们已经达到了这一步的最优结果,不需要继续考虑了。 **

    **当然可能sum1[j]比较大,一减下去就小于0了,那再去看看m-sum[j]-minj[j-1]>=0与否,也就是不异或1,如果不异或1才能合法,那么只好m-=sum[j],k+0,也就是不操作。当然如果两步都不满足,标记一个flag=1,也就是无论怎么操作,a序列的和总是要大于m,让飞船爆炸的。 ** 完整代码


    #include<bits/stdc++.h>
    using namespace std;
    typedef __int128 ll;
    inline ll qr()
    {
    	ll x=0,f=0;char ch=0;
    	while(!isdigit(ch)){f|=ch=='-';ch=getchar();}
    	while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    	return f?-x:x;
    }
    inline void print(ll x){
    	if(x<0){putchar('-');x=-x;}
        if(x>9) print(x/10);
        putchar(x%10+'0');
    }
    const int maxn=1e5+10;
    int n,q;
    ll m;
    ll k;
    ll a[maxn];
    ll sum1[51],sum[51],sum2[51];//序列a第j位异或1的和,原序列a第j位之和,第j位全为1的和。 
    ll minj[51];//j位及以下的最小总和是多少。 
    int flag;
    int main()
    {
    	n=qr();
    	for(int i=1;i<=n;i++)
    	{
    		a[i]=qr();
    		for(int j=0;j<=50;j++)
    		{
    		sum[j]+=(a[i]&((ll)1<<j)); //累加所有a的j位。k的j位为0时的结果。
    		sum2[j]+=((ll)1<<j);//累加每一位都为1的结果。 
    		}
    	}
    	for(int j=0;j<=50;j++)
    	{
    		sum1[j]=sum2[j]-sum[j];//k的j位为1时a的j位的累加和。 
    		if(!j) minj[j]=min(sum1[j],sum[j]); 
    		else minj[j]=minj[j-1]+min(sum1[j],sum[j]);//选择二者更小的那个 
    	}
    	q=qr();
    	for(int i=1;i<=q;i++)
    	{
    		m=qr();k=0;flag=0;
    		for(int j=50;j>=0;j--)
    		{
    			if(j==0){//到达边界了,前面的判断都给过去了,这里只用处理sum就好了,一定至少有一个能让它合法。 
    				if(m-sum1[j]>=0)
    				{
    					m-=sum1[j],k+=1;//每次m减去这一位的和
    					continue;	
    				}
    				if(m-sum[j]>=0)
    				{
    					m-=sum[j];
    					continue;
    				}
    			}
    			if(m-minj[j-1]-sum1[j]>=0)//选择这一位的k能够为1的同时保证下面能够凑出来保证合法的数,这大概算是个估价,第一步就可以判断出是否合法 
    			{
    				m-=sum1[j],k+=((ll)1<<j);//这一位可以为1。k累加上 
    				continue;
    			}
    			if(m-minj[j-1]-sum[j]>=0)//这一位k为1是没戏了,只能为0,加上原aj位的和 
    			{
    				m-=sum[j]; 
    				continue;
    			}
    			flag=1;	break;//两个都不能用,肯定是坏了,直接break。 
    		}
    		if(flag==1) //(要用flag判断,而不是k==0,因为k可以等于0)
    		{
    		printf("%d
    ",-1);
    		}
    		else print(k),printf("
    ");
    	}
    	return 0;
    } 
    

    哦对了,虽然我这整个操作中似乎不会爆long long 但是不用int128还就是会爆炸,40pts。 如果有大佬能看出来哪里暴了欢迎指正。

    (当然如果有人能看到这篇博文的话)

  • 相关阅读:
    从零开始整SpringBoot-工具与插件
    算法与数据结构学习笔记(目录)
    牛客小白月赛30(个人题解)
    Manjaro 上手使用简明教程
    C++函数:std::tie 详解
    Educational Codeforces Round 99 (Rated for Div. 2) (A ~ F)个人题解
    VS Code C++ 项目快速配置模板
    【字符串算法】字典树详解
    关于算法竞赛某些常见基础错误
    Teacher Ma专场
  • 原文地址:https://www.cnblogs.com/mikuo/p/13769182.html
Copyright © 2011-2022 走看看