首页 > 代码库 > CF 372B Counting Rectangles is Fun [dp+数据维护]
CF 372B Counting Rectangles is Fun [dp+数据维护]
题意,给出一个n行m列的矩阵
里面元素是0或者1
给出q个询问
a,b,c,d
求(a,b)到(c,d)有多少个由0组成的矩形
我们定义
即为求(a,b)到(c,d)有多少个由0组成的矩形
对一个矩形来说
dp[a][b][c][d]=dp[a][b][c][d-1]+dp[a][b][c-1][d]-dp[a][b][c-1][d-1]+包含右下角(当前点)的矩形;
重点就在包含右下角(当前点c,d)的矩形,如何计算这个
我们可以暴力扫描,需要nm的复杂度,乘上原有复杂度,,,已经会超过时限
也可以用一个VECTOR来记录1的位置,对我们扫描到的行,二分查找G[该行]中1的位置(在第b列到第d列间第一个1),然后根据相对位置再来计算,最坏查找次复杂度为
也可以状压整张图
我们要知道的是b列到d列中1的情况,我们把代表该行状态的数s先左位移再右位移到只剩b列到d列的状态
然后作运算s&(-s)返回第一个1的位置比如,如果s是0101000,则返回8
为什么能这样?来看下是怎么回事
还是以s=0101000为例
负数即正数的补码,-s即是1010111+1=1011000
0101000
1011000 &
0001000
但是这里
会返回2^40
但是没关系,我们对返回的数模997(经测验没有地址冲突)
这样就可以把它丢到表里
vis[2^1]=1
vis[2^2]=2
vis[2^3]=3...
vis[2^40]=40
这样就可以得到当前行中b到d列里从右往左第一个1的相对于第d列的位置
算下复杂度,全是几个O(1)的操作,大致为O(1)
说下我觉得最厉害最省事的方法
CF上最短size的代码
是这么做的
记录每个格子距离上一个1的距离d[i][j]
在找包含右下角(当前点c,d)的矩形的时候,我们取adder=min(记录数d[i][d],adder)(adder一开始是d-b+1),每次加上这个adder,从c行到a行(d[c][d]~d[a][d]),一直维护这个adder
O(1)且非常方便
#include <cstdio> #include <cstring> #include <cmath> #include <iostream> #include <algorithm> using namespace std; int g[55][55]; int num[55][55]; int dp[55][55][55][55]; int main(){ #ifndef ONLINE_JUDGE freopen("/home/rainto96/in.txt","r",stdin); #endif int n,m,q; cin>>n>>m>>q; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){ scanf("%1d",&g[i][j]); num[i][j]=num[i][j-1]+1; if(g[i][j]==1) num[i][j]=0; } for(int a=1;a<=n;a++) for(int b=1;b<=m;b++) for(int c=a;c<=n;c++) for(int d=b;d<=m;d++){ int& now=dp[a][b][c][d]=dp[a][b][c][d-1]+dp[a][b][c-1][d]-dp[a][b][c-1][d-1]; int add=d-b+1; for(int i=c;i>=a;i--){ add=min(add,num[i][d]); now+=add; } } for(int i=1;i<=q;i++){ int a,b,c,d; cin>>a>>b>>c>>d; cout<<dp[a][b][c][d]<<endl; } }
CF 372B Counting Rectangles is Fun [dp+数据维护]