二进制优化问题
因为任意一个数,都可以用二进制来表示,也就是可以通过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:///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;
}
因篇幅问题不能全部显示,请点此查看更多更全内容