倍增专题

(未完成)

其实就是在牛客上找了几道题刷一下。

A 紧急集合

题目
树上给出已知三点,找一点使得三点到这点距离最短,查询次数为5e5
思路就是找两两的LCA,选择其中不同的那个就是,距离可以直接用深度算出来。

#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+5;
int head[maxn],f[20][maxn],dep[maxn],lg[maxn],cnt,n,q;
struct node{
	int v;
	int nxt;
}e[maxn<<1];
void adde(int u,int v){
	e[++cnt].v=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;
	f[0][u]=fa;
	for(int i=1;i<=lg[dep[u]];i++){
		f[i][u]=f[i-1][f[i-1][u]];
	}
	for(int i=head[u];~i;i=e[i].nxt){
		int v=e[i].v;
		if(v!=fa)dfs(v,u);
	}
}
int  LCA(int a,int b){
	if(dep[a]<dep[b])swap(a,b);
	while(dep[a]>dep[b])a=f[lg[dep[a]-dep[b]]-1][a];
	if(a==b)return a;
	for(int i=lg[dep[a]];i>=0;i--){
		if(f[i][a]!=f[i][b]){
			a=f[i][a];
			b=f[i][b];
		}
	}
	return f[0][a];
}
void ini(){
	cnt=-1;
	for(int i=0;i<=19;i++)f[i][0]=0;
	dep[0]=0;
	lg[0]=0;
	memset(head,-1,sizeof(head));
	for(int i=1;i<=n;i++)
    lg[i]=lg[i-1]+(1<<lg[i-1]==i);//logi+1 
}
int main(){
	scanf("%d%d",&n,&q);
	ini();
	for(int i=1;i<n;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		adde(u,v);
		adde(v,u);
	}
	dfs(1,0);
	int ans;
	for(int i=1;i<=q;i++){
		int a,b,c;
		int t1,t2,t3;
		scanf("%d%d%d",&a,&b,&c);
		t1=LCA(a,b);
		t2=LCA(b,c);
		t3=LCA(a,c);
		if(t1==t2)ans=t3;
		else if(t2==t3)ans=t1;
		else ans=t2;
		long long d=dep[a]+dep[b]+dep[c]-dep[t1]-dep[t2]-dep[t3];
		printf("%d %lld\n",ans,d);
	}
	return 0;
}

B 疫情控制

题目
H 国有n 个城市,这 n 个城市用n-1 条双向道路相互连通构成一棵树,1 号城市是首都,也是树中的根节点。H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。现在,在H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时) 请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

思路是二分答案,对于一个答案,倍增军队尽可能向上移动,如果有根的子树不能封锁,则从其余的子节点中选点。将所有可以到根节点的降序排序,所有没有被覆盖的子节点也降序排序。
两种情况移动:1 本次移动不影响本子树的封锁并且可以覆盖到。
2 本次移动虽然会让本子树无法封锁但是却能够减少距离并且可以覆盖到。
实践证明这种思路是对的

#include<bits/stdc++.h>
using namespace std;
/*
思路是二分答案
先倍增预处理(nlogn)
对于每一个答案 尽可能上移军队(mlogn)
最后还需要检测一下是否可行(n)
时间复杂度为o(mlognlogn) 
*/
const int maxn=1e5+5;
int n,m;
int f[20][maxn];
long long dis[20][maxn];
int dep[maxn]={0},lg[maxn]={0};
int ind[maxn],cnt,head[maxn];
int vis[maxn];
bool son[maxn];
queue<int> q;
struct more{
	int index;
	long long time;
	long long base; 
	bool operator <(const more &x)const{
	return time<x.time;}
};
priority_queue<more> q1,q2;
struct node{
	int v;
	long long w;
	int nxt;
}e[maxn<<1];
void adde(int u,int v,long long w){
	e[++cnt].nxt=head[u];
	e[cnt].v=v;
	e[cnt].w=w;
	head[u]=cnt;
}
void bfs(){
	dep[1]=1;
	f[0][1]=0;
	dis[0][1]=0;
	q.push(1);
	while(q.size()){
		int u=q.front();
		q.pop();
		for(int i=head[u];~i;i=e[i].nxt){
			int v=e[i].v;
			if(dep[v])continue;
			dep[v]=dep[u]+1;
			f[0][v]=u;
			dis[0][v]=e[i].w;
			for(int j=1;j<=lg[dep[v]];j++){
				f[j][v]=f[j-1][f[j-1][v]];
				dis[j][v]=dis[j-1][v]+dis[j-1][f[j-1][v]];
			}
			q.push(v);
		}
	}
}
bool check(int u)
{
    bool pson=0;
    if(vis[u])    return 1;
    for(int i=head[u];~i;i=e[i].nxt)
    {
        int v=e[i].v;
        if(dep[v]<dep[u])    continue;
        pson=1;
        if(!check(v))    return 0;
    }
    if(!pson)    return 0;
    return 1;
}
bool judge(more a,more b){
	vis[a.index]--;
	if(check(a.index)){
	vis[a.index]++;
	return true;}
	if(a.base<b.base){//如果可以减少总路径需求 
		q2.push({a.index,a.base,a.base});
		vis[a.index]++;
		return true;
	}
	return false;
}
bool solve(){
	if(check(1))return true;
	for(int i=head[1];~i;i=e[i].nxt){
		int v=e[i].v;
		if(dep[v]!=dep[1]+1)continue;
		if(!check(v)){
			q2.push({v,dis[0][v],dis[0][v]});
		}
	}
	more n1,n2;
	while(q1.size()&&q2.size()){
		n1=q1.top();
		n2=q2.top();
		if(n1.time<n2.time){
			q1.pop();
		}
		else if(judge(n1,n2)){
			q2.pop();
			q1.pop();
			vis[n1.index]--;
			vis[n2.index]++;
		}
		else {
			q1.pop();
		}
	}
	if(q2.size())return false;
	else return true;
}
void ini(){
	cnt=-1;
	memset(son,0,sizeof(son));
	memset(head,-1,sizeof(head));
	for(int i=19;i>=0;i--){
		f[i][0]=dis[i][0]=0;
	}
	for(int i=1;i<=50000;i++){
		lg[i]=lg[i-1]+((1<<lg[i-1])==i);
	}
}
int main(){ 
	scanf("%d",&n);
	ini();
	for(int i=1;i<n;i++){
		int u,v;
		long long w;
		scanf("%d%d%lld",&u,&v,&w);
		adde(u,v,w);
		adde(v,u,w);
	} 
	bfs();
	scanf("%d",&m);
	int s=0;
	for(int i=head[1];~i;i=e[i].nxt){
		int v=e[i].v;
		if(dep[v]<dep[1])continue;
		s++;son[v]=1;
	}
	if(s>m){
		cout<<"-1"<<endl;
		return 0;
	}
	for(int i=1;i<=m;i++)
	scanf("%d",ind+i);
	long long l=0,r=500000000,ans;
	while(r>=l){
		memset(vis,0,sizeof(vis));
		while(q1.size())q1.pop();
		while(q2.size())q2.pop();
		long long mid=l+r>>1;
		for(int i=1;i<=m;i++){
			long long tmp=mid;
			int index=ind[i];
			for(int j=lg[dep[index]];j>=0;j--){
				if(tmp-dis[j][index]>=0&&f[j][index]>1){
					tmp-=dis[j][index];
					index=f[j][index];
				}
			}
			vis[index]++;
			if(son[index]&&tmp-dis[0][index]>0)//先判断是否为子节点 
					q1.push({index,tmp-dis[0][index],dis[0][index]});
		}
		if(solve()){
			ans=mid;
			r=mid-1;
		}
		else {
			l=mid+1;
		}
	}
	cout<<ans<<endl;
	return 0; 
}
/*
8
1 2 2 
1 3 1
1 4 2
1 5 2
1 6 5
1 7 1
1 8 1
7
1 2 3 4 5 6 7 8 
ans 0
8
1 2 1
1 3 1
1 4 1
1 5 1
3 6 1
4 7 1
5 8 1
3 
2 3 8
ans -1
10
2 1 3
2 3 4
1 4 7
5 1 9
6 1 2
4 7 9
7 8 8
9 8 8
1 10 2
5
2 8 5 4 2
ans 9
*/

C A and B and Lecture Rooms

题目
貌似是一道div2E题
但是难度只有2100分(甚至更低
给树上两个点问和这两个点距离相同的点有多少个
同样询问量很大
分情况讨论:
1)两个点同深度则找到LCA然后个数为n减去两个分支的大小
2)两个点不同深度则找到中点,用中点大小减去深度大的分支大小

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
struct node{
	int v;
	int nxt;
}e[maxn<<1];
int dep[maxn],siz[maxn],f[20][maxn],lg[maxn],head[maxn],n,cnt;
void adde(int u,int v){
	e[++cnt].v=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
void ini(){
	memset(head,-1,sizeof(head));
	cnt=-1;
	dep[0]=1;
	for(int i=1;i<=n;i++)
		lg[i]=lg[i-1]+((1<<lg[i-1])==i);
}
void dfs(int u,int fa){
	dep[u]=dep[fa]+1;
	siz[u]=1;
	f[0][u]=fa;
	for(int i=1;i<=lg[dep[u]];i++){
		f[i][u]=f[i-1][f[i-1][u]];
	}
	for(int i=head[u];~i;i=e[i].nxt){
		int v=e[i].v;
		if(v==fa)continue;
		dfs(v,u);
		siz[u]+=siz[v];
	}
}
int solve(int x,int y){
	if(dep[x]==dep[y]){
		if(x==y)return n;
		for(int i=lg[dep[x]];i>=0;i--)
			if(f[i][x]!=f[i][y])x=f[i][x],y=f[i][y];//这里是同一深度一起跳 
		return n-siz[x]-siz[y];//结果是减去这两个分支的所有点 
	}
	if(dep[x]<dep[y])swap(x,y);
	int xx=x;
	int high=dep[x]-dep[y];
	for(int i=lg[dep[x]];i>=0;i--){
		if(dep[f[i][x]]>=dep[y])x=f[i][x];//一点跳到另一点深度相同 
	}
	for(int i=lg[dep[x]];i>=0;i--)
		if(f[i][x]!=f[i][y])x=f[i][x],y=f[i][y],high+=(2<<i);//两点同时跳直到父为同一点 
	if(x!=y)high+=2;//可能出现深度相同点正好相同的情况,所以判断一下 
	if(high%2==1)return 0; 
	high>>=1;
	for(int i=lg[dep[xx]];i>=0;i--){//一直跳到相同点的前一点 
		if(high>(1<<i)){
			xx=f[i][xx];
			high-=(1<<i);
		}
	} 
	return siz[f[0][xx]]-siz[xx];//答案为相同点减去深度大的分支 
}
int main(){
	int m,a,b;
	cin>>n;
	ini();
	for(int i=1;i<n;i++){
		scanf("%d%d",&a,&b);
		adde(a,b);
		adde(b,a);
	}
	dfs(1,0);
	cin>>m;
	for(int i=1;i<=m;i++){
		scanf("%d%d",&a,&b);
		int ans=solve(a,b);
		printf("%d\n",ans);
	}
	return 0;
} 
/*
15
1 2
1 3
1 4
2 5
2 6
2 7
5 8
6 9
9 14
14 15
7 10
4 13
3 11
3 12
6
10 15
13 12
2 15
8 4
15 12
6 13
*/

D 国旗计划

题目
给定n个区间,问强制选取其中一个区间之后,还需要多少个区间能覆盖一条环上的所有线段,不存在完全包含的区间。
因为覆盖只要覆盖一次,则将环变成两倍长的线段将问题转化为覆盖线段的问题(保证问题求解不会超出两倍线段长度的范围),然后倍增求点数即可。需要注意倍增的数组需要开两倍大小。

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e5+5;
int n;
long long m;
struct edge{
	long long l;
	long long r;
	int ind;
}e[maxn];
bool cmp(edge a,edge b){
	return a.l<b.l;
}
int f[22][maxn];
int vis[maxn];
void ini()
{
    for(int i=1,cnt=1;i<=2*n;i++)
    {
        while(cnt<=2*n&&e[cnt].l<=e[i].r )cnt++;
        f[0][i]=cnt-1;
    }
    for(int i=1;i<=20;i++)
    for(int j=1;j<=2*n;j++)   
        f[i][j] = f[i-1][f[i-1][j]];
}
void solve(int k){
	int lim=e[k].l+m;
	int ans=0;
	for(int i=20;i>=0;i--){
		int ind=f[i][k];
		if(ind!=0&&e[ind].r<lim){
			ans+=(1<<i);
			k=ind;
		}
	}
	printf("%d ",ans+2);
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		scanf("%lld%lld",&e[i].l,&e[i].r);
		if(e[i].r<e[i].l)e[i].r+=m;
		e[i].ind=i;
	}
	sort(e+1,e+1+n,cmp);
	for(int i=1;i<=n;i++){
		vis[e[i].ind]=i;
	}
	for(int i=n+1;i<=2*n;i++){
		e[i].l=e[i-n].l+m;
		e[i].r=e[i-n].r+m;
		e[i].ind=e[i-n].ind;
	}
	ini();
	for(int i=1;i<=n;i++)solve(vis[i]);
	return 0;
}

E smile house

题目
题目大意是给定一个有向图,问最小正环是多少。
首先对任意正环的判断考虑采用floyd算法,其次要问最小正环,则需要用倍增。
设dp[s][i][j]表示走小于等于2^s步时的i到j的最大值,那么状态转移为
dp[s][i][j]=max(dp[s][i][j],dp[s-1][i][k]+dp[s-1][k][j])
用last[i][j]存储当前i到j的最大值,那么假如在当前状态下多走少于等于2^s步,状态可以这样更新
tmp[i][j]=max(tmp[i][j],last[i][k]+dp[s][k][j])
可能有人会问,假如走两步得出的最大值和走一步一样,那dp[1][i][j]不就相当于至多走一步的最大值,那不就错了。
但其实状态转移是一个floyd,跑一遍floyd最大值一点没变,那就说明无解了,不影响。
实际得出答案的时候用贪心,考虑两种情况
1.当前足够得到正环,不保存状态直接s/=2;
2.当前不够得到正环,保存状态并s/=2,同时增加总必须步数;

#include<bits/stdc++.h>
using namespace std;
const int maxn=305;
int n,m;
int dp[20][maxn][maxn];
int last[maxn][maxn];
int tmp[maxn][maxn];
void ini1(){
	memset(dp,0xcf,sizeof(dp));
	memset(last,0xcf,sizeof(last));
	for(int k=0;k<=19;k++)
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++){
		if(i==j)dp[k][i][j]=0;
	}
}
void ini2(){ 
	for(int s=1;s<=19;s++)
	for(int i=1;i<=n;i++)
	for(int j=1;j<=n;j++)
	for(int k=1;k<=n;k++)
		dp[s][i][j]=max(dp[s-1][i][k]+dp[s-1][k][j],dp[s][i][j]);
	for(int i=1;i<=n;i++)
	last[i][i]=0;
}
int main(){
	scanf("%d%d",&n,&m);
	int u,v,w1,w2;
	bool flag;
	ini1();
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&u,&v,&w1,&w2);
		dp[0][u][v]=w1;
		dp[0][v][u]=w2;
	}
	ini2();
	int ans=0;
	for(int s=19;s>=0;s--){
		flag=false;
		memset(tmp,0xcf,sizeof(tmp));
		for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		for(int k=1;k<=n;k++){
			tmp[i][j]=max(tmp[i][j],last[i][k]+dp[s][k][j]);
		}
		for(int i=1;i<=n;i++){
			if(tmp[i][i]>0)flag=true;
		}
		if(flag)
		continue;
		else {
			for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
			last[i][j]=tmp[i][j];
			ans+=(1<<s);
		}	
	}
	if(ans==1048575)cout<<"0"<<endl;
	else cout<<ans+1<<endl;
	return 0;
} 

F 萌萌哒(补)

题目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值