【过题记录】 7.27

比特跳跃


不难发现,比特跳跃最多跳跃一次
所以1号点到每个点的最短路径上肯定最多只有一个比特跳跃的边
对于一个点,如果他想要从某个点比特跳跃跳到当前点,一定是从他的子集跳过来
不然答案肯定比从1号点跳过来更劣

由于刚开始的图可能不一定是连通图
所以我们要先让1号点连接其他点将图变成连通图。
然后跑一遍最短路

这个时候,已经初步得到了1号点跟其他点的最短路径
但是不一定是最优的
我们还需要将当前点的最短路跟从他子集中的最短路比特跳跃过来的值进行比较。

在上述基础上再跑一次最短路就是最优答案

同时这里还有一个枚举子集的技巧:

	for (int i = 2; i <= n; i++){
	    for (int j = 0; (1<<j) <= n; j++) if ((i>>j)&1){
	        mind[i] = min(mind[i],mind[i^(1<<j)]);
	    }

由于子集是满足前缀性质的,所以当你枚举目前大子集的时候是包含小子集的答案的(类似于树状数组的思想)


Code

#include<bits/stdc++.h>
#include<iostream>
#include<queue>
using namespace std;

#define int long long

const int N = 5e5+100,inf = 1e18+10;
int n,m,k;
struct Node{
	int y,Next,v;
}e[2*N];
int len,Linkk[N];

void Insert(int x,int y,int z){
	e[++len] = (Node){y,Linkk[x],z};
	Linkk[x] = len;
}

int Calc(int x,int y){
	return k*(x|y);
}

typedef pair < int , int > pii;
#define mp make_pair
#define fi first
#define se second 

priority_queue < pii , vector < pii> , greater < pii > >q;
int dis[N],mind[N];
bool vi[N];

void Work(){
	cin>>n>>m>>k;
	for (int i = 1,x,y,z; i <= m; i++)
	  scanf("%lld %lld %lld",&x,&y,&z),Insert(x,y,z),Insert(y,x,z);
	for (int i = 2; i <= n; i++) Insert(1,i,Calc(1,i));
	for (int i = 0; i <= n; i++) dis[i] = inf;
	q.push(mp(0,1));
	dis[1] = 0;
	while (q.size()){
		pii P = q.top(); q.pop();
		int x = P.se , v = P.fi;
		vi[x] = 1;
		for (int i = Linkk[x]; i; i = e[i].Next){
			int y = e[i].y,vv = e[i].v;
			if (v + vv >= dis[y]) continue;
			dis[y] = v+vv; q.push(mp(dis[y],y));
		}
	}
	for (int i = 0; i <= n; i++) mind[i] = dis[i] , vi[i] = 0;
	for (int i = 2; i <= n; i++){
	    for (int j = 0; (1<<j) <= n; j++) if ((i>>j)&1){
	        mind[i] = min(mind[i],mind[i^(1<<j)]);
	    }
	    dis[i] = min(dis[i],mind[i]+k*1ll*i);
	}
	q.push(mp(0,1));
	while (q.size()){
		pii P = q.top(); q.pop();
		int x = P.se , v = P.fi;
		for (int i = Linkk[x]; i; i = e[i].Next){
			int y = e[i].y,vv = e[i].v;
			if (v+vv >= dis[y]) continue;
			dis[y] = v+vv;
			q.push(mp(dis[y],y));
		}
	}
	for (int i = 2; i <= n; i++) cout<<dis[i]<<' '; cout<<endl;
	len = 0;
	for (int i = 1; i <= n; i++) Linkk[i] = 0;
}

signed main(){
	int t; cin>>t; while (t--) Work();
	return 0;
}
/*
3
6 4 3
1 3 2
1 5 20
2 4 1
4 6 10
6 4 3
1 3 2
1 5 20
2 4 1
4 6 10
6 4 3
1 3 2
1 5 20
2 4 1
4 6 10
*/

抓拍

对于这个题,我们只需要关注四个方向的max值和min值
答案就是 2 ∗ ( m a x x − m i n x + m a x y − m i n y ) 2*(maxx-minx+maxy-miny) 2(maxxminx+maxyminy)
我们可以画出(maxx-minx)关于时间t的函数图像
稍微手摸一下可以发现
maxx-minx是按照-2,-1,0,1,2的斜率顺序变化的
y方向的同理
所以这是一个单峰函数
用三分求极值即可


#include<map>
#include<iostream>
using namespace std;

#define int long long

const int N = 2e5+100;

int delx[] = {0,0,-1,1};
int dely[] = {1,-1,0,0};
map < char , int > dic;

int n;
struct Node{
	int x,y,di;
}a[N];

int Check(int x){
	int maxx = -1e9-10 , maxy = -1e9-10 , minx = 1e9+10 , miny = 1e9+10;
	for (int i = 1; i <= n; i++){
		maxx = max(maxx,a[i].x+x*delx[a[i].di]);
		maxy = max(maxy,a[i].y+x*dely[a[i].di]);
		minx = min(minx,a[i].x+x*delx[a[i].di]);
		miny = min(miny,a[i].y+x*dely[a[i].di]);
	}
//	cout<<maxx<<' '<<minx<<' '<<maxy<<' '<<miny<<endl; 
	return 2*(maxx-minx+maxy-miny);
}

signed main(){
	scanf("%d",&n);
	dic['N'] = 0; dic['S'] = 1; dic['W'] = 2; dic['E'] = 3;
	for (int i = 1; i <= n; i++){
		char ch;
		cin>>a[i].x>>a[i].y>>ch;
		while (ch != 'E' && ch!='S' && ch!='N' && ch!='W') ch =  getchar();
		a[i].di = dic[ch];
	}
	int l = 0 , r = 1e9;
	while (l <= r){
		int Mid1 = l+(r-l)/3 , Mid2 = r-(r-l)/3;
		if (Check(Mid1) > Check(Mid2)) l = Mid1+1;
		else r = Mid2-1;
//		cout<<"l = "<<l<<' '<<r<<endl; 
	}
	cout<<min(Check(l),Check(r));
	return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值