题意:
一个长度为N的序列(每个元素是
(ai,bi)
这样的数对),连续地分成若干组。每组左右边界是
(l1,r1)
,
(l2,r2)
,⋯,
(lp,rp)
,满足
li=ri−1+1
,
li≤ri
,
l1=1
,
rp=n
。分组必须满足两个条件:前面组的元素的b值比后面组元素的a值大;令
Mi
为第i个组内最大的a,所有
Mi
的和不超过
limit
。令Si为第i个组的元素的b的和,最小化
maxSi
。
题解:
二分答案。
能否破除b和a的关系呢?
考虑什么情况下必须捆绑在一起。
如果
ai>bj
,那么任意
bk≤bj
,都必须与
ai
捆绑在一起,否则不符合题意。
所以考虑排序
bj
,然后从大到小扫。
然后剩下的就没有限制了,于是问题就变成,给出一个数列,(每个组a的最大值)的和不能超过limit,(各组内b的的和)的最大值最小。
然后一个单调队列优化DP,具体细节看代码。
想交OJ的话可以看这个SPOJ.com - Problem SEQPAR2
然后lcy大神用线段树,我跑了8.72s,而Ta却只要4s,线段树题解看Ta的吧:http://blog.youkuaiyun.com/never_see/article/details/72802369
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;
const int size = 50000+10;
const LL INF = 0x7fffffff;
int n,limit,q[size],a[size],b[size];
int dp[size],p1[size],p2[size];
bool check(int m) {
int i,j=1,k,s=0,f=0,r=0;
for(i=1; i<=n; i++) {
s+=b[i];
while(s>m) s-=b[j++];
if(j>i) return 0;
while(f<r and a[q[r-1]]<=a[i]) --r;
while(f<r and q[f]<j) ++f;
q[r++]=i;
dp[i]=dp[j-1]+a[q[f]];
for(k=f+1; k<r; k++)
dp[i]=Min(dp[i],dp[q[k-1]]+a[q[k]]);
}
return dp[n]<=limit;
}
bool cmp(int x,int y) {
return b[x]<b[y];
}
int main() {
int i, j, l, r, mid;
freopen("gold.in","r",stdin);
freopen("gold.out","w",stdout);
scanf("%d%d", &n, &limit);
for (i = 1; i <= n; ++i)
scanf("%d%d", &a[i], &b[i]), p1[i] = p2[i] = i;
std::sort(p1 + 1, p1 + n + 1, cmp);
for (j = 1, i = n; i >= 1; --i)
for (; j <= n && b[p1[j]] <= a[i]; ++j)
p2[p1[j]] = i;
for (i = 1, j = 1; i <= n; i = l, ++j) {
a[j] = a[i];
b[j] = b[i];
for (l = i + 1, r = Max(p2[i], i); l <= r; ++l) {
a[j] = Max(a[j], a[l]);
b[j] += b[l];
r = Max(r, p2[l]);
}
}
n = j - 1;
for (l = 0, r = INF; l < r;) {
mid = l + r >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
printf("%d\n", l);
return 0;
}
题解:
这个题目有两种做法,一种直接用斜率优化,另一种在斜率优化的基础上进行点分治优化。
然后这个题目在NOI2014上被加强了为购票,题目和做法差不多,可以参见这个题目的做法。
代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
typedef long long LL;
const int N = 100000+10;
const LL INF = 1LL<<60;
int n,g[N],nxt[N<<1],v[N<<1],w[N<<1],ok[N<<1],tot=1,son[N],f[N];
int size,now,fa[N],V[N];
int q[N],anc[N],t;
LL ans[N],d[N],W[N];
inline int read(int &in) {
in=0;char ch=getchar();
for(;ch>='0'&&ch<='9';ch=getchar())
in=in*10+ch-'0';
return in;
}
inline void add_edge(int x,int y,int z) {
v[++tot]=y;w[tot]=z;nxt[tot]=g[x];ok[tot]=1;g[x]=tot;
}
inline int up(LL &x,LL y) { if(x>y) x=y; }
inline double pos(int x,int y) {
return (double)(ans[y]-ans[x])/(double)(d[y]-d[x]);
}
namespace my_self {
inline void find_root(int x,int y) {
son[x]=1;f[x]=0;
for(int i=g[x];i;i=nxt[i])
if(ok[i] and v[i]!=y) {
find_root(v[i],x);son[x]+=son[v[i]];
if(son[v[i]]>f[x]) f[x]=son[v[i]];
}
if(size-son[x]>f[x]) f[x]=size-son[x];
if(f[x]<f[now]) now=x;
}
inline void erfen(int x) {
int l=1,r=t-1,tmp=t,mid;
while(l<=r) {
mid=(l+r)>>1;
if((double)V[x]<=pos(q[mid],q[mid+1]))
r=(tmp=mid)-1;
else l=mid+1;
}
up(ans[x],W[x]+ans[q[tmp]]-d[q[tmp]]*V[x]);
}
inline void dfs(int x) {
W[x]+=d[x]*V[x];
for(int i=g[x];i;i=nxt[i])
if(v[i]!=fa[x])
d[v[i]]=d[fa[v[i]]=x]+w[i],dfs(v[i]);
}
inline void cal(int x,int y) {
erfen(x);
for(int i=g[x];i;i=nxt[i])
if(ok[i]&&v[i]!=y)cal(v[i],x);
}
inline void cal2(int x,int y) {
up(ans[x],W[x]+ans[y]-d[y]*V[x]);
for(int i=g[x];i;i=nxt[i])
if(ok[i]&&v[i]!=fa[x])cal2(v[i],y);
}
inline void solve(int x) {
f[0]=size=son[x];
find_root(x,now=0);
int root=now,ta;
if(root!=x) {
for(int i=g[root];i;i=nxt[i])
if(v[i]==fa[root]) {
ok[i]=ok[i^1]=0,solve(x);break;
}
int i;
for(ta=0,i=fa[root];;i=fa[i]) {
anc[++ta]=i;if(i==x) break;
}
for(t=0;ta;q[++t]=anc[ta--])
while(t>1&&pos(anc[ta],q[t])<pos(q[t],q[t-1]))
t--;
if(root>1) erfen(root);
while(t>1 and pos(root,q[t])<pos(q[t],q[t-1]))
t--;
q[++t]=root;
for(int i=g[root];i;i=nxt[i]) if(ok[i]) cal(v[i],root);
for(int i=g[root];i;i=nxt[i]) if(ok[i]) ok[i^1]=0,solve(v[i]);
}
else
for(int i=g[x];i;i=nxt[i])
if(ok[i]) ok[i^1]=0,cal2(v[i],x),solve(v[i]);
}
}
int main() {
freopen("harbingers.in","r",stdin);
freopen("harbingers.out","w",stdout);
read(n);
for(int i=1;i<n;i++) {
int x,y,z;read(x);read(y);read(z);
add_edge(x,y,z);add_edge(y,x,z);
}
for(int i=2;i<=n;i++) {
int x;read(x);read(V[i]);W[i]=x;ans[i]=INF;
}
son[1]=n;my_self::dfs(1);my_self::solve(1);
for(int i=2;i<=n;i++)
printf("%lld ",ans[i]);
return 0;
}
原题APIO 2010特别行动队
斜率优化经典题
代码:
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#define F(i,j,n) for(int i=j;i<=n;i++)
#define D(i,j,n) for(int i=j;i>=n;i--)
typedef long long ll;
const int maxn = 100000+10;
using namespace std;
int n,l,r;
ll a,b,c,q[maxn],f[maxn],s[maxn];
inline int read()
{
int x=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
inline double getk(int x,int y)
{
return (double)((f[x]+a*s[x]*s[x]-b*s[x])-(f[y]+a*s[y]*s[y]-b*s[y]))/(double)(s[x]-s[y]);
}
int main()
{
n=read();a=read();b=read();c=read();
F(i,1,n) s[i]=s[i-1]+read();
l=r=1;q[1]=0;
F(i,1,n)
{
while (l<r&&getk(q[l],q[l+1])>a*s[i]*2) l++;
f[i]=f[q[l]]+a*s[q[l]]*s[q[l]]-b*s[q[l]]-a*s[i]*s[q[l]]*2+a*s[i]*s[i]+b*s[i]+c;
while (l<r&&getk(q[r-1],q[r])<getk(q[r],i)) r--;
q[++r]=i;
}
printf("%lld\n",f[n]);
return 0;
}