P2619 [国家集训队]Tree I
题意: V V V个点 E E E条边的无向带权图,每条边的颜色为黑色或白色。求恰好有 n e e d need need条白边的生成树中权值最小的生成树。
设选择
x
x
x条白边的答案为
f
(
x
)
f(x)
f(x)。
首先,根据
k
r
u
s
k
a
l
kruskal
kruskal直接生成一颗最小生成树,假设有
x
x
x条白边,如果要减少到
x
−
1
x-1
x−1条,则需要删除在前的一条白边,加入一条边权等于或更大的黑边。如果要增加到
x
+
1
x+1
x+1条,同样需要删除一条黑边,加入一条边权等于或更大的白边。
从而,
f
(
x
)
f(x)
f(x)是一个下凹函数。
把点数看作
x
x
x轴,
f
(
x
)
f(x)
f(x)的值看作
y
y
y轴,
f
(
x
)
f(x)
f(x)的图像大致如下:
如果分别对点
x
=
{
0
,
1
,
.
.
.
,
∞
}
x=\{0,1,...,\infty\}
x={0,1,...,∞}做切线,可以发现切线的斜率从左到右单调递增,如图分别做点
A
,
D
,
G
A,D,G
A,D,G的切线。
此外,在斜率相同、截距不同的所有与
f
(
x
)
f(x)
f(x)有交点的直线中,与
f
(
x
)
f(x)
f(x)相切的那条直线截距最短。
从而假设我们知道了选
x
x
x个白点斜率,我们只需要最小化直线的截距。
直线的方程为
y
=
k
∗
x
+
b
y=k*x+b
y=k∗x+b,令曲线
f
(
x
)
f(x)
f(x)和直线相交,有
f
(
x
)
=
y
=
k
∗
x
+
b
→
b
=
f
(
x
)
−
k
∗
x
f(x)=y=k*x+b\rightarrow b=f(x)-k*x
f(x)=y=k∗x+b→b=f(x)−k∗x,为了求得截距
b
b
b,给所有白点的边权都
−
k
-k
−k,则选
x
x
x个点恰好给
b
b
b带来
−
k
∗
x
-k*x
−k∗x的贡献,这样去做最小生成树就解除了要选固定数量的白边的限制。
不知道选 x x x个白点的斜率怎么办?我们可以二分 k k k,在做最小生成树的时候统计使用的白点的数量,如果白点个数少了,则增大斜率,如果多了,则减小斜率。当统计的数量恰好为白点的个数,再由 f ( x ) = b + k ∗ x f(x)=b+k*x f(x)=b+k∗x还原恰好选择 x x x点的代价。这种方法就是传说中的wqs二分。
此外,如果 k k k是个浮点数怎么办?为了避免浮点,可以在排序的时候权值相同则让白点优先排在前面。这样把二分的条件改成如果点数 ≥ n e e d \ge need ≥need则更新答案为 b + n e e d ∗ k b+need*k b+need∗k。
#include<bits/stdc++.h>
typedef long long ll;
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define nep(i,r,l) for(int i=r;i>=l;i--)
void sc(int &x){scanf("%d",&x);}
void sc(int &x,int &y){scanf("%d%d",&x,&y);}
void sc(int &x,int &y,int &z){scanf("%d%d%d",&x,&y,&z);}
void sc(char *s){scanf("%s",s);}
void out(int x){printf("%d\n",x);}
void out(ll x){printf("%lld\n",x);}
void out(int x,int y){printf("%d %d\n",x,y);}
void out(ll x,ll y){printf("%lld %lld\n",x,y);}
void out(int x,int y,int z){printf("%d %d %d\n",x,y,z);}
void out(ll x,ll y,ll z){printf("%lld %lld %lld\n",x,y,z);}
using namespace std;
const int N=1e5+5;
int n,m,q;
struct node
{
int u,v,w,c;
bool operator<(const node&o)const
{
if(w==o.w) return c<o.c;
return w<o.w;
}
}e[N],e2[N];
int f[N];
int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);}
pair<int,int>sol(int k)
{
rep(i,1,m)
if(e[i].c==0) e[i].w-=k;
sort(e+1,e+1+m);
rep(i,1,n) f[i]=i;
int ans=0,res=0;
rep(i,1,m)
{
int fu=getf(e[i].u),fv=getf(e[i].v);
if(fu==fv) continue;
f[fu]=fv;
ans+=e[i].c==0;
res+=e[i].w;
}
rep(i,1,m)
if(e[i].c==0) e[i].w+=k;
return {ans,res};
}
int main()
{
//freopen("1.in","r",stdin);freopen("1.out","w",stdout);
sc(n,m,q);
rep(i,1,m)
sc(e[i].u,e[i].v,e[i].w),sc(e[i].c),e[i].u++,e[i].v++;
int l=-100,r=100,ans;
while(l<=r)
{
int m=l+r>>1;
pair<int,int>x=sol(m);
if(x.first>=q) ans=x.second+q*m,r=m-1;
else l=m+1;
}
out(ans);
}
P4767 [IOI2000]邮局
设
f
(
x
)
f(x)
f(x)为建立了
x
x
x个邮局的最小代价,有
f
(
x
)
≤
f
(
x
−
1
)
f(x)\le f(x-1)
f(x)≤f(x−1)和
f
(
x
−
1
)
−
f
(
x
)
≥
f
(
x
)
−
f
(
x
+
1
)
f(x-1)-f(x)\ge f(x)-f(x+1)
f(x−1)−f(x)≥f(x)−f(x+1),从而
f
(
x
)
f(x)
f(x)的图像也为一个单调下凹的函数。同样可以二分斜率
k
k
k,给每建立一个邮局的花费增加
−
k
-k
−k,就可以去掉恰好选
P
P
P个邮局的限制,就可以用
O
(
W
2
)
O(W^2)
O(W2)的
d
p
dp
dp解决。总的时间复杂度为
O
(
W
2
l
o
g
A
)
O(W^2logA)
O(W2logA)。
此外,还可以发现这个
O
(
W
2
)
O(W^2)
O(W2)的
d
p
dp
dp可以用四边形不等式优化,将时间复杂度降到
O
(
W
l
o
g
W
)
O(WlogW)
O(WlogW)。总的时间复杂度可化为
O
(
W
l
o
g
W
l
o
g
A
)
O(WlogWlogA)
O(WlogWlogA)。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=3005;
int n,m,a[N],sum[N];
int cost(int l,int r)
{
int m=l+r>>1;
return sum[r]-sum[m]-a[m]*(r-m)+a[m]*(m-l+1)-sum[m]+sum[l-1];
}
pair<int,int>dp[N];
pair<int,int>cal(int i,int j,int k)
{
if(i>j) return {inf,inf};
return {dp[i].first+cost(i+1,j)-k,dp[i].second+1};
}
pair<int,int> sol(int k)
{
memset(dp,inf,sizeof(dp));
dp[0]={0,0};
for(int i=1;i<=n;i++)
for(int j=0;j<i;j++)
dp[i]=min(dp[i],cal(j,i,k));
return dp[n];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
int l=-10000,r=10000,ans;
while(l<=r)
{
int mid=l+r>>1;
pair<int,int>x=sol(mid);
if(x.second<=m) ans=x.first+m*mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
四边形不等式
四边形不等式参考文章
用四边形不等式优化
d
p
dp
dp需要
c
o
s
t
i
,
j
cost_{i,j}
costi,j满足:
- c o s t i , j + c o s t i + 1 , j + 1 ≤ c o s t i , j + 1 + c o s t i + 1 , j ( i < j ) cost_{i,j}+cost_{i+1,j+1}\le cost_{i,j+1}+cost_{i+1,j}(i<j) costi,j+costi+1,j+1≤costi,j+1+costi+1,j(i<j)
- c o s t a , d ≥ c o s t b , c ( a ≤ b ≤ c ≤ d ) cost_{a,d}\ge cost_{b,c}(a\le b\le c\le d) costa,d≥costb,c(a≤b≤c≤d)
此外,
1
1
1的充要条件为
c
o
s
t
i
,
k
+
c
o
s
t
j
,
h
≤
c
o
s
t
i
,
h
+
c
o
s
t
k
,
j
(
i
≤
j
<
k
≤
h
)
cost_{i,k}+cost_{j,h}\le cost_{i,h}+cost_{k,j}(i\le j<k\le h)
costi,k+costj,h≤costi,h+costk,j(i≤j<k≤h)
这样可以优化
d
p
i
,
j
=
m
i
n
(
d
p
i
,
m
+
d
p
m
+
1
,
j
)
+
c
o
s
t
i
,
j
(
i
≤
m
<
j
)
dp_{i,j}=min(dp_{i,m}+dp_{m+1,j})+cost_{i,j}(i\le m< j)
dpi,j=min(dpi,m+dpm+1,j)+costi,j(i≤m<j)的
d
p
dp
dp方程。令
s
i
,
j
s_{i,j}
si,j为让上述方程取得最小值的
m
m
m,则有
s
i
,
j
−
1
≤
s
i
,
j
≤
s
i
+
1
,
j
s_{i,j-1}\le s_{i,j}\le s_{i+1,j}
si,j−1≤si,j≤si+1,j,
O
(
n
3
)
O(n^3)
O(n3)的方程就能被优化到
O
(
n
2
)
O(n^2)
O(n2)。
同时还能优化
d
p
j
=
m
i
n
(
d
p
i
+
c
o
s
t
i
,
j
∣
i
∈
[
0
,
j
−
1
]
)
dp_{j}=min(dp_i+cost_{i,j}|i\in [0,j-1])
dpj=min(dpi+costi,j∣i∈[0,j−1])的方程,需要使用单调队列二分,
O
(
n
2
)
O(n^2)
O(n2)的方程就能被优化到
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
如果上述条件改为:
c
o
s
t
i
,
j
+
c
o
s
t
i
+
1
,
j
+
1
≥
c
o
s
t
i
,
j
+
1
+
c
o
s
t
i
+
1
,
j
(
i
<
j
)
cost_{i,j}+cost_{i+1,j+1}\ge cost_{i,j+1}+cost_{i+1,j}(i<j)
costi,j+costi+1,j+1≥costi,j+1+costi+1,j(i<j)
还可以优化 d p i , j = m a x ( d p i , m + d p m + 1 , j ) + c o s t i , j ( i ≤ m < j ) dp_{i,j}=max(dp_{i,m}+dp_{m+1,j})+cost_{i,j}(i\le m< j) dpi,j=max(dpi,m+dpm+1,j)+costi,j(i≤m<j)和 d p j = m a x ( d p i + c o s t i , j ∣ i ∈ [ 0 , j − 1 ] ) dp_{j}=max(dp_i+cost_{i,j}|i\in [0,j-1]) dpj=max(dpi+costi,j∣i∈[0,j−1])的方程。
P6246 [IOI2000] 邮局 加强版
题解参考文章
d
p
dp
dp的转移方程为
d
p
j
=
m
i
n
(
d
p
i
+
c
o
s
t
i
+
1
,
j
∣
i
∈
[
0
,
j
−
1
]
)
dp_j=min(dp_i+cost_{i+1,j}|i\in[0,j-1])
dpj=min(dpi+costi+1,j∣i∈[0,j−1])。
c
o
s
t
l
,
r
cost_{l,r}
costl,r为在第
l
l
l和第
r
r
r个邮局之间放一个邮局的最小代价。
cost满足四边形不等式
对于任意
a
<
a
+
1
<
b
<
b
+
1
a<a+1<b<b+1
a<a+1<b<b+1,证明
c
o
s
t
a
,
b
+
c
o
s
t
a
+
1
,
b
+
1
≤
c
o
s
t
a
,
b
+
1
+
c
o
s
t
a
+
1
,
b
cost_{a,b}+cost_{a+1,b+1}\le cost_{a,b+1}+cost_{a+1,b}
costa,b+costa+1,b+1≤costa,b+1+costa+1,b
首先,在
[
l
,
r
]
[l,r]
[l,r]之间建立邮局选择
[
l
,
r
]
[l,r]
[l,r]之间的中位数最优。
其次,当区间长度为偶数选择中间两个任意一个作为中位数都合法。
从而,区间
[
l
+
1
,
r
]
,
[
l
,
r
]
[l+1,r],[l,r]
[l+1,r],[l,r]可以取同样的中位数。
那么
[
a
+
1
,
b
+
1
]
[a+1,b+1]
[a+1,b+1]和
[
a
,
b
+
1
]
[a,b+1]
[a,b+1]取中位数相同,有
c
o
s
t
a
,
b
+
1
−
c
o
s
t
a
+
1
,
b
+
1
=
s
m
1
−
s
a
(
1
)
cost_{a,b+1}-cost_{a+1,b+1}=s_{m_1}-s_a (1)
costa,b+1−costa+1,b+1=sm1−sa(1)
[
a
,
b
]
[a,b]
[a,b]和
[
a
+
1
,
b
]
[a+1,b]
[a+1,b]取中位数相同,有
c
o
s
t
a
,
b
−
c
o
s
t
a
+
1
,
b
=
s
m
2
−
s
a
(
2
)
cost_{a,b}-cost_{a+1,b}=s_{m_2}-s_a(2)
costa,b−costa+1,b=sm2−sa(2)
(
1
)
−
(
2
)
(1)-(2)
(1)−(2)得
c
o
s
t
a
,
b
+
1
+
c
o
s
t
a
+
1
,
b
−
(
c
o
s
t
a
,
b
+
c
o
s
t
a
+
1
,
b
+
1
)
=
s
m
1
−
s
m
2
cost_{a,b+1}+cost_{a+1,b}-(cost_{a,b}+cost_{a+1,b+1})=s_{m_1}-s_{m_2}
costa,b+1+costa+1,b−(costa,b+costa+1,b+1)=sm1−sm2
显然
m
1
≥
m
2
m_1\ge m_2
m1≥m2,故有
s
m
1
≥
s
m
2
→
s
m
1
−
s
m
2
≥
0
s_{m_1}\ge s_{m_2}\rightarrow s_{m_1}-s_{m_2}\ge 0
sm1≥sm2→sm1−sm2≥0。得证
c
o
s
t
a
,
b
+
1
+
c
o
s
t
a
+
1
,
b
≥
c
o
s
t
a
,
b
+
c
o
s
t
a
+
1
,
b
+
1
cost_{a,b+1}+cost_{a+1,b}\ge cost_{a,b}+cost_{a+1,b+1}
costa,b+1+costa+1,b≥costa,b+costa+1,b+1
结论
当 d p j dp_j dpj由 d p i dp_i dpi转移过来,称 i i i优化 j j j,则在 c o s t i , j cost_{i,j} costi,j满足四边形不等式的情况下, i i i优化的点必为一段连续区间。
反证法
设存在
i
≠
j
,
a
<
b
<
c
i\neq j,a<b<c
i=j,a<b<c使得
i
i
i能优化
a
,
c
a,c
a,c,
i
i
i不能优化
b
b
b,
j
j
j能优化
b
b
b。
则有
(
A
)
f
i
+
c
o
s
t
i
+
1
,
a
<
f
j
+
c
o
s
t
j
+
1
,
a
→
f
i
−
f
j
<
c
o
s
t
j
+
1
,
a
−
c
o
s
t
i
+
1
,
a
(
B
)
f
i
+
c
o
s
t
i
+
1
,
c
<
f
j
+
c
o
s
t
j
+
1
,
c
→
f
i
−
f
j
<
c
o
s
t
j
+
1
,
c
−
c
o
s
t
i
+
1
,
c
(
C
)
f
i
+
c
o
s
t
i
+
1
,
b
>
f
j
+
c
o
s
t
j
+
1
,
b
→
f
i
−
f
j
>
c
o
s
t
j
+
1
,
b
−
c
o
s
t
i
+
1
,
b
(A)\ \ f_i+cost_{i+1,a}<f_j+cost_{j+1,a}\\\rightarrow f_i-f_j<cost_{j+1,a}-cost_{i+1,a}\\(B)\ \ f_i+cost_{i+1,c}<f_j+cost_{j+1,c}\\\rightarrow f_i-f_j<cost_{j+1,c}-cost_{i+1,c}\\(C)\ \ f_i+cost_{i+1,b}>f_j+cost_{j+1,b}\\\rightarrow f_i-f_j>cost_{j+1,b}-cost_{i+1,b}
(A) fi+costi+1,a<fj+costj+1,a→fi−fj<costj+1,a−costi+1,a(B) fi+costi+1,c<fj+costj+1,c→fi−fj<costj+1,c−costi+1,c(C) fi+costi+1,b>fj+costj+1,b→fi−fj>costj+1,b−costi+1,b
整合得
{
c
o
s
t
j
+
1
,
b
−
c
o
s
t
i
+
1
,
b
<
f
i
−
f
j
<
c
o
s
t
j
+
1
,
c
−
c
o
s
t
i
+
1
,
c
c
o
s
t
j
+
1
,
b
−
c
o
s
t
i
+
1
,
b
<
f
i
−
f
j
<
c
o
s
t
j
+
1
,
a
−
c
o
s
t
i
+
1
,
a
\begin{cases}cost_{j+1,b}-cost_{i+1,b}<f_i-f_j<cost_{j+1,c}-cost_{i+1,c}\\cost_{j+1,b}-cost_{i+1,b}<f_i-f_j<cost_{j+1,a}-cost_{i+1,a}\end{cases}
{costj+1,b−costi+1,b<fi−fj<costj+1,c−costi+1,ccostj+1,b−costi+1,b<fi−fj<costj+1,a−costi+1,a
移项得
{
c
o
s
t
j
+
1
,
b
+
c
o
s
t
i
+
1
,
c
<
c
o
s
t
j
+
1
,
c
+
c
o
s
t
i
+
1
,
b
(
1
)
c
o
s
t
j
+
1
,
b
+
c
o
s
t
i
+
1
,
a
<
c
o
s
t
j
+
1
,
a
+
c
o
s
t
i
+
1
,
b
(
2
)
\begin{cases}cost_{j+1,b}+cost_{i+1,c}<cost_{j+1,c}+cost_{i+1,b}&(1)\\cost_{j+1,b}+cost_{i+1,a}<cost_{j+1,a}+cost_{i+1,b}&(2)\end{cases}
{costj+1,b+costi+1,c<costj+1,c+costi+1,bcostj+1,b+costi+1,a<costj+1,a+costi+1,b(1)(2)
而四边行不等式满足的条件为:
对任意
i
≤
j
<
k
≤
h
i\le j<k\le h
i≤j<k≤h,有
c
o
s
t
i
,
k
+
c
o
s
t
j
,
h
≤
c
o
s
t
i
,
h
+
c
o
s
t
j
,
k
cost_{i,k}+cost_{j,h}\le cost_{i,h}+cost_{j,k}
costi,k+costj,h≤costi,h+costj,k
数轴上画图即交叉小于包含。
则当
i
+
1
<
j
+
1
<
b
<
c
i+1<j+1<b<c
i+1<j+1<b<c,式子
(
1
)
(1)
(1)不满足。
当
j
+
1
<
i
+
1
<
a
<
b
j+1<i+1<a<b
j+1<i+1<a<b,式子
(
2
)
(2)
(2)不满足。
从而结论成立。
从而利用上面的结论,当更新到点
i
i
i时,可以将
[
1
,
n
]
[1,n]
[1,n]划分为若干个不同的区间,每个区间被同一个点优化。
如果一个段区间同时能被多个点优化,保留最大的点即可,这样任何时刻
[
1
,
n
]
[1,n]
[1,n]都能被划分为若干个不重叠的区间。
当更新
d
p
i
dp_i
dpi时,在队列里二分,找到能优化
i
i
i的点,对
i
i
i进行更新。
随后,求出 i i i能优化的区间,从队列中最后一段区间开始,如果这段区间被 i i i优化更优,将他合并进 i i i优化的区间。否则,对于最后一段区间,二分将其划分成两段,一段合并进 i i i优化的区间,另一段保留。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f3f3f3f3fll
using namespace std;
typedef long long ll;
const int N=500005;
int n,m;
ll sum[N],a[N];
ll cost(int l,int r)
{
int m=l+r>>1;
return sum[r]-sum[m]-a[m]*(r-m)+a[m]*(m-l+1)-sum[m]+sum[l-1];
}
pair<ll,ll>dp[N];
pair<ll,ll>cal(int i,int j,int k)
{
if(i>j) return {inf,inf};
return {dp[i].first+cost(i+1,j)-k,dp[i].second+1};
}
struct node
{
int x,l,r;
node(int x=0,int l=0,int r=0):x(x),l(l),r(r){}
};
int top;
node q[N];
pair<ll,ll> sol(int k)
{
memset(dp,inf,sizeof(dp));
dp[0]={0,0};
q[top=1]=node(0,1,n);
for(int i=1;i<=n;i++)
{
int l=1,r=top,p;
while(l<=r)
{
int m=l+r>>1;
if(q[m].l<=i) p=m,l=m+1;
else r=m-1;
}
dp[i]=cal(q[p].x,i,k);
p=-1;
while(top&&cal(i,q[top].l,k)<=cal(q[top].x,q[top].l,k)) p=q[top--].l;
if(top&&cal(i,q[top].r,k)<=cal(q[top].x,q[top].r,k))
{
int l=q[top].l,r=q[top].r;
while(l<=r)
{
int m=l+r>>1;
if(cal(i,m,k)<=cal(q[top].x,m,k))
p=m,r=m-1;
else l=m+1;
}
q[top].r=p-1;
}
if(p!=-1)
q[++top]={i,p,n};
}
return dp[n];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
sort(a+1,a+1+n);
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+a[i];
int l=-2000000,r=2000000;
ll ans;
while(l<=r)
{
int mid=l+r>>1;
pair<ll,ll>x=sol(mid);
if(x.second<=m) ans=x.first+1ll*m*mid,l=mid+1;
else r=mid-1;
}
printf("%lld\n",ans);
}