【Codeforces 627E】Orchestra

双指针

题目大意:

给定一个 $r\times c$ 的地图,其中有 $n$ 个格子上有棋子。给定 $k$,询问有多少子矩形包含至少 $k$ 个棋子。

题解:

枚举上边界,下边界从上往下移动。考虑计算下边界每往下移动一格,多出来的贡献。

考虑新的下边界上的一个棋子的纵坐标为 $x$,我们考虑所有包含 $x$ 的矩形,如果原来包含 $k-1$ 个棋子(现在恰好 $k$ 个),则会多出 $1$ 的贡献。

我们每加入一个棋子,把它左右各 $k$ 个棋子拿出来计算新增的矩形即可。

multiset 可以比较方便地维护,但效率较低,$O(r^2+rkn\log n)$。用链表可以获得非常高的效率,$O(r^2+rkn)$。

我写的是方便的 multiset 做法,实现时需要注意常数。

Code:

#pragma GCC optimize("Ofast")
#pragma GCC optimize("unroll-loops")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
#include<iostream>
#include<vector>
#include<set>
#include<cstring>
using namespace std;
const int N=3005;
vector<int>vec[N];
int n,m,k,p;
long long ans=0;
int xx[233],*wz=xx+50;
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n>>m>>k>>p;
    for(int i=1;i<=k;++i){
        int x,y;
        cin>>x>>y;
        vec[x].push_back(y);
    }
    for(int r0=1;r0<=n;++r0){
        multiset<int>st;
        int s=0;
        for(int r=r0;r<=n;++r){
            for(int x:vec[r]){
                auto it=st.insert(x),tmp=it;
                for(int c=1;c<=p;++c)
                wz[-c]=(tmp==st.begin()?0:*--tmp);
                tmp=it;
                for(int c=0;c<=p;++c)
                wz[c]=(tmp==st.end()?m+1:*tmp++);
                for(int i=1;i<=p;++i)
                s+=(wz[i]-wz[i-1])*(wz[i-p]-wz[i-p-1]);
            }
            ans+=s;
        }
    }
    cout<<ans<<'\n';
    return 0;
}