倍增的两道应用题(题解报告)
作者:互联网
文章目录
洛谷 P1967 [NOIP2013 提高组] 货车运输
题目链接
题意:题意不难看懂,就是要求两点间道路中最大权值的最小值
思路:这道题有很多种解法,这里讲利用LCA的解法
首先我们知道,解肯定是建立在最大生成树上的
先用kruscal建最大生成树
然后再在LCA中维护一个d(u,v)表示从节点u走到节点v经过的道路的最小权重
然后就可以上代码了
AC代码:
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e5 + 10,M = 5e5 + 10,INF = 0x3f3f3f3f;
int head[N],tot;
int n,m,q;
int Fa[N];
int fa[N][20],d[N][20],dep[N];
int vis[N];
struct Edge{
int v,w,next;
}e[M << 1];
struct node{
int u,v,w;
bool operator < (const node& b)const{
return w > b.w;
}
}a[N];
void add(int u,int v,int w){
e[tot].v = v;
e[tot].next = head[u];
e[tot].w = w;
head[u] = tot ++;
}
int getf(int x){return x == Fa[x] ? x : Fa[x] = getf(Fa[x]);}
void kruscal(){
for(int i = 1;i <= n;i ++) Fa[i] = i;
sort(a,a + m);
// for(int i = 0;i < m;i ++) cout << a[i].w << " ";
// cout << endl;
for(int i = 0;i < m;i ++){
int fu = getf(a[i].u);
int fv = getf(a[i].v);
int u = a[i].u,v = a[i].v,w = a[i].w;
if(fu != fv){
Fa[fu] = fv;
add(u,v,w);
add(v,u,w);
}
}
}
void dfs(int u){
vis[u] = 1;
for(int i = 1;i <= 16;i ++){
if(dep[u] < (1 << i)) break;
fa[u][i] = fa[fa[u][i - 1]][i - 1];
d[u][i] = min(d[u][i - 1],d[fa[u][i - 1]][i - 1]);
}
for(int i = head[u];~i;i = e[i].next){
int v = e[i].v,w = e[i].w;
if(v != fa[u][0]){
fa[v][0] = u;
dep[v] = dep[u] + 1;
d[v][0] = w;
dfs(v);
}
}
}
int LCA(int x,int y){
if(dep[x] < dep[y]) swap(x,y);
int t = dep[x] - dep[y];
for(int i = 0;i <= 16;i ++){
if(t & (1 << i)) x = fa[x][i];
}
if(x == y) return x;
for(int i = 16;i >= 0;i --){
if(fa[x][i] != fa[y][i]){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
int solve(int u,int v){
int t = dep[u] - dep[v];
int ans = INF;
for(int i = 0;i <= 16;i ++){
if(t & (1 << i)){
ans = min(ans,d[u][i]);
u = fa[u][i];
}
}
return ans;
}
int main(){
memset(head,-1,sizeof(head));
cin >> n >> m;
for(int i = 0;i < m;i ++){
cin >> a[i].u >> a[i].v >> a[i].w;
}
kruscal();//最大生成树
for(int i = 1;i <= n;i ++)//预处理出fa[][],d[][],dep[]
if(!vis[i]) dfs(i);
cin >> q;
for(int i = 0;i < q;i ++){
int u,v;
cin >> u >> v;
if(getf(u) != getf(v)) cout << -1 << endl;
else{
int t = LCA(u,v);
cout << min(solve(u,t),solve(v,t)) << endl;
}
}
return 0;
}
牛客 Genius ACM
题目链接
ps:这道题的优化真的很妙!!
题意:首先易证校验值就是一个有序数组里,最大与最小的差的平方加上次大与次小的差的平方加上…依次类推
然后这个问题就是一个贪心问题了
思路:可以用倍增优化一下取值操作,再用归并优化一下排序操作 因为要算校验值数组必须有序,那么已经取到的数必然是有序的,新加进去的若干数只需要先自身排序再与前面的数归并一下即可
AC代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 5e5 + 10;
int T,n,m;
typedef long long ll;
ll k;
ll a[N],b[N],c[N];
int ans,R;
void Merge(int l,int mid,int r){
int i = l,j = mid + 1,t = l;
while(i <= mid && j <= r){
if(b[i] <= b[j]) c[t ++] = b[i ++];
else c[t ++] = b[j ++];
}
while(i <= mid) c[t ++] = b[i ++];
while(j <= r) c[t ++] = b[j ++];
}
ll cal(int l,int r){
ll res = 0;
for(int i = R + 1;i <= r;i ++){
b[i] = a[i];
}
sort(b + R + 1,b + 1 + r);
Merge(l,R,r);
for(int i = 1;i <= min(m,(r - l + 1) >> 1);i ++){
res += (c[l + i - 1] - c[r - i + 1]) * (c[l + i - 1] - c[r - i + 1]);
}
return res;
}
int work(int l){
int p = 1,r = l - 1;
while(p){
ll res = cal(l,min(n,r + p));
if(res <= k){
R = r = min(n,r + p);
for(int i = l;i <= r;i ++) b[i] = c[i];
if(r == n) break;
p <<= 1;
}
else p >>= 1;
}
return r;
}
int main(){
scanf("%d",&T);
while(T --){
ans = 0;
R = 0;
scanf("%d%d%lld",&n,&m,&k);
for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
int l = 1;
while(l <= n){
l = work(l) + 1;
ans ++;
}
printf("%d\n",ans);
}
return 0;
}
标签:include,return,fa,int,题解,应用题,dep,ans,倍增 来源: https://blog.csdn.net/cpp_juruo/article/details/113937898