森森开了一家快递公司,叫森森快递。因为公司刚刚开张,所以业务路线很简单,可以认为是一条直线上的N个城市,这些城市从左到右依次从0到(N−1)编号。由于道路限制,第i号城市(i=0,⋯,N−2)与第(i+1)号城市中间往返的运输货物重量在同一时刻不能超过Ci公斤。
公司开张后很快接到了Q张订单,其中j张订单描述了某些指定的货物要从Sj号城市运输到Tj号城市。这里我们简单地假设所有货物都有无限货源,森森会不定时地挑选其中一部分货物进行运输。安全起见,这些货物不会在中途卸货。
为了让公司整体效益更佳,森森想知道如何安排订单的运输,能使得运输的货物重量最大且符合道路的限制?要注意的是,发货时间有可能是任何时刻,所以我们安排订单的时候,必须保证共用同一条道路的所有货车的总重量不超载。例如我们安排1号城市到4号城市以及2号城市到4号城市两张订单的运输,则这两张订单的运输同时受2-3以及3-4两条道路的限制,因为两张订单的货物可能会同时在这些道路上运输。
输入格式:
输入在第一行给出两个正整数N和Q(2≤N≤105, 1≤Q≤105),表示总共的城市数以及订单数量。
第二行给出(N−1)个数,顺次表示相邻两城市间的道路允许的最大运货重量Ci(i=0,⋯,N−2)。题目保证每个Ci是不超过231的非负整数。
接下来Q行,每行给出一张订单的起始及终止运输城市编号。题目保证所有编号合法,并且不存在起点和终点重合的情况。
输出格式:
在一行中输出可运输货物的最大重量。
输入样例:
10 6
0 7 8 5 2 3 1 9 10
0 9
1 8
2 7
6 3
4 5
4 2
输出样例:
7
样例提示:我们选择执行最后两张订单,即把5公斤货从城市4运到城市2,并且把2公斤货从城市4运到城市5,就可以得到最大运输量7公斤。
思路:首先这个很容易想到区间的贪心,区间的贪心一般是这么个分析思路:按照什么顺序来选择区间?如何排序?
显然城市之间的最大运输量是途经路段载荷的最小值
首先考虑区间不相交的情况:不相交的两个区间哪个先选都没有影响,能运输的货物重量为
接着考虑包含的情况,若,那么,选择不仅能获得较大的运输货物量,并且其他区间的占用更少,别的区间能载更多货物
最后是局部相交的情况:最大运货量一定是要么是相交部分的最小值(因为不管选哪个订单都会经过相交部分,两个订单的量加起来不能超过相交部分),要么是不相交的部分更小,两个都可以选一部分,如果是前种情况,选择订单1和订单2没有区别,如果是后种情况
1、若两个都选能凑到相交部分的最小值,那么先选前面和先选后面没区别
2、若两个都选凑不到相交部分的最小值,那么先选前面和先选后面的也没区别
附上三个小时才结束代码。。。再次强烈安利广大胖友写博客,暑假做过的那么多线段树的题就跟白做了一样
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int N=1e5+10;
struct node
{
int l,r;
LL val;
int lazy;
}tr[N<<2];
int num[N];
void push_up(int root)
{
tr[root].val=min(tr[root<<1].val,tr[root<<1|1].val);
}
void build(int root,int l,int r)
{
tr[root].l=l;
tr[root].r=r;
tr[root].lazy=0;
if(l==r)
{
cin>>tr[root].val;
return ;
}
int mid=(l+r)>>1;
build(root<<1,l,mid);
build(root<<1|1,mid+1,r);
push_up(root);
}
void push_down(int root)
{
if(tr[root].lazy)
{
tr[root<<1].lazy+=tr[root].lazy; //注意是+=,写成+过不了最后一个点
tr[root<<1|1].lazy+=tr[root].lazy;
tr[root<<1].val+=tr[root].lazy;
tr[root<<1|1].val+=tr[root].lazy;
tr[root].lazy=0;
}
}
LL query(int root,int l,int r) //[l,r]是待查区间,[tr[root].l,tr[root].r]是当前区间
{
if(l<=tr[root].l&&r>=tr[root].r) return tr[root].val; //待查区间包含当前区间,直接返回当前区间的最小值
int mid=(tr[root].l+tr[root].r)>>1;
if(tr[root].lazy) push_down(root);
LL ans; //待查区间完全在左边
if(r<=mid) return query(root<<1,l,r);
else if(l>mid) return query(root<<1|1,l,r);
else ans=min(query(root<<1,l,mid),query(root<<1|1,mid+1,r));
return ans;
}
void update(int root,int l,int r,int val)
{
if(l<=tr[root].l&&r>=tr[root].r)
{
tr[root].lazy+=val;
tr[root].val+=val;
return ;
}
if(tr[root].lazy) push_down(root);
int mid=(tr[root].l+tr[root].r)>>1;
if(r<=mid) update(root<<1,l,r,val);
else if(l>mid) update(root<<1|1,l,r,val);
else
{
update(root<<1,l,mid,val);
update(root<<1|1,mid+1,r,val);
}
push_up(root);
}
//pair<int,int> p[N];
struct point
{
int s;
int t;
point(){};
point(int s,int t):s(s),t(t){}
bool operator<(const point &rhs)const
{
if(t==rhs.t)
return s>rhs.s;
else return t<rhs.t;
}
}p[N];
int main()
{
int n,q;
cin>>n>>q;
build(1,1,n-1); //
int a,b;
for(int i=1;i<=q;++i)
{
cin>>a>>b;
if(a>b) swap(a,b);
p[i]=point(a,b); //优先终点按从小到大,终点相同应该从大到小
}
sort(p+1,p+1+q);
LL ans=0;
for(int i=1;i<=q;++i)
{
int s=p[i].s,t=p[i].t; //下标不一致,debug两行泪 按照题目的下标设定,我应该查询[s,t-1],但是由于我是[1,n]建树,所以查询区间改成[s+1,t]
LL num=query(1,s+1,t);
if(num>0)
{
ans+=num;
update(1,s+1,t,-num);
}
}
cout<<ans<<endl;
return 0;
}
太久没写线段树,以至于区间更新想不起来用线段树写出来的弱智代码
//这道题的直觉是 两个城市越近越好,优先选择订单经过的城市中负荷量最小值最大的订单比较好
//有点像区间的贪心问题emmm
//。。。这是不是01背包的变种
//dp[i] //考虑前i个订单能获得的最大值
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int c[N];
struct order
{
int s,t;
}tran[N];
int dp[N];
int main()
{
int n,q;
cin>>n>>q;
for(int i=1;i<=n-1;++i)
cin>>c[i];
for(int i=1;i<=q;++i)
cin>>tran[i].s>>tran[i].t;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;++i)
{
int x=INF;
for(int j=tran[i].s;j<tran[i].t;++j)
{
x=min(x,c[i]);
//cout<<x<<endl;
}
if(x<0) dp[i]=dp[i-1];
else
{
dp[i]=dp[i-1]+x;
for(int j=tran[i].s;j<tran[i].t;++j)
{
c[j]-=x;
}
}
}
cout<<dp[n]<<endl;
return 0;
}