【BZOJ-3638&3272&3267&3502】k-Maximum Subsequence Sum 费用流构图 + 线段树手动增广
3638: Cf172 k-Maximum Subsequence Sum
Time Limit: 50 Sec Memory Limit: 256 MBSubmit: 174 Solved: 92
[Submit][Status][Discuss]
Description
给一列数,要求支持操作: 1.修改某个数的值 2.读入l,r,k,询问在[l,r]内选不相交的不超过k个子段,最大的和是多少。
Input
The first line contains integer n (1 ≤ n ≤ 105), showing how many numbers the sequence has. The next line contains n integers a1, a2, ..., an (|ai| ≤ 500).
The third line contains integer m (1 ≤ m ≤ 105) — the number of queries. The next m lines contain the queries in the format, given in the statement.
All changing queries fit into limits: 1 ≤ i ≤ n, |val| ≤ 500.
All queries to count the maximum sum of at most k non-intersecting subsegments fit into limits: 1 ≤ l ≤ r ≤ n, 1 ≤ k ≤ 20. It is guaranteed that the number of the queries to count the maximum sum of at mostk non-intersecting subsegments doesn't exceed 10000.
Output
For each query to count the maximum sum of at most k non-intersecting subsegments print the reply — the maximum sum. Print the answers to the queries in the order, in which the queries follow in the input.
Sample Input
9 -8 9 -1 -1 -1 9 -8 9
3
1 1 9 1
1 1 9 2
1 4 6 3
Sample Output
25
0
HINT
In the first query of the first example you can select a single pair (1, 9). So the described sum will be 17.
Look at the second query of the first example. How to choose two subsegments? (1, 3) and (7, 9)? Definitely not, the sum we could get from (1, 3) and (7, 9) is 20, against the optimal configuration (1, 7) and (9, 9) with 25.
The answer to the third query is 0, we prefer select nothing if all of the numbers in the given interval are negative.
Source
3272: Zgg吃东西
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 93 Solved: 59
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
9 -8 9 -1 -1 -1 9 -8 9
5
1 1 9 1
1 1 9 2
1 4 6 3
0 3 -8
1 1 9 1
Sample Output
25
0
10
HINT
对于100%的数据 N,M<=100000 ,1<=k<=20.l<=l<=r<=n.数值的绝对值不会超过500.
Zgg是可以有手空闲的…
Source
3267: KC采花
Time Limit: 30 Sec Memory Limit: 256 MBSubmit: 65 Solved: 51
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
9 -8 9 -1 -1 -1 9 -8 9
3
1 1 9 1
1 1 9 2
1 4 6 3
Sample Output
25
0
HINT
100%的数据,满足n,m<=50000,k<=20,ai以及修改的val的绝对值不超过500。
Source
Solution
这道题,首先想到最大费用最大流,这类的对序列上的问题,是比较经典的问题,详见 《网络流与线性规划24题》最长k可重区间集问题
那么这道题,最标准的做法就是:
把每个数拆成两个点$I_{1}$和$I_{2}$ $I_{1}-->I_{2}$,$cap=1$,$cost=a[i]$
然后$S-->I_{1}$,$cap=INF$,$cost=0$;以及$I_{2}-->T$,$cap=INF$,$cost=0$;
并且相邻的两位连$I'_{2}-->I''_{1}$,$cap=1$,$cost=0$
那么只需要跑$K$次最大费用最大流,累加答案即可
但是很显然这种数据范围,这么做会TLE,那么不妨考虑对这种做法进行优化:
每次增广的过程,实质上就是取一段和最大的子序列,并将其反转
很显然对于这类序列上的操作,可以用线段树去实现,那么这道题的正确做法就明确了
费用流的构图,用线段树去手动模拟增广路程
线段树维护的方法,只需要维护一段区间的最大和子序列,涉及取反,所以还需要维护最小和子序列
但是发现,每进行一次取反,当前最大和子序列一定变成最小和子序列,最小和子序列一定变成最大,那么直接swap一下就好
鉴于一次询问需要增广K次,也就是一次询问要取反,所以显然需要开一个栈记录一下当前询问所反转的所有区间,在结束时还原
修改操作直接修改即可
总的时间复杂度是$O(knlogn)$
Code
#include<iostream> #include<cmath> #include<cstring> #include<algorithm> #include<cstdio> using namespace std; 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; } #define maxn 101000 int n,m,a[maxn],ans; struct DataNode { int lx,rx,mx,sum,pl,pr,p1,p2; void add(int pos,int x) {p1=p2=pl=pr=pos,mx=sum=lx=rx=x;} }; struct SegmentTreeNode { int l,r,tag; DataNode maxx,minn; void add(int x) {maxx.add(l,x); minn.add(l,-x);} }tree[maxn<<2]; inline DataNode Merge(DataNode ls,DataNode rs) { DataNode rt; rt.sum=ls.sum+rs.sum; rt.lx=ls.sum+rs.lx>ls.lx? ls.sum+rs.lx : ls.lx; rt.pl=ls.sum+rs.lx>ls.lx? rs.pl : ls.pl; rt.rx=rs.sum+ls.rx>rs.rx? rs.sum+ls.rx : rs.rx; rt.pr=rs.sum+ls.rx>rs.rx? ls.pr : rs.pr; rt.mx=ls.rx+rs.lx; rt.p1=ls.pr; rt.p2=rs.pl; if (rt.mx<ls.mx) rt.mx=ls.mx,rt.p1=ls.p1,rt.p2=ls.p2; if (rt.mx<rs.mx) rt.mx=rs.mx,rt.p1=rs.p1,rt.p2=rs.p2; return rt; } inline void Update(int now) { tree[now].minn=Merge(tree[now<<1].minn,tree[now<<1|1].minn); tree[now].maxx=Merge(tree[now<<1].maxx,tree[now<<1|1].maxx); } inline void Pushdown(int now) { if (!tree[now].tag || tree[now].l==tree[now].r) return; tree[now].tag^=1; swap(tree[now<<1].maxx,tree[now<<1].minn); swap(tree[now<<1|1].minn,tree[now<<1|1].maxx); tree[now<<1].tag^=1; tree[now<<1|1].tag^=1; } inline void BuildTree(int now,int l,int r) { tree[now].l=l; tree[now].r=r; tree[now].tag=0; if (l==r) {tree[now].add(a[l]); return;} int mid=(l+r)>>1; BuildTree(now<<1,l,mid); BuildTree(now<<1|1,mid+1,r); Update(now); } inline void Change(int now,int pos,int x) { Pushdown(now); if (tree[now].l==tree[now].r) {tree[now].add(x); return;} int mid=(tree[now].l+tree[now].r)>>1; if (pos<=mid) Change(now<<1,pos,x); else Change(now<<1|1,pos,x); Update(now); } inline DataNode Query(int now,int L,int R) { Pushdown(now); if (L==tree[now].l && R==tree[now].r) return tree[now].maxx; int mid=(tree[now].l+tree[now].r)>>1; if (R<=mid) return Query(now<<1,L,R); if (L>mid) return Query(now<<1|1,L,R); return Merge(Query(now<<1,L,mid),Query(now<<1|1,mid+1,R)); } inline void Reverse(int now,int L,int R) { Pushdown(now); if (L<=tree[now].l && R>=tree[now].r) {swap(tree[now].maxx,tree[now].minn); tree[now].tag^=1; return;} int mid=(tree[now].l+tree[now].r)>>1; if (L<=mid) Reverse(now<<1,L,R); if (R>mid) Reverse(now<<1|1,L,R); Update(now); } struct StackNode { int l,r; void Push(int L,int R) {l=L,r=R;} }stack[21]; int top; inline void SolveAsk(int L,int R,int K) { ans=0; top=0; while (K--) { DataNode tmp=Query(1,L,R); if (tmp.mx>0) ans+=tmp.mx; else break; Reverse(1,tmp.p1,tmp.p2); stack[++top].Push(tmp.p1,tmp.p2); } for (int i=top; i>=1; i--) Reverse(1,stack[i].l,stack[i].r); printf("%d\n",ans); } inline void SolveChange(int pos,int x) {Change(1,pos,x);} void Freopen() {freopen("data.in","r",stdin);freopen("data.out","w",stdout);} void Fclose() {fclose(stdin);fclose(stdout);} int main() { // Freopen(); n=read(); for (int i=1; i<=n; i++) a[i]=read(); BuildTree(1,1,n); m=read(); while (m--) { int opt=read(),L,R,K,x,d; switch (opt) { case 1 : L=read(),R=read(),K=read(); SolveAsk(L,R,K); break; case 0 : x=read(),d=read(); SolveChange(x,d); break; } } // Fclose(); return 0; }
3502: PA2012 Tanie linie
Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 181 Solved: 36
[Submit][Status][Discuss]
Description
n个数字,求不相交的总和最大的最多k个连续子序列。
1<= k<= N<= 1000000。
Input
Output
Sample Input
7 -3 4 -9 5
Sample Output
HINT
Source
Solution
和刚刚一样,但是范围扩大了,需要开longlong,所以内存很卡,时间也很卡..
Code
#include<iostream> #include<cmath> #include<cstring> #include<algorithm> #include<cstdio> using namespace std; 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; } #define maxn 1000010 int n,m,a[maxn];long long ans; struct DataNode { long long lx,rx,mx,sum;int pl,pr,p1,p2; void add(int pos,int x) {p1=p2=pl=pr=pos,mx=sum=lx=rx=x;} }; struct SegmentTreeNode { int l,r,tag; DataNode maxx,minn; void add(long long x) {maxx.add(l,x); minn.add(l,-x);} }tree[maxn*2+200000]; inline DataNode Merge(DataNode ls,DataNode rs) { DataNode rt; rt.sum=ls.sum+rs.sum; rt.lx=ls.sum+rs.lx>ls.lx? ls.sum+rs.lx : ls.lx; rt.pl=ls.sum+rs.lx>ls.lx? rs.pl : ls.pl; rt.rx=rs.sum+ls.rx>rs.rx? rs.sum+ls.rx : rs.rx; rt.pr=rs.sum+ls.rx>rs.rx? ls.pr : rs.pr; rt.mx=ls.rx+rs.lx; rt.p1=ls.pr; rt.p2=rs.pl; if (rt.mx<ls.mx) rt.mx=ls.mx,rt.p1=ls.p1,rt.p2=ls.p2; if (rt.mx<rs.mx) rt.mx=rs.mx,rt.p1=rs.p1,rt.p2=rs.p2; return rt; } inline void Update(int now) { tree[now].minn=Merge(tree[now<<1].minn,tree[now<<1|1].minn); tree[now].maxx=Merge(tree[now<<1].maxx,tree[now<<1|1].maxx); } inline void Pushdown(int now) { if (!tree[now].tag || tree[now].l==tree[now].r) return; tree[now].tag^=1; swap(tree[now<<1].maxx,tree[now<<1].minn); swap(tree[now<<1|1].minn,tree[now<<1|1].maxx); tree[now<<1].tag^=1; tree[now<<1|1].tag^=1; } inline void BuildTree(int now,int l,int r) { tree[now].l=l; tree[now].r=r; tree[now].tag=0; if (l==r) {tree[now].add(a[l]); return;} int mid=(l+r)>>1; BuildTree(now<<1,l,mid); BuildTree(now<<1|1,mid+1,r); Update(now); } inline DataNode Query(int now,int L,int R) { Pushdown(now); if (L==tree[now].l && R==tree[now].r) return tree[now].maxx; int mid=(tree[now].l+tree[now].r)>>1; if (R<=mid) return Query(now<<1,L,R); if (L>mid) return Query(now<<1|1,L,R); return Merge(Query(now<<1,L,mid),Query(now<<1|1,mid+1,R)); } inline void Reverse(int now,int L,int R) { Pushdown(now); if (L<=tree[now].l && R>=tree[now].r) {swap(tree[now].maxx,tree[now].minn); tree[now].tag^=1; return;} int mid=(tree[now].l+tree[now].r)>>1; if (L<=mid) Reverse(now<<1,L,R); if (R>mid) Reverse(now<<1|1,L,R); Update(now); } inline void SolveAsk(int L,int R,int K) { while (K--) { DataNode tmp=Query(1,L,R); if (tmp.mx>0) ans+=tmp.mx; else break; Reverse(1,tmp.p1,tmp.p2); } printf("%lld\n",ans); } int main() { n=read(); int K=read(); for (int i=1; i<=n; i++) a[i]=read(); BuildTree(1,1,n); SolveAsk(1,n,K); return 0; }