12.4日记
CDQ分治
- HDU1541:给定一些(a,b),定义(a,b)的等级为满足(a2<=a&&b2<=b)的(a2,b2)的个数。输出等级为0-n-1的星星个数。
二维偏序裸题。第一位排好序,第二维树状数组即可。
注意:树状数组不可以处理下标为0的情况,因此需要先+1。或者就直接离散化。离散化的时候,注意int len=unique(a+1,a+n+1)-a-1;
线段树
- HDU4578:区间加减+区间乘+区间修改+区间询问一次二次三次方和。
我tm哭了,debug了一天。既然是取模,所以lazy[id][3]标记在没有值的时候要设置为-1,不是用0判断。
思路:修改最优先,乘其次,加减最后。每次pushdown先判断修改,如果没有修改再进行乘和加减。每次修改值的时候,一次性把123次全都修改。
#include<bits/stdc++.h>
#define mid (l+r)/2
using namespace std;
const int M=1e5+20;
int v[4*M][4],lazy[4*M][4],P=1e4+7;
inline void push_up(int id){
for(int i=1;i<=3;++i)
v[id][i]=(v[id*2][i]+v[id*2+1][i])%P;
}
inline void operate(int *tar,int a,int b,int len){
tar[3]=(1LL*tar[3]*a*a*a%P+1LL*tar[2]*3*a*a*b%P+1LL*tar[1]*3*a*b*b%P+1LL*b*b*b*len%P)%P,
tar[2]=(1LL*tar[2]*a*a%P+1LL*tar[1]*2*a*b%P+1LL*b*b*len%P)%P,
tar[1]=(tar[1]*a+b*len)%P;
}
inline void push_down(int id,int l,int r){
if (lazy[id][3]!=-1){
int c=lazy[id][3];
lazy[id*2][1]=0,lazy[id*2][2]=1,lazy[id*2][3]=c,
v[id*2][1]=c*(mid-l+1)%P,
v[id*2][2]=v[id*2][1]*c%P,
v[id*2][3]=v[id*2][2]*c%P,
lazy[id*2+1][1]=0,lazy[id*2+1][2]=1,lazy[id*2+1][3]=c,
v[id*2+1][1]=c*(r-mid)%P,
v[id*2+1][2]=v[id*2+1][1]*c%P,
v[id*2+1][3]=v[id*2+1][2]*c%P;
lazy[id][1]=0,lazy[id][2]=1,lazy[id][3]=-1;
return;
}
int a=lazy[id][2],b=lazy[id][1];
if (lazy[id*2][3]!=-1)
lazy[id*2][3]=(lazy[id*2][3]*a+b)%P;
else
lazy[id*2][2]=lazy[id*2][2]*a%P,
lazy[id*2][1]=(lazy[id*2][1]*a+b)%P;
operate(v[id*2],a,b,mid-l+1);
if (lazy[id*2+1][3]!=-1)
lazy[id*2+1][3]=(lazy[id*2+1][3]*a+b)%P;
else
lazy[id*2+1][2]=lazy[id*2+1][2]*a%P,
lazy[id*2+1][1]=(lazy[id*2+1][1]*a+b)%P;
operate(v[id*2+1],a,b,r-mid);
lazy[id][1]=0,lazy[id][2]=1,lazy[id][3]=-1;
}
void build(int id,int l,int r){
for(int i=1;i<=3;++i)
v[id][i]=lazy[id][i]=0;
lazy[id][2]=1,lazy[id][3]=-1;
if (l==r)
return;
build(id*2,l,mid);
build(id*2+1,mid+1,r);
}
void operate1(int id,int l,int r,int ql,int qr,int a,int b){
if (ql<=l&&r<=qr){
if (lazy[id][3]!=-1)
lazy[id][3]=(lazy[id][3]*a+b)%P;
else
lazy[id][2]=(lazy[id][2]*a)%P,
lazy[id][1]=(lazy[id][1]*a+b)%P;
operate(v[id],a,b,r-l+1);
return;
}
push_down(id,l,r);
if (ql<=mid)
operate1(id*2,l,mid,ql,qr,a,b);
if (mid<qr)
operate1(id*2+1,mid+1,r,ql,qr,a,b);
push_up(id);
}
void operate3(int id,int l,int r,int ql,int qr,int c){
if (ql<=l&&r<=qr){
lazy[id][1]=0,lazy[id][2]=1,lazy[id][3]=c,
v[id][1]=c*(r-l+1)%P,
v[id][2]=v[id][1]*c%P,
v[id][3]=v[id][2]*c%P;
return;
}
push_down(id,l,r);
if (ql<=mid)
operate3(id*2,l,mid,ql,qr,c);
if (mid<qr)
operate3(id*2+1,mid+1,r,ql,qr,c);
push_up(id);
}
int query(int id,int l,int r,int ql,int qr,int q){
if (ql<=l&&r<=qr)
return v[id][q];
push_down(id,l,r);
int ans=0;
if (ql<=mid)
ans+=query(id*2,l,mid,ql,qr,q);
if (mid<qr)
ans+=query(id*2+1,mid+1,r,ql,qr,q);
return ans%P;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
while(n||m){
build(1,1,n);
for(int i=1;i<=m;++i){
int op,x,y,c;
scanf("%d%d%d%d",&op,&x,&y,&c);
switch(op){
case 1:{
operate1(1,1,n,x,y,1,c);
break;
}
case 2:{
operate1(1,1,n,x,y,c,0);
break;
}
case 3:{
operate3(1,1,n,x,y,c);
break;
}
case 4:{
printf("%d
",query(1,1,n,x,y,c));
break;
}
}
}
scanf("%d%d",&n,&m);
}
return 0;
}
单调数据结构
- POJ2559:给N个宽度为1的并列的矩形,求选出一个矩形,面积最大。
思路:可以贪心,以第i个矩形为最大模板,能选取的最大面积是左边第一个比他小的位置和右边第一个比他小的位置的距离*h[i],先单调栈求出两边第一个比他小的数,最后再扫一遍,都是(O(n))的。
但对于这种区间最小值*区间长度的最大值的题目,可以直接单调栈处理。
#include<cstdio>
#include<utility>
#include<stack>
using namespace std;
const int M=1e5+20;
int h[M];
#define pii pair<int,int>
#define LL long long
LL rect_max_U(int n,int *h){
h[n+1]=-1;
LL ans=0;
stack<int> st;
for(int i=1;i<=n+1;++i)
if (st.empty()||h[i]>h[st.top()])
st.push(i);
else{
int lef;
while(!st.empty()&&h[i]<=h[st.top()]){
lef=st.top(),st.pop();
ans=max(ans,1LL*h[lef]*(i-lef));
}
st.push(lef),h[lef]=h[i];
}
return ans;
}
int main(){
int n;
while(~scanf("%d",&n)&&n){
for(int i=1;i<=n;++i)
scanf("%d",&h[i]);
printf("%lld
",rect_max_U(n,h));
}
return 0;
}
- POJ3494:给一个01矩阵,求最大子矩阵的面积。
思路:太妙了,扫每一个横行,实际上相当于做了n遍最大矩形并面积。
#include<cstdio>
#include<utility>
#include<stack>
using namespace std;
const int M=2e3+20;
int mat[M][M],lmin[M],rmin[M];
#define pii pair<int,int>
int rect_max_U(int n,int *h){
h[n+1]=-1;
int ans=0;
stack<int> st;
for(int i=1;i<=n+1;++i)
if (st.empty()||h[i]>h[st.top()])
st.push(i);
else{
int lef;
while(!st.empty()&&h[i]<=h[st.top()]){
lef=st.top(),st.pop();
ans=max(ans,h[lef]*(i-lef));
}
st.push(lef),h[lef]=h[i];
}
return ans;
}
int main(){
int n,m;
while(~scanf("%d%d",&n,&m)){
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
scanf("%d",&mat[i][j]);
if (mat[i][j])
mat[i][j]+=mat[i-1][j];
}
int ans=0;
for(int i=1;i<=n;++i)
ans=max(ans,rect_max_U(m,mat[i]));
printf("%d
",ans);
}
return 0;
}
- POJ2796:求区间最小值*区间和的最大值。
思路:和POJ2559一样,只不过这里改成了区间和,求前缀和套单调栈即可。本质上是求出让每个值为最小值的最大区间。
#include<cstdio>
#include<utility>
#include<stack>
using namespace std;
const int M=1e5+20;
int h[M],l,r;
#define pii pair<int,int>
#define LL long long
LL sum[M];
LL rect_max_U(int n,int *h){
h[n+1]=-1;
LL ans=0;
stack<int> st;
for(int i=1;i<=n+1;++i)
if (st.empty()||h[i]>h[st.top()])
st.push(i);
else{
int lef;
while(!st.empty()&&h[i]<=h[st.top()]){
lef=st.top(),st.pop();
LL c=(sum[i-1]-sum[lef-1])*h[lef];
if (ans<c)
ans=c,l=lef,r=i-1;
}
st.push(lef),h[lef]=h[i];
}
return ans;
}
int main(){
int n;
while(~scanf("%d",&n)&&n){
l=1,r=n;
for(int i=1;i<=n;++i)
scanf("%d",&h[i]),sum[i]=sum[i-1]+h[i];
LL ans=rect_max_U(n,h);
printf("%lld
%d %d
",ans,l,r);
}
return 0;
}
总结
今天调那个线段树调了1w年……服了,以后就长了个教训,不是所有时候都可以用0判断是否存在的,尤其是对于取模的情况。
明日计划
明天比较忙,线段树往前赶一赶,CDQ把二维偏序和三维偏序的裸题写了。