前言
水分警告
JZOJ 3169 生产汽车
题目
n n n个工人, m m m辆汽车都必须按顺序加工,对于每辆车 w j w_j wj,由工人 i i i加工时,需要的时间是 a i × w j a_i\times w_j ai×wj,下一个工人接替上一个工人必须是空闲的,除了第一个工人不允许有等待,问最少完工的时间
分析
首先用前缀和预处理
s
[
j
]
=
∑
i
=
1
n
a
[
j
]
s[j]=\sum_{i=1}^na[j]
s[j]=∑i=1na[j]
设
f
[
i
]
f[i]
f[i]表示第
i
i
i辆车最小开始时间,那么
f
[
i
]
=
f
[
i
−
1
]
+
m
a
x
{
w
[
i
−
1
]
×
s
[
j
]
−
w
[
i
]
×
s
[
j
−
1
]
}
f[i]=f[i-1]+max\{w[i-1]\times s[j]-w[i]\times s[j-1]\}
f[i]=f[i−1]+max{w[i−1]×s[j]−w[i]×s[j−1]}
设
k
(
j
<
k
)
k(j<k)
k(j<k)比
j
j
j决策更优
w
[
i
−
1
]
×
s
[
k
]
−
w
[
i
]
×
s
[
k
−
1
]
>
w
[
i
−
1
]
×
s
[
j
]
−
w
[
i
]
×
s
[
j
−
1
]
w[i-1]\times s[k]-w[i]\times s[k-1]>w[i-1]\times s[j]-w[i]\times s[j-1]
w[i−1]×s[k]−w[i]×s[k−1]>w[i−1]×s[j]−w[i]×s[j−1]
移项得到
w
[
i
−
1
]
×
(
s
[
k
]
−
s
[
j
]
)
>
w
[
i
]
×
(
s
[
k
−
1
]
−
s
[
j
−
1
]
)
w[i-1]\times (s[k]-s[j])>w[i]\times (s[k-1]-s[j-1])
w[i−1]×(s[k]−s[j])>w[i]×(s[k−1]−s[j−1])
最后得到
s
[
k
]
−
s
[
j
]
s
[
k
−
1
]
−
s
[
j
−
1
]
>
w
[
i
]
w
[
i
−
1
]
\frac{s[k]-s[j]}{s[k-1]-s[j-1]}>\frac{w[i]}{w[i-1]}
s[k−1]−s[j−1]s[k]−s[j]>w[i−1]w[i]
但是后面这个东西不满足单调性,所以首先要维护上凸壳,然后二分最优的位置
代码
#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
typedef long long ll;
ll s[100001]; int n,m,w[100001],len,q[100001];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline double slope(int x,int y){
return 1.0*(s[x]-s[y])/(s[x-1]-s[y-1]);
}
signed main(){
n=iut(); m=iut();
for (rr int i=1;i<=n;++i) s[i]=s[i-1]+iut();
for (rr int i=1;i<=m;++i) w[i]=iut();
for (rr int i=1;i<=n;++i){
for (;len>1&&slope(i,q[len])>slope(q[len],q[len-1]);--len);
q[++len]=i;
}
rr ll ans=0;
for (rr int i=2;i<=m;++i){
rr double t=1.0*w[i]/w[i-1];
rr int l=0,r=len;
while (l<r){
rr int mid=(l+r)>>1;
if (slope(q[mid+1],q[mid])>t) l=mid+1;
else r=mid;
}
ans+=w[i-1]*s[q[l]]-w[i]*s[q[l]-1];
}
return !printf("%lld",ans+s[n]*w[m]);
}
JZOJ 3170 挑选玩具
题目
ABC找到
N
N
N个箱子,箱子里装着一些玩具,一共有
M
M
M种玩具,编号从1到
M
M
M,同一种玩具可能出现在多个箱子里。
ABC决定从中选择一些箱子,把这些箱子中的玩具聚集到一起,必须保证每种玩具至少出现一次。
问ABC一共有多少种选择方案。
分析
首先设
d
p
[
i
]
dp[i]
dp[i]表示集合为
i
i
i时集合中的每一种玩具都出现的方案数,显然
d
p
[
0
]
=
1
dp[0]=1
dp[0]=1
可以用容斥解决,答案就是
2
m
−
∑
(
2
d
p
[
!
i
]
−
1
)
×
(
−
1
)
∣
i
∣
2^m-\sum(2^{dp[!i]}-1)\times(-1)^{|i|}
2m−∑(2dp[!i]−1)×(−1)∣i∣
那
f
f
f怎么快速解决呢,可以想到分治
那么由于分成两部分时一部分某一位全是1,一部分某一位全是0,通过这样
d
p
[
i
+
m
i
d
]
+
=
d
p
[
i
]
dp[i+mid]+=dp[i]
dp[i+mid]+=dp[i]
代码
#include <cstdio>
#include <cctype>
#define rr register
using namespace std;
const int mod=1000000007;
int c[1048576],dp[1048576],n,m,f[1048576],ans; bool b[1048576];
inline signed iut(){
rr int ans=0; rr char c=getchar();
while (!isdigit(c)) c=getchar();
while (isdigit(c)) ans=(ans<<3)+(ans<<1)+(c^48),c=getchar();
return ans;
}
inline signed modd(int x){
rr int f=1;
if (x<0) x=-x,f=-f;
if (x>=mod) x-=mod;
return x*f;
}
inline void merge(int l,int r){
if (l==r) {dp[l]=c[l]; return;}
rr int mid=(l+r)>>1;
merge(l,mid),merge(mid+1,r);
for (rr int i=l;i<=mid;++i) dp[i+mid-l+1]+=dp[i];
}
signed main(){
n=iut(); m=iut(); f[0]=1;
for (rr int i=1;i<=n;++i){
rr int t=0;
for (rr int cnt=iut();cnt;--cnt)
t|=1<<(iut()-1);
++c[t];
}
for (rr int i=1;i<(1<<m);++i) b[i]=b[i>>1]^(i&1);
for (rr int i=1;i<=n;++i) f[i]=modd(f[i-1]+f[i-1]);
merge(0,(1<<m)-1);
for (rr int i=0;i<(1<<m);++i)
ans=modd(ans+(b[i]?(1-f[dp[(1<<m)-i-1]]):(f[dp[(1<<m)-i-1]]-1)));
return !printf("%d",modd(ans+mod));
}