小白月赛86
D.dfs
问图中有多少个长方形,正方形也算长方形,并且每个长方形都是当前区域最大的。
判断长方形可以计算连通块面积s,以及横坐标之差最大值mx,纵坐标之差最大值my,如果mx*my=s
则是长方形。
int xm[4]={1,0,-1,0};
int ym[4]={0,-1,0,1};
int mxx,mxy,mnx,mny,sum;
void dfs(int x,int y,int n,int m){
vis[x][y]=1;
sum+=1;
mxx=max(mxx,x);
mxy=max(mxy,y);
mnx=min(mnx,x);
mny=min(mny,y);
rep(i,0,3){
int xx=x+xm[i],yy=y+ym[i];
if(xx<1||xx>n||yy<1||yy>m||a[xx][yy]||vis[xx][yy])continue;
dfs(xx,yy,n,m);
}
}
void solve(void){
int n,m,ans=0;
cin>>n>>m;
rep(i,1,n)
rep(j,1,m){
char t;
cin>>t;
if(t=='*')a[i][j]=1;
}
rep(i,1,n)
rep(j,1,m){
if(a[i][j]==0&&vis[i][j]==0){
mxx=mnx=i;
mxy=mny=j;
sum=0;
dfs(i,j,n,m);
// cout<<i<<' '<<j<<'\n';
// cout<<mxx<<' '<<mnx<<' '<<mxy<<' '<<mny<<' '<<sum<<'\n';
// cout<<ans<<'\n'<<'\n';
if((mxx-mnx+1)*(mxy-mny+1)==sum)ans++;
}
}
cout<<ans<<'\n';
}
E.可口蛋糕
每个蛋糕都有饱腹值和美味值,选择一个连续区间内的蛋糕,使饱腹值大于W,美味值尽可能大。看题解似乎可以写滑窗,但是不会啊,还是遍历一侧边界+lower_bound好写
遍历左边界,对于每个左边界i,用Lower_bound找第一个使区间饱腹值大于等于W的右边界j,再在[j,n]里选一个右边界使得[i,j]内美味值最大。可以先进行一个前缀和预处理,那么寻找j就转化为在[j,n]里选一个j,使得pre[j]最大。
暴力寻找是O(n)的,显然不可以。但这个东西就是维护区间最大值,也可以先预处理,只不过是从后往前维护。
ll wpre[N],dpre[N],dmx[N];
void solve(void){
int n;
ll W,ans=-1e18;
cin>>n>>W;
rep(i,1,n)cin>>w[i],wpre[i]=wpre[i-1]+w[i];
rep(i,1,n)cin>>d[i],dpre[i]=dpre[i-1]+d[i];
dmx[n+1]=-1e15;
rep1(i,n,1)dmx[i]=max(dmx[i+1],dpre[i]);//预处理[i,n]内的dpre最大值
rep(i,1,n){
int j=lower_bound(wpre+1,wpre+1+n,wpre[i-1]+W)-wpre;//寻找j下界
ans=max(ans,dmx[j]-dpre[i-1]);//查表找到[j,n]内dpre最大值
}
cout<<ans;
}
F.set维护区间
对于给定数列内,所有升序且每次加一的序列[i,j],维护一个sum=sigma((j-i+1)^2),后续会有1e5次单点修改,每次修改后都要查询sum。
首先可以转化为差分数组,升序且每次加一转化成差分后等价于:第一个数随意,后面有一段连续的1。每次修改时,观察差分数组中目标位置的值,如果修改前为1,说明修改前是一个目标序列的一部分,修改后会把它断开,如果修改后为1,说明修改会把两段连上。
但只有差分数组,我们只能知道这次修改的性质,而不能知道修改后sum的变化,因为我们不知道修改影响的全1区间的首尾位置。对此我们可以再开一个set<pii>
,每个元素存区间的左右端点,每次修改时直接用lower_bound就能查找到受影响的的区间。
具体来说,如果修改前是1,修改点x位于一个目标区间(l,r)内,修改后会形成(l,x-1),(x,r)两个新区间,如果修改后是1,修改点x位于一个区间(x,r)内,(x,r)左侧的区间为(l,x-1),修改后会形成一个新区间(l,r)。每次修改后都更新set,差分数组以及sum
这种做法的巧妙之处在于所有区间的左端点l构成了一个升序序列,所有区间在set内也是升序排列的,因此用Lower_bound可以快速查到目标区间,并且返回的区间的前一个区间,就是该区间的左侧区间。
set<pii>s;
ll sum;
void add(int x,int y){//插入区间
s.insert({x,y});//更新set和sum
sum+=(y-x+1)*(y-x+1);
}
void del(int x,int y){
s.erase({x,y});
sum-=(y-x+1)*(y-x+1);
}
void update(int x,int w){//把区间更新变成两次单点更新
if(x==1||x==n+1||w==0)return;
//如果更新的是第一个位置或者尾部后一个,只会影响原始元素绝对大小
//不会影响相对大小,对要维护的升序序列无意义
dif[x]+=w;
if(dif[x]==1){//修改后为1,连上
auto t1=*s.lower_bound({x,-1});
auto t2=*prev(s.lower_bound({x,-1}));
int r=t1.second,l1=t2.first;
del(x,r);
del(l1,x-1);
add(l1,r);//x所在区间和前一个区间连上了
}
else if(dif[x]-w==1){//修改后为1,拆开
auto t=*prev(s.lower_bound({x,-1}));
int l=t.first,r=t.second;
del(l,r);
add(l,x-1);
add(x,r);
}
}
void solve(void){
cin>>n>>m;
rep(i,1,n){
cin>>a[i];
dif[i]=a[i]-a[i-1];
}
rep(i,1,n){//初始化
int j=i+1;
while(dif[j]==1&&j<=n)j++;
j--;
add(i,j);//将初始序列的目标区间插入
i=j;
}
rep(i,1,m){
int l,r,w;
cin>>l>>r>>w;//区间加转化为差分数组上两个单点修改
update(l,w);
update(r+1,-w);
cout<<sum<<'\n';
}
}