比特跳跃
不难发现,比特跳跃最多跳跃一次
所以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∗(maxx−minx+maxy−miny)
我们可以画出(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;
}