二进制优化问题

因为任意一个数,都可以用二进制来表示,也就是可以通过1,2,4,8…的序列相加得到,那也就是说为了得到1,2,3,4,…s这些数可以通过1+2+4+8…的序列拼凑而来,而不是用1+1+1+1的序列拼凑而来
比如7=1+1+1+1+1+1+1, 同时7=1+2+4 (0111)
13=1+1+1+1+1+1+1+1+1+1+1+1+1 ,同时13=8+4+1(1101)
就可以将O(n)的复杂度降为O(logn),用于很多问题的时间优化中

题目一:升级版多重背包问题

题目链接: https://www.acwing.com/activity/content/problem/content/1000/
分析:
简单错误思路:直接拉平、直接把每种物品最多可以选si个转化为有si个该物品,选的时候也就满足了最多有si个该物品可以选择了,但是这种方法对数组的大小要求很高,允许的内存分配大小内不够
正确思路:对于一种物品来说,最多可以选s个,也就是可以选1,2,3,4,5…s个,直接将物品数组拉平,相当于就是每次可以选1个,对每个该类物品判断选还是不选,但是选出来的该物品的个数肯定是1,2,3,4,5…s中的一种,但是一个一个地选会超时;
既然如此,就想着采用二进制的方式选,因为任意一个数,都可以用二进制来表示,也就是可以通过1,2,4,8…的序列相加得到,那也就是说选1,2,3,4,…s个可以通过1+2+4+8…的序列拼凑而来,而不是用1+1+1+1的序列拼凑而来,也就可以将最多选s个物品,分为log(s)组,对每一组进行判断选还是不选,就降低了复杂度到Nlog(s)V
AC代码:

#include<iostream>
using namespace std;
const int N=20000005;
int v[N];
int w[N];
int dp[N];
int n,V;
int vi,wi,si,t;
int main()
{
    t=0;
    cin>>n>>V;
    for(int i=0;i<n;i++)
    {
        cin>>vi>>wi>>si;
        for(int k=1;k<=si;k*=2)
        {
            t++;
            v[t]=vi*k;   //每一组物品里会放多个物品,分别放1,2,4,8....个数的物品,这些物品的个数能够凑齐总的s个,对这些物品进行选或者不选的抉择就行
            w[t]=wi*k;
            si-=k;    //每次减去1 2 4 8
        }
        if(si>0)   //这个地方注意,这个si出来的时候不一定是大于0的,因此t不能先赋值后更新,最后在这儿填充,如果这块儿si不是大于0的就没法填充最后的那个t
        {
            t++;
            v[t]=si*vi;
            w[t]=si*wi;
        }
    }

    for(int i=1;i<=t;i++)
    {
        for(int j=V;j>=v[i];j--)
        {
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[V];
    return 0;
}

题目二:倍增法求LCA

题目链接: https://www.acwing.com/problem/content/1174/
分析:
向上标记法之所以求LCA慢,是因为这dd一次只能爬一格,那么我们一次爬很多格不就OK了,二进制拆分可以组成任何想要的数,因此,用二进制来优化
更多见:https://blog.csdn.net/m0_58642116/article/details/128550161?spm=1001.2014.3001.5501
AC代码:

#include<iostream>
#include<vector>
#include<queue>
#include<string.h> 
using namespace std;
const int N=40005;
int n,m,root;
int fa[N][16];     //fa[i][j]表示结点i往上跳2^j步所到达的结点 
int depth[N];      //表示每个结点到根节点的深度 
vector<int>tr[N];   //邻接表 
queue<int>tmp;
const int INF=0x3f3f3f3f;
void bfs(int s)
{
	memset(depth,INF,sizeof(depth));
	tmp.push(s);
	depth[0]=0;depth[s]=1;
	while(!tmp.empty())
	{
		int u=tmp.front();
		tmp.pop();
		for(int i=0;i<tr[u].size();i++)
		{
			int j=tr[u][i];
			if(depth[j]==INF)  //还没访问过,避免一个结点有多个父节点的情况 
			{
				depth[j]=depth[u]+1;
				tmp.push(j); 
				fa[j][0]=u;   //注意这个不是移动0步,而是移动2^0=1步 
				for(int k=1;k<=15;k++)
				{
					fa[j][k]=fa[fa[j][k-1]][k-1];
				}
			} 
		}
	}
}
int lca(int x,int y)
{
	if(depth[x]<depth[y])  swap(x,y);  //保证x比y深
	//先将x移动到跟y一样深
	for(int k=15;k>=0;k--)
	{
		// 设置了哨兵depth[0] = 0: 如果从i开始跳2^j步会跳过根节点 
	    // fa[fa[j][k-1]][k-1] = 0
	    // 那么fa[i][j] = 0 depth[fa[i][j]] = depth[0] = 0,depth[y]>=1正是我们不进行处理的特殊情况 
		if(depth[fa[x][k]]>=depth[y])  //x移动2^k步后还是比y深,就继续往上移动 
		{
			x=fa[x][k];
		}
	}
	if(x==y) return x; 
	//将x和y一起往上移动到最近公共祖先处
	for(int k=15;k>=0;k--)
	{
		// 假如a,b都跳出根节点,fa[a][k]==fa[b][k]==0 不符合更新条件,即不跳过去的特殊情况 
		if(fa[x][k]!=fa[y][k])  //先判断再移动,如果跳过去后已经跳过了LCA了就不跳 
		{
			x=fa[x][k];
			y=fa[y][k];
		}
	}
	//最后出来的时候fa[x][0]或者fa[y][0]就为LCA 
	return fa[x][0];
}

int main()
{
	cin>>n;
	for(int i=0;i<n;i++)
	{
		int a,b;
		cin>>a>>b;
		tr[a].push_back(b);
		tr[b].push_back(a);
		if(b==-1) root=a;
	}
	bfs(root);              //预处理nlog(n) 
	cin>>m;
	for(int i=0;i<m;i++)    //多次询问,每次询问O(logn) 
	{
		int x,y;cin>>x>>y;
		int p=lca(x,y);
		if(p==x) cout<<1<<endl;
		else if(p==y) cout<<2<<endl;
		else cout<<0<<endl;
	}
	return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值