【2022省选模拟】喜欢最最痛——数据结构、凸包、二分

2024-01-10 11:10

本文主要是介绍【2022省选模拟】喜欢最最痛——数据结构、凸包、二分,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我找不到原题链接

题目描述

在这里插入图片描述
在这里插入图片描述

题解

答案可以分为两部分:树上的原边、额外边。假设我们要用 i i i 条额外边,那么一定是选当前可选的最短的 i i i 条边,所以选的额外边的权值和一定是关于 i i i 单增的下凸包。

对于树上的原边,如果不加额外边那么代价一定是 2 ∗ 边 权 和 2*边权和 2。加一条额外边时,正好可以让树上少走一条简单路径,我们要最小化代价,即是最大化选的路径的长度,所以此时一定是选直径。

i i i 条额外边,正好可以让树上少走 i i i 条边不重复的简单路径。我们设 d p i dp_i dpi 表示选最多 i i i 条路径时的最大路径长度和,那么凭着做WQS二分时的直觉可以猜到这个 d p i dp_i dpi 是个单增的上凸包。既然两部分的答案都是凸包,并且它们加起来也是凸包,所以我们就可以通过二分斜率来找到最小值点。

第二部分的答案可以用平衡树动态维护凸包,然后每次可以直接平衡树上二分一个 log ⁡ \log log 求答案。现在问题是怎么快速求每一个 d p i dp_i dpi

用树形DP方法显然至少是 O ( n 2 ) O(n^2) O(n2) 的,我们需要用一个类似求费用流时的反悔退流的方法。当我们选一条路径的时候,我们直接选直径,然后把直径上的边权全部取相反数。第二次选路径的时候,我们还是选直径,此时如果选的直径上有第一次选的边,那么边权被抵消掉,相当于把两条边相交的路径自动处理成了两条边不交的路径。由于证明了每次选择任意路径必定合法,所以每次贪心选直径的正确性也易证。

我们需要支持路径边权翻转,动态维护直径(由于边权不好动态维护,所以下面的做法都是把边权转换成点权做的)。LCT就不说了,我只讲树链剖分的做法。

对于一条直径,它在重链上会交出一段区间。我们只需要维护每个节点走轻儿子能够到达的最远点和距离,然后在重链上做线段树的信息合并。由于要支持翻转边权,所以你要同时维护正边权和负边权情况下的答案。每条重链的答案是独立的,所以最方便且高效的做法是用全局平衡二叉树维护。

但只是这样的话,我们会漏掉走两个轻儿子的路径。当然,我们可以用set维护轻儿子的答案,顺便求走轻儿子的最长路径。但是这样既麻烦又慢,我们可以把边转点过后再三度化,这样每个点最多只有一个轻儿子,可以直接忽略这个情况。

总复杂度可以做到 O ( n log ⁡ n ) O(n\log n) O(nlogn),但是由于数据结构中维护信息update时的常数非常大(20左右),所以在代码中一些地方写成 O ( n log ⁡ 2 n ) O(n\log^2n) O(nlog2n) 并不会慢多少。

代码

献上 O ( n log ⁡ n ) O(n\log n) O(nlogn) 最快代码

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define uns unsigned
#define IF (it->first)
#define IS (it->second)
#define END putchar('\n')
#pragma GCC optimize("Ofast")
using namespace std;
const int MAXN=400005;
const ll INF=1e17;
inline ll read(){ll x=0;bool f=1;char s=getchar();while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();return f?x:-x;
}
int ptf[50],lpt;
inline void print(ll x,char c='\n'){if(x<0)putchar('-'),x=-x;ptf[lpt=1]=x%10;while(x>9)x/=10,ptf[++lpt]=x%10;while(lpt)putchar(ptf[lpt--]^48);if(c>0)putchar(c);
}
inline ll lowbit(ll x){return x&-x;}struct edge{int v,to;edge(){}edge(int V,int T){v=V,to=T;}
}e[MAXN<<1];
int EN,G[MAXN];
inline void addedge(int u,int v){e[++EN]=edge(v,G[u]),G[u]=EN;e[++EN]=edge(u,G[v]),G[v]=EN;
}
vector<int>G_[MAXN];
int n,k,m;
ll val[MAXN];
inline void pdfs(int x,int fa){//三度化 vector<int>sn;for(int v:G_[x])if(v^fa)pdfs(v,x),sn.push_back(v);if(sn.size()<3){for(int v:sn)addedge(x,v);}else{int o=sn.size()-2;k+=o;for(int i=k;i>k-o+1;i--)addedge(i-1,i);addedge(x,k-o+1),addedge(sn.back(),k),sn.pop_back();for(int i=sn.size()-1,j=k;i>0;i--,j--)addedge(sn[i],j);addedge(x,sn[0]);}
}
int fa[MAXN],dep[MAXN],siz[MAXN],hs[MAXN];
int hd[MAXN],id[MAXN],IN,tl[MAXN],tp[MAXN];
struct node{int u,v;ll d;node(){}node(int U,int V,ll D){u=U,v=V,d=D;}inline bool operator<(const node&b)const{if(d^b.d)return d<b.d;else if(v^b.v) return v<b.v;else return u<b.u;}
}bx[MAXN],as[MAXN];
int fr[MAXN];
ll dr[MAXN];
struct itn{int ls,rs,u,v;ll a,s;node lf[2],rf[2],f[2];bool lz;itn(){}itn(int x,ll A){ls=rs=0,lz=0,s=a=A,u=v=x;f[0]=rf[0]=lf[0]=node(x,fr[x],dr[x]+a);f[1]=rf[1]=lf[1]=node(x,fr[x],dr[x]-a);f[0]=max(f[0],as[x]),f[1]=max(f[1],as[x]);}
}t[MAXN];
int rt[MAXN];
inline void cover(int x){if(!x)return;t[x].a=-t[x].a,t[x].s=-t[x].s;swap(t[x].lf[0],t[x].lf[1]);swap(t[x].rf[0],t[x].rf[1]);swap(t[x].f[0],t[x].f[1]);t[x].lz^=1;
}
inline void pushd(int x){if(t[x].lz)cover(t[x].ls),cover(t[x].rs),t[x].lz=0;
}
inline void update(int x){int ls=t[x].ls,rs=t[x].rs;t[x].u=t[x].v=id[x];if(ls)t[x].u=t[ls].u;if(rs)t[x].v=t[rs].v;int u=t[x].u,v=t[x].v;t[x].s=t[ls].s+t[rs].s+t[x].a;for(int e=0,inv=1;e<2;e++,inv*=-1){node a=t[ls].lf[e],b=t[rs].lf[e];t[x].lf[e]=max(max(a,node(u,b.v,b.d+(t[ls].s+t[x].a)*inv)),node(u,fr[id[x]],(t[ls].s+t[x].a)*inv+dr[id[x]]));a=t[ls].rf[e],b=t[rs].rf[e];t[x].rf[e]=max(max(node(v,a.v,a.d+(t[rs].s+t[x].a)*inv),b),node(v,fr[id[x]],(t[rs].s+t[x].a)*inv+dr[id[x]]));a=t[ls].rf[e],b=t[rs].lf[e];t[x].f[e]=max(max(t[ls].f[e],t[rs].f[e]),max(node(a.v,fr[id[x]],a.d+t[x].a*inv+dr[id[x]]),max(node(fr[id[x]],b.v,b.d+t[x].a*inv+dr[id[x]]),node(a.v,b.v,a.d+b.d+t[x].a*inv) )));t[x].f[e]=max(t[x].f[e],as[id[x]]);}
}
inline void updp(int x,int z){if(!x)return;pushd(x);if(z<x)updp(t[x].ls,z);else if(z>x)updp(t[x].rs,z);update(x);
}
inline void reve(int x,int l,int r,int a,int b){if(!x||l>r)return;if(l>=a&&r<=b){cover(x);return;}pushd(x);if(x>=a&&x<=b)t[x].a=-t[x].a;if(a<x)reve(t[x].ls,l,x-1,a,b);if(b>x)reve(t[x].rs,x+1,r,a,b);update(x);
}
inline void updfr(int x){fr[x]=x,dr[x]=0;if(bx[x].d>dr[x])fr[x]=bx[x].v,dr[x]=bx[x].d;
}
inline void updbx(int x){if((x^tp[x])||!fa[x])return;bx[fa[x]]=t[rt[x]].lf[0];as[fa[x]]=t[rt[x]].f[0];
}
inline int build(int l,int r){if(l>r)return 0;int p=0,s=0,x;for(int i=l;i<=r;i++)s+=siz[id[i]]-siz[hs[id[i]]];for(x=l;x<r;x++){p+=siz[id[x]]-siz[hs[id[x]]];if((p<<1)>s)break;}t[x]=itn(id[x],val[id[x]]);t[x].ls=build(l,x-1),t[x].rs=build(x+1,r);return update(x),x;
}
//prepare end
inline void dfs1(int x){siz[x]=1,dep[x]=dep[fa[x]]+1,hs[x]=0;for(int i=G[x];i;i=e[i].to){int v=e[i].v;if(v==fa[x])continue;fa[v]=x,dfs1(v),siz[x]+=siz[v];if(siz[v]>siz[hs[x]])hs[x]=v;}
}
inline void dfs2(int x){hd[x]=++IN,id[IN]=x,tp[x]=(x==hs[fa[x]]?tp[fa[x]]:x),tl[tp[x]]=IN;fr[x]=x,dr[x]=0,as[x]=bx[x]=node(-1,-1,0);if(hs[x])dfs2(hs[x]);for(int i=G[x];i;i=e[i].to){int v=e[i].v;if(v==fa[x]||v==hs[x])continue;dfs2(v);}if(x==tp[x]){rt[x]=build(hd[x],tl[x]),updbx(x);if(fa[x])updfr(fa[x]);}
}
inline void rev(int u,int v){if(u<1||v<1)return;while(tp[u]^tp[v]){if(dep[tp[u]]<dep[tp[v]])swap(u,v);reve(rt[tp[u]],hd[tp[u]],tl[tp[u]],hd[tp[u]],hd[u]);updbx(tp[u]),u=fa[tp[u]];updfr(u),updp(rt[tp[u]],hd[u]);}if(dep[u]>dep[v])swap(u,v);reve(rt[tp[u]],hd[tp[u]],tl[tp[u]],hd[u],hd[v]);while(fa[tp[u]]){updbx(tp[u]),u=fa[tp[u]];updfr(u),updp(rt[tp[u]],hd[u]);}updbx(tp[u]);
}
ll sum,dp[MAXN],ans[MAXN];
//FHQ begin
mt19937 Rand(835025);
struct fhq{int ls,rs,siz,val;ll a,s;fhq(){}fhq(ll A){ls=rs=0,siz=1,val=Rand(),a=s=A;}
};
#define pii pair<int,int>
#define fi first
#define se second
struct FHQ{fhq t[MAXN];int IN,root;FHQ(){IN=0;}inline void update(int x){t[x].s=t[t[x].ls].s+t[t[x].rs].s+t[x].a;t[x].siz=t[t[x].ls].siz+t[t[x].rs].siz+1;}inline pii splitz(int x,ll k){if(!x)return pii(0,0);pii res;if(t[x].a>k)res=splitz(t[x].ls,k),t[x].ls=res.se,update(x),res.se=x;else res=splitz(t[x].rs,k),t[x].rs=res.fi,update(x),res.fi=x;return res;}inline int mergg(int x,int y){if(!x||!y)return x^y;int res=233;if(t[x].val<t[y].val)t[x].rs=mergg(t[x].rs,y),update(x),res=x;else t[y].ls=mergg(x,t[y].ls),update(y),res=y;return res;}inline void insr(ll a){pii x=splitz(root,a);t[++IN]=fhq(a),root=mergg(x.fi,mergg(IN,x.se));}inline ll getmin(int x,int pk){if(!x)return dp[pk];int ds=t[t[x].ls].siz+1;ll cp=t[x].a+dp[pk+ds]-dp[pk+ds-1];if(cp>=0)return getmin(t[x].ls,pk);return t[t[x].ls].s+t[x].a+getmin(t[x].rs,pk+ds);}
}TP;
//FHQ end
signed main()
{freopen("love.in","r",stdin);freopen("love.out","w",stdout);t[0].lf[0]=t[0].lf[1]=t[0].rf[0]=t[0].rf[1]=t[0].f[0]=t[0].f[1]=node(0,0,-INF);n=read(),k=(n<<1)-1,m=read();for(int i=n+1;i<=k;i++){int u=read(),v=read();val[i]=read(),sum+=(val[i]<<1);G_[u].push_back(i),G_[i].push_back(u);G_[v].push_back(i),G_[i].push_back(v);}pdfs(1,0);dfs1(1),dfs2(1),ans[0]=dp[0]=sum;for(int i=1;i<=m;i++){node an=t[rt[1]].f[0];dp[i]=dp[i-1]-an.d,rev(an.u,an.v);}for(int i=1;i<=m;i++)TP.insr(read()),ans[i]=TP.getmin(TP.root,0);for(int i=0;i<=m;i++)print(ans[i],i<m?' ':'\n');return 0;
}

这篇关于【2022省选模拟】喜欢最最痛——数据结构、凸包、二分的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/590588

相关文章

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

usaco 1.2 Transformations(模拟)

我的做法就是一个一个情况枚举出来 注意计算公式: ( 变换后的矩阵记为C) 顺时针旋转90°:C[i] [j]=A[n-j-1] [i] (旋转180°和270° 可以多转几个九十度来推) 对称:C[i] [n-j-1]=A[i] [j] 代码有点长 。。。 /*ID: who jayLANG: C++TASK: transform*/#include<

poj 1113 凸包+简单几何计算

题意: 给N个平面上的点,现在要在离点外L米处建城墙,使得城墙把所有点都包含进去且城墙的长度最短。 解析: 韬哥出的某次训练赛上A出的第一道计算几何,算是大水题吧。 用convexhull算法把凸包求出来,然后加加减减就A了。 计算见下图: 好久没玩画图了啊好开心。 代码: #include <iostream>#include <cstdio>#inclu

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

poj 2976 分数规划二分贪心(部分对总体的贡献度) poj 3111

poj 2976: 题意: 在n场考试中,每场考试共有b题,答对的题目有a题。 允许去掉k场考试,求能达到的最高正确率是多少。 解析: 假设已知准确率为x,则每场考试对于准确率的贡献值为: a - b * x,将贡献值大的排序排在前面舍弃掉后k个。 然后二分x就行了。 代码: #include <iostream>#include <cstdio>#incl

poj 3104 二分答案

题意: n件湿度为num的衣服,每秒钟自己可以蒸发掉1个湿度。 然而如果使用了暖炉,每秒可以烧掉k个湿度,但不计算蒸发了。 现在问这么多的衣服,怎么烧事件最短。 解析: 二分答案咯。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <c

poj 3258 二分最小值最大

题意: 有一些石头排成一条线,第一个和最后一个不能去掉。 其余的共可以去掉m块,要使去掉后石头间距的最小值最大。 解析: 二分石头,最小值最大。 代码: #include <iostream>#include <cstdio>#include <cstdlib>#include <algorithm>#include <cstring>#include <c