Problem A 划艇
题意:有 1 ≤ n ≤ 500 1\le n\le500 1≤n≤500个元素,第 i i i个元素可以选或不选,如果选它的值必须在 [ a i , b i ] [a_i,b_i] [ai,bi]之间,要求至少选出一个元素,且选出的元素值递增。求方案数 m o d    1 0 9 + 7 \mod10^9+7 mod109+7。
首先,当元素个数较少而元素值的范围较大时,往往可以考虑离散化。
为了方便起见,把每个区间离散化,变为 [ a i , b i + 1 ) [a_i,b_i+1) [ai,bi+1)。
设离散化后的第 i i i个区间为 [ x i , x i + 1 ) [x_i,x_{i+1}) [xi,xi+1),长度为 l e n i len_i leni。
设 d p i , j dp_{i,j} dpi,j为选出的最后一个元素为第 i i i个元素,它的值在 [ x j , x j + 1 ) [x_j,x_{j+1}) [xj,xj+1)之间的方案数。
如果前一个元素不在
[
x
j
,
x
j
+
1
)
[x_j,x_{j+1})
[xj,xj+1)之间比较好转移。
d
p
i
,
j
+
=
l
e
n
j
∑
I
=
0
i
−
1
∑
J
=
0
j
−
1
d
p
I
,
J
dp_{i,j}+=len_j\sum_{I=0}^{i-1}\sum_{J=0}^{j-1}dp_{I,J}
dpi,j+=lenjI=0∑i−1J=0∑j−1dpI,J
如果前一个元素在
[
x
j
,
x
j
+
1
)
[x_j,x_{j+1})
[xj,xj+1)之间就比较难转移了。设
k
k
k为第一个在
[
x
j
,
x
j
+
1
)
[x_j,x_{j+1})
[xj,xj+1)之间的元素。
设除 i , k i,k i,k外有 c n t cnt cnt个元素在 [ x j , x j + 1 ) [x_j,x_{j+1}) [xj,xj+1)之间,则方案数就是有 c n t + 2 cnt+2 cnt+2个元素,每个元素可在 [ x j , x j + 1 ) [x_j,x_{j+1}) [xj,xj+1)之间选或不选(第 i , k i,k i,k个元素必须选),选出的元素值递增的方案数。
如果除
i
,
k
i,k
i,k外有
a
a
a个元素被选择,则显然有
(
c
n
t
a
)
(
l
e
n
a
+
2
)
{cnt\choose a}{len\choose a+2}
(acnt)(a+2len)
种方案。所以方案总数就是
∑
a
=
0
c
n
t
(
c
n
t
a
)
(
l
e
n
a
+
2
)
=
∑
a
=
0
c
n
t
(
c
n
t
a
)
(
l
e
n
c
n
t
−
a
+
2
)
\sum_{a=0}^{cnt}{cnt\choose a}{len\choose a+2}\\ =\sum_{a=0}^{cnt}{cnt\choose a}{len\choose cnt-a+2}
a=0∑cnt(acnt)(a+2len)=a=0∑cnt(acnt)(cnt−a+2len)
这个怎么求呢?考虑有
c
n
t
+
l
e
n
cnt+len
cnt+len个人,其中有
c
n
t
cnt
cnt个神犇,
l
e
n
len
len个蒟蒻。
现在要选出 c n t + 2 cnt+2 cnt+2个人,则枚举神犇个数就可以得出 ∑ a = 0 c n t ( c n t a ) ( l e n c n t − a + 2 ) \sum_{a=0}^{cnt}{cnt\choose a}{len\choose cnt-a+2} ∑a=0cnt(acnt)(cnt−a+2len)。
因此
∑
a
=
0
c
n
t
(
c
n
t
a
)
(
l
e
n
c
n
t
−
a
+
2
)
=
(
c
n
t
+
l
e
n
c
n
t
+
2
)
\sum_{a=0}^{cnt}{cnt\choose a}{len\choose cnt-a+2}\\ ={cnt+len\choose cnt+2}
a=0∑cnt(acnt)(cnt−a+2len)=(cnt+2cnt+len)
总结一下:
d
p
i
,
j
+
=
∑
k
=
1
i
−
1
[
[
x
j
,
x
j
+
1
)
⊆
[
a
k
,
a
k
+
1
)
]
(
c
n
t
+
l
e
n
j
c
n
t
+
2
)
×
∑
I
=
0
k
−
1
∑
J
=
0
j
−
1
d
p
I
,
J
dp_{i,j}+=\sum_{k=1}^{i-1}[[x_j,x_{j+1})\subseteq[a_k,a_k+1)]{cnt+len_j\choose cnt+2}\times\sum_{I=0}^{k-1}\sum_{J=0}^{j-1}dp_{I,J}
dpi,j+=k=1∑i−1[[xj,xj+1)⊆[ak,ak+1)](cnt+2cnt+lenj)×I=0∑k−1J=0∑j−1dpI,J
这个dp是
O
(
n
5
)
O(n^5)
O(n5)的,用前缀和优化可以做到
O
(
n
3
)
O(n^3)
O(n3)。
最后一个问题是如何求组合数。由于 ( c n t + l e n j c n t + 2 ) cnt+len_j\choose cnt+2 (cnt+2cnt+lenj)中的 l e n j len_j lenj可能很大,所以不能预处理。
这时可以运用公式
(
n
m
)
=
n
m
(
n
−
1
m
−
1
)
{n\choose m}=\frac nm{n-1\choose m-1}
(mn)=mn(m−1n−1)
递推,每当
c
n
t
cnt
cnt加1就把组合数乘以
c
n
t
+
l
e
n
c
n
t
+
2
\frac{cnt+len}{cnt+2}
cnt+2cnt+len。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace io{
const int l=1<<19;
char buf[l],*s,*t,c;
char gc(){
if(s==t){
t=(s=buf)+fread(buf,1,l,stdin);
return s==t?EOF:*s++;
}
return *s++;
}
template<class I>void gi(I &x){
x=0;c=gc();while(c<'0'||c>'9')c=gc();
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
}
};
using io::gi;
const int N=505,M=1005;
const ll p=1000000007,P=p*p;
inline ll add(ll a,ll b){return a+b<p?a+b:a+b-p;}
inline ll sub(ll a,ll b){return a-b<0?a-b+p:a-b;}
int n,m;
ll a[N],b[N],x[M],l[M],inv[N],sum[N][M],dp[N][M];
int main(){
scanf("%d",&n);
inv[1]=1;
for(int i=2;i<=n+2;i++)inv[i]=(p-p/i)*inv[p%i]%p;
for(int i=1;i<=n;i++){
scanf("%d%d",&a[i],&b[i]);
++b[i];
x[++m]=a[i];
x[++m]=b[i];
}
sort(x+1,x+m+1);
m=unique(x+1,x+m+1)-x-1;
for(int i=1;i< m;i++)l[i]=x[i+1]-x[i];
for(int i=0;i< m;i++)sum[0][i]=1;
for(int i=1;i<=n;i++){
a[i]=lower_bound(x+1,x+m+1,a[i])-x;
b[i]=lower_bound(x+1,x+m+1,b[i])-x;
for(int j=a[i];j<b[i];j++){
ll len=l[j],c=((len*(len-1))>>1)%p,d=len*sum[i-1][j-1]%p,now=0;
for(int k=i-1;k;k--)if(a[k]<=j&&j<b[k]){
d+=c*sum[k-1][j-1];
if(d>=P)d-=P;
++now;
c=c*((now+len)*inv[now+2]%p)%p;
}
dp[i][j]=d%p;
}
sum[i][0]=1;
for(int j=1;j<m;j++)sum[i][j]=add(sub(add(sum[i-1][j],sum[i][j-1]),sum[i-1][j-1]),dp[i][j]);
}
printf("%lld",sub(sum[n][m-1],1));
return 0;
}
Problem B 烟花表演
暴力不难想到:设 d p v , x dp_{v,x} dpv,x为 v v v的子树内 v v v到每个叶子距离为 x x x的最小代价。
通过感性理解观察可以发现
d
p
v
dp_v
dpv是下凸的。则我们需要对凸包执行2种操作:
- 在子树上方加一条边
- 合并若干凸包
可以发现,只要维护凸包的拐点横坐标即可。不妨设每个拐点斜率改变1(拐点可以重合)。
则操作2直接合并凸包的所有拐点即可。
对于操作1:
显然每次操作1后,凸包最右端斜率为1。
所以操作2后,设 v v v有 d v d_v dv个儿子,则凸包最右端斜率为 d v d_v dv。
因此操作1后删除拐点最大值 d v + 1 d_v+1 dv+1次,再添加2个拐点即可。
求出 d p 1 dp_1 dp1的拐点之后,由于 d p 1 , 0 = dp_{1,0}= dp1,0=树中所有边长之和,所以很容易求出 d p 1 dp_1 dp1的最小值。(删除拐点最大值 d v d_v dv次即可找出最大值所在拐点)
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=300005,M=N<<1;
int n,m,d[N],fa[N],rt[N],lc[M],rc[M],tot;
ll fw[N],val[M],ans;
int merge(int v,int u){
if(!(v&&u))return v|u;
if(val[v]<val[u])swap(v,u);
rc[v]=merge(rc[v],u);
if(rand()&1)swap(lc[v],rc[v]);
return v;
}
int main(){
static ll l,r;
scanf("%d%d",&n,&m);
for(int i=2;i<=n+m;i++){
scanf("%d%lld",&fa[i],&fw[i]);
ans+=fw[i];
++d[fa[i]];
}
for(int i=n+m;i!=1;i--){
l=0;r=0;
if(i<=n){
while(--d[i])rt[i]=merge(lc[rt[i]],rc[rt[i]]);
r=val[rt[i]];
rt[i]=merge(lc[rt[i]],rc[rt[i]]);
l=val[rt[i]];
rt[i]=merge(lc[rt[i]],rc[rt[i]]);
}
val[++tot]=l+fw[i];
val[++tot]=r+fw[i];
rt[i]=merge(rt[i],merge(tot-1,tot));
rt[fa[i]]=merge(rt[fa[i]],rt[i]);
}
while(d[1]--)rt[1]=merge(lc[rt[1]],rc[rt[1]]);
while(rt[1]){
ans-=val[rt[1]];
rt[1]=merge(lc[rt[1]],rc[rt[1]]);
}
printf("%lld",ans);
return 0;
}
Problem C 最大差分
Subtask 1
这个Subtask很简单,每次询问到 m i n , m a x min,max min,max时把区间缩小为 ( m i n , m a x ) (min,max) (min,max)继续询问即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace io{
const int l=1<<19;
char buf[l],*s,*t,c;
char gc(){
if(s==t){
t=(s=buf)+fread(buf,1,l,stdin);
return s==t?EOF:*s++;
}
return *s++;
}
template<class I>void gi(I &x){
x=0;c=gc();while(c<'0'||c>'9')c=gc();
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
}
};
using io::gi;
const int N=100005;
int AKAPIO,n,t;
ll l,r=1000000000000000000ll,mn,mx,a[N],s;
int main(){
scanf("%d%d",&AKAPIO,&n);
while(1){
printf("? %lld %lld\n",l,r);
fflush(stdout);
scanf("%lld%lld",&mn,&mx);
if(mn==-1)break;
a[++t]=mn;
if(mn!=mx)a[++t]=mx;
if(t==n)break;
l=mn+1;r=mx-1;
if(l>r)break;
if(r-l+1==n-t){
for(int i=l;i<=r;i++)a[++t]=i;
break;
}
}
sort(a+1,a+n+1);
for(int i=1;i<n;i++)s=max(s,a[i+1]-a[i]);
printf("! %lld\n",s);
fflush(stdout);
return 0;
}
Subtask 2
这个Subtask比较神奇。
设整个区间最小值为
m
i
n
min
min,最大值为
m
a
x
max
max,则可以发现
a
n
s
≥
m
a
x
−
m
i
n
n
−
1
ans\ge\frac{max-min}{n-1}
ans≥n−1max−min
把
(
m
i
n
,
m
a
x
)
(min,max)
(min,max)中的数以
⌊
m
a
x
−
m
i
n
n
−
1
⌋
\lfloor\frac{max-min}{n-1}\rfloor
⌊n−1max−min⌋为长度分块,不难发现块内不存在答案。
因此询问每个块中的最小值、最大值,最后考虑这些值和 m i n , m a x min,max min,max即可。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
namespace io{
const int l=1<<19;
char buf[l],*s,*t,c;
char gc(){
if(s==t){
t=(s=buf)+fread(buf,1,l,stdin);
return s==t?EOF:*s++;
}
return *s++;
}
template<class I>void gi(I &x){
x=0;c=gc();while(c<'0'||c>'9')c=gc();
while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48);c=gc();}
}
};
using io::gi;
const int N=100005;
int AKAPIO,n,t;
ll mn,mx,mn1,mx1,a[N],b,s;
int main(){
scanf("%d%d",&AKAPIO,&n);
printf("? 0 1000000000000000000\n");
fflush(stdout);
scanf("%lld%lld",&mn,&mx);
a[++t]=mn;
a[++t]=mx;
b=(mx-mn)/(n-1);
for(ll i=mn+1;i<mx;i+=b+1){
printf("? %lld %lld\n",i,min(mx-1,i+b));
fflush(stdout);
scanf("%lld%lld",&mn1,&mx1);
if(mn1!=-1){
a[++t]=mn1;
if(mn1!=mx1)a[++t]=mx1;
}
}
sort(a+1,a+t+1);
for(int i=1;i<t;i++)s=max(s,a[i+1]-a[i]);
printf("! %lld\n",s);
fflush(stdout);
return 0;
}