zoukankan      html  css  js  c++  java
  • bzoj2727: [HNOI2012]双十字

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=2727

    思路:

    先预处理出c[i],down[i]

    c[i]表示i点向两侧最多扩展多远(不包括自身,因为长度为1的横线显然是不合题意的)

    down[i]表示向下扩展多远(也不包括自身,因为"下端必须严格低于两条水平线段")

    这个可以通过O(R*C)的预处理求得


    首先我们枚举竖线,因为竖线只有一根

    然后考虑对竖线一个点i,它做下十字的中心时的方案数

    枚举i上面的点j做上十字的中心

    求出当前的top,表示最高能到的点的行号

    分情况讨论

    1.c[j]>c[i] 枚举下十字的长度len,因为c[j]>c[i],所以上十字一定有len-1种长度可取,上横线以上的竖线长度有(j-top)种,下横线一下的竖线长度有down[i]种

    ans=Σ(len=1...c[i]-1)*(j-top)*down[i]

    =c[i]*(c[i]-1)/2*(j-top)*down[i]


    2.c[j]<=c[i],这时上十字的长度不够了,我们可以拿总方案-不合法的方案

    总方案:c[i]*c[j]*(j-top)*(down[i])

    不合法的方案:c[j]*(c[j]+1)/2*(j-top)*(down[i])

    因为所有上十字长度大于等于下十字长度的都不合法,这时c[j]<=c[i],所以类似情况1,等差数列求和即可

    ans=(c[i]*c[j]-c[j]*(c[j]+1)/2)*(j-top)*(down[i])


    这时暴力得答案就有80分

    但这还不够,显然这是可以用树状数组优化的

    开3个树状数组,把式子中关于j的三个部分维护起来,下标就是c[i]

    t1维护(-c[j]*(c[j]+1)/2)*(j-top)

    t2维护c[j]*(j-top)

    t3维护(j-top)


    每次在树状数组里相应区间查即可。


    坑:"(事实上R*C可能稍大于原设定)"

    “两条水平的线段不能在相邻的两行”也就是树状数组不能做完i就插入i,而是插入i-1


    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    typedef long long ll;
    const int maxn=1300010,mod=1000000009,maxm=10010;
    using namespace std;
    int n,m,c[maxn],down[maxn],cnt;bool is[maxn];
    int p(int x,int y){return (x-1)*m+y;}
    struct Tbit{
    	int ord[maxm],tot;ll v[maxn];
    	void clear(){
    		for (int i=1;i<=tot;i++)
    			for (int j=ord[i];j<=m;j+=(j&(-j))) v[j]=0;
    		tot=0;
    	}
    	void add(int x,ll val){
    		ord[++tot]=x;
    		for (;x<=m;x+=(x&(-x))) v[x]=(v[x]+val)%mod;
    	}
    	ll query(int x){
    		ll res=0;
    		for (;x;x-=(x&(-x))) res=(res+v[x])%mod;
    		return res%mod;
    	}
    }t1,t2,t3;
    
    void init(){
    	scanf("%d%d%d",&n,&m,&cnt);
    	for (int i=1,x,y;i<=cnt;i++) scanf("%d%d",&x,&y),is[p(x,y)]=1;
    	
    	for (int i=1;i<=n;i++){
    		int now=0;
    		for (int j=1;j<=m;j++){
    			int t=p(i,j);
    			if (is[t]) now=j;
    			else c[t]=j-now-1;
    		}
    		now=m+1;
    		for (int j=m;j;j--){
    			int t=p(i,j);
    			if (is[t]) now=j;
    			else c[t]=min(c[t],now-j-1);
    		}
    	}
    	for (int i=n;i;i--){
    		for (int j=1;j<=m;j++){
    			int t=p(i,j);
    			if (is[t]) down[t]=-1;
    			else if (i==n) down[t]=0;
    			else down[t]=down[p(i+1,j)]+1;
    		}
    	}
    }
    
    //i在下,j在上
    //c[j]<=c[i] ans=(c[i]*c[j]-c[j]*(c[j]+1)/2)*(j-top)*down[i]
    //c[j]>c[i] ans=(c[i]-1)*c[i]/2*(j-top)*down[i]
    //t1 -c[j]*(c[j]+1)/2*(j-top)
    //t2 c[j]*(j-top)
    //t3 (top-j)
    void work(){
    	ll ans=0;
    	for (int j=1;j<=m;j++){
    		t1.clear(),t2.clear(),t3.clear();
    		int top=0;
    		for (int i=1;i<=n;i++){
    			int t=p(i,j);
    			if (is[t]){top=i;t1.clear(),t2.clear(),t3.clear();continue;}
    			ans+=t1.query(c[t])*down[t]%mod;
    			ans+=t2.query(c[t])*c[t]*down[t]%mod;
    			ans+=(t3.query(m)-t3.query(c[t]))*(c[t]-1)*c[t]/2*down[t]%mod;
    			ans%=mod,t=p(i-1,j);
    			if (i==1) continue;
    			if (c[t]){
    				t1.add(c[t],-1ll*c[t]*(c[t]+1)/2*(i-1-top-1));
    				t2.add(c[t],c[t]*(i-1-top-1));
    				t3.add(c[t],i-1-top-1);
    			}
    		}
    	}
    	printf("%lld
    ",ans);
    }
    
    int main(){
    	init(),work();
    	return 0;
    }




  • 相关阅读:
    算法面试准备(一)之----交叉熵与logistic回归推导
    Julia初学备忘
    二维数组中的查找,替换空格
    快慢指针倒数第K个节点,每K个一组反转链表
    贝叶斯网络之----(d-分离步骤)
    一笑
    尾曲
    ggplot在python中的使用(plotnine)
    SVC之SMO算法理解
    特征选取之IV(信息值)及python实现
  • 原文地址:https://www.cnblogs.com/thythy/p/5493482.html
Copyright © 2011-2022 走看看