[bzoj2658]小蓝的好友

题目大意

求一个n*m网格里有多少矩阵包含至少一个黑格子。
数据保证随机

Treap

可以想到求有多少矩阵不包含任何黑格子。
直接跑单调栈会TLE。
我们可以针对某一行,每个格子求出其最长上升的距离,得到一个数组up。
然后,对up数组进行笛卡尔树建立。
那么,我们知道对于笛卡尔树中任意一个结点x,其对答案贡献为 (h[x]h[fa[x]])size[x](size[x]+1)/2
我们记sum[x]表示以x为根的子树中所有结点对答案的贡献(实现中由于不保存父亲指针,所以不包含x对答案的贡献)
这样一行的答案可以算出了。
从上往下做,每次相当于全部格子的值+1,同时有一些格子值变为0。
treap就是一种笛卡尔树,用treap维护即可。

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int maxn=40000+10,maxq=100000+10;
int i,j,k,l,t,n,m,p,top,tot,root;
int h[maxn],go[maxq],next[maxq];
int key[maxn],tree[maxn][2],size[maxn],ad[maxn];
ll ans,now,sum[maxn];
int count(int x){
    return x*(x+1)/2;
}
void add(int x,int y){
    go[++tot]=y;
    next[tot]=h[x];
    h[x]=tot;
}
void mark(int x,int y){
    key[x]+=y;
    ad[x]+=y;
}
void down(int x){
    if (ad[x]){
        if (tree[x][0]) mark(tree[x][0],ad[x]);
        if (tree[x][1]) mark(tree[x][1],ad[x]);
        ad[x]=0;
    }
}
void update(int x){
    size[x]=1;sum[x]=0;
    if (tree[x][0]){
        size[x]+=size[tree[x][0]];
        sum[x]+=sum[tree[x][0]]+(ll)count(size[tree[x][0]])*(key[tree[x][0]]-key[x]);
    }
    if (tree[x][1]){
        size[x]+=size[tree[x][1]];
        sum[x]+=sum[tree[x][1]]+(ll)count(size[tree[x][1]])*(key[tree[x][1]]-key[x]);
    }
}
void rotate(int &x,int z){
    int y=tree[x][z];
    tree[x][z]=tree[y][1-z];
    tree[y][1-z]=x;
    update(x);
    update(y);
    x=y;
}
void change(int &x,int y){
    down(x);
    if (y==size[tree[x][0]]+1){
        key[x]=0;
        update(x);
        return;
    }
    else if (y<=size[tree[x][0]]){
        change(tree[x][0],y);
        rotate(x,0);
    }
    else{
        change(tree[x][1],y-size[tree[x][0]]-1);
        rotate(x,1);
    }
}
void build(int &x,int l,int r){
    if (l>r){
        x=0;
        return;
    }
    x=(l+r)/2;
    build(tree[x][0],l,x-1);
    build(tree[x][1],x+1,r);
    update(x);
}
int main(){
    scanf("%d%d%d",&n,&m,&p);
    fo(i,1,p){
        scanf("%d%d",&j,&k);
        add(j,k);
    }
    ans=(ll)count(n)*count(m);
    build(root,1,m);
    fo(i,1,n){
        mark(root,1);
        t=h[i];
        while (t){
            change(root,go[t]);
            t=next[t];
        }
        ans-=sum[root]+(ll)count(size[root])*key[root];
    }
    printf("%lld\n",ans);
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值