任(一道思维巧妙的矩形前缀和问题)

本文探讨了在信息学竞赛中,如何从暴力算法出发,逐步优化至更高效的解决方案。通过对矩阵前缀和的运用,实现了对连通块数量的快速计算,避免了大量重复计算,显著提升了算法效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本题一开始就没准备写正解。。。

暴力(70):先把前两个点,bfs出来就好(信息奥赛一本通,病毒)。

对于n = 1和n = 2的情况。莫队啊!

考虑加入一列对答案的影响。

如果n=2,先处理询问端点在同一行的,再处理不同行的。

#include<bits/stdc++.h>
using namespace std;
int m, n, qq, xm, xn, dn, dm, ans, an[200005],vis[2005][2005];
char s[2005][2005];
int zl[4][2] = {{0,1},{0,-1},{1,0},{-1,0}};
struct node
{
	int l, r, num;
};
node q[200005],q1[200005],q2[200005];
bool cmp(const node a,const node b)
{
	if(a.l == b.l) return a.r < b.r;
	return  a.l < b.l;
}
void read(int &x)
{
	x = 0; int f = 0; char c = getchar();
	while(c < '0' || c > '9')
    {
    	if(c == '-') f = 1; c = getchar();
    }
    while(c >= '0' &&  c <= '9')
    {
    	x = x *10  + c -'0'; c = getchar();
    }
    if(f) x = -x;
}
void dfs(int x,int y)
{
	vis[x][y] = 1;
	for(int i = 0; i <= 3; i++)
	{
		int xx = x + zl[i][0]; int yy = y + zl[i][1];
		if(xx >= xn && xx <= dn && yy >= xm && yy <= dm && vis[xx][yy] == 0 && s[xx][yy] == '1')
		dfs(xx,yy);
	}
}
void hehe(int shu,int hang, node q3[200005])
{
	        sort(q3+1,q3+1+shu,cmp);
		   int l = 0 ,r = 0; ans = 0;
		   for(int i = 1; i <= shu; i++)
		   {
		   	while(l < q3[i].l)
		   	{
		   		if(s[hang][l] == '1')
		   			if(s[hang][l+1] == '0') ans--;
		   	    l++; 
			   }
		   	while(r > q3[i].r)
		   	{
		   	   if(s[hang][r] == '1')
				  	if(s[hang][r-1] == '0') ans--;
		   	    r--;
			}
		   	while(r < q3[i].r)
		   	{
		   		r++;
		   	   if(s[hang][r] == '1')
				  		if(s[hang][r-1] == '0') ans++;
		   	}
		   	an[q3[i].num] = ans;
		   }
}
void he(int shu,node q3[200005])
{
	 sort(q3+1,q3+1+shu,cmp);
	 int l = 0 ,r = 0; ans = 0;
	  for(int i = 1; i <= shu; i++)
		   {
		   	while(l < q3[i].l)
		   	{
		   		if(s[1][l] == '1' && s[2][l] == '1')
		   		{if(s[1][l+1] == '0' && s[2][l+1] == '0') ans--;}	
		   	    else if(s[1][l] == '1' && s[2][l] == '0') {if(s[1][l+1] == '0')ans--; }
		   	    else if(s[1][l] == '0' && s[2][l] == '1') {if(s[2][l+1] == '0')ans--; }
				   l++; 
			   }
		   	while(r > q3[i].r)
		   	{
		   	     if(s[1][r] == '1' && s[2][r] == '1')
		   			{if(s[1][r-1] == '0' && s[2][r-1] == '0') ans--;}
		   	     else if(s[1][r] == '1' && s[2][r] == '0') {if(s[1][r-1] == '0')ans--;}
		   	     else if(s[1][r] == '0' && s[2][r] == '1') {if(s[2][r-1] == '0')ans--;}
		   	    r--;
			}
		   	while(r < q3[i].r)
		   	{
		   		r++;
		        if(s[1][r] == '1' && s[2][r] == '1')
		   			{if(s[1][r-1] == '0' && s[2][r-1] == '0') ans++;}
		   	     else if(s[1][r] == '1' && s[2][r] == '0') {if(s[1][r-1] == '0')ans++;}
		   	     else if(s[1][r] == '0' && s[2][r] == '1') {if(s[2][r-1] == '0')ans++;}
		   	}
		   	an[q3[i].num] = ans;
		   }
}
int main()
{
	freopen("duty.in","r",stdin);
	freopen("duty.out","w",stdout);
	read(n);read(m); read(qq);	
	for(int i = 1; i <= n; i++)
		scanf("%s",s[i]+1);
		int heheheh = strlen(s[1]);
	if(n == 1)
	{
		  s[1][0] = '0';
		   for(int e = 1; e <= qq; e++)
	       {
	          int x,y; read(x);read(q[e].l);read(y);read(q[e].r); q[e].num = e;
		   }
	     	hehe(qq,1,q);
		   for(int i = 1; i <= qq; i++)
		   printf("%d\n",an[i]);
	}
	else if(n == 2)
	{
	       s[2][0] = s[1][0] = '0';
	       int cnt = 0 , cnt1 = 0, cnt2 = 0;
		   for(int e = 1; e <= qq; e++)
	       {
	          int x,l,r,y; read(x);read(l);read(y);read(r); 
			  if(x == 1 && y == 1) {q[++cnt].l = l;q[cnt].num = e;q[cnt].r = r;}
			   else if(x == 2 && y == 2) {q1[++cnt1].l = l;q1[cnt1].num = e;q1[cnt1].r = r;}
			    else {q2[++cnt2].l = l;q2[cnt2].num = e;q2[cnt2].r = r;}
		   }
		   hehe(cnt,1,q);
		   hehe(cnt1,2,q1);
		   he(cnt2,q2);
		for(int i = 1; i <= qq; i++)
		   printf("%d\n",an[i]);
	}
	else
	{
	   for(int e = 1; e <= qq; e++)
	   {
	   	ans = 0;
	   	memset(vis,0,sizeof(vis));
	   	read(xn);read(xm);read(dn);read(dm);
	   	 for(int i = xn; i <= dn; i++)
	   	  for(int j = xm; j <= dm; j++)
	   	  	if(vis[i][j] == 0 && s[i][j] == '1')
	   	  	 {
	   	  	     ans++ ; dfs(i,j);	
	   	  	 }
	   	  printf("%d\n",ans);
	   }	
	}
	return 0;
} 

然而事实证明打暴力不如写正解。//150行的大暴力。。。然而我把l写成1了少了30分,泪流满面。。。。。

我们可以发现:一个 矩形区域内:连通块数 = 点数 - 边数//不存在环,两个颜色相同的方块联通只有一条简单路径。

所以我们利用矩阵前缀和。预处理出每个以(1,1)为左上端点,(i,j)为右下端点的矩形内的黑色块数和黑色块之间的边数。

然而如果截取出一个子矩形(红色部分),要切断它和它旁边的联系(蓝边)。

利用前缀和算出的是红色部分+3条蓝边的总共边数。这时要剪掉蓝边,一个pre记录所有的纵向边,一个pre1记录所有的横向边。

还是利用前缀和把蓝边搞出来。。。

#include<bits/stdc++.h>
using namespace std;
char s[2005][2005];
int dian[2005][2005], bian[2005][2005], pre[2005][2005], pre1[2005][2005], n, m, q;
void read(int &x)
{
	x = 0; int f = 0; char c = getchar();
	while(c < '0' || c > '9')
    {
    	if(c == '-') f = 1; c = getchar();
    }
    while(c >= '0' &&  c <= '9')
    {
    	x = x *10  + c -'0'; c = getchar();
    }
    if(f) x = -x;
}
int main()
{
	  freopen("duty.in","r",stdin);
  freopen("duty.out","w",stdout);
	read(n);read(m);read(q);
	for(int i = 1; i <= n; i++)
	scanf("%s",s[i]+1);
	for(int i = 1; i <= n; i++)
	 for(int j = 1; j <= m; j++)
	  {
	  	if(s[i][j] == '1')
		  {
		  	dian[i][j]++;
		  	if(s[i][j-1] == '1') bian[i][j]++;
		  	if(s[i-1][j] == '1') bian[i][j]++;
		  } 
	  	dian[i][j] = 	dian[i][j] + dian[i-1][j] + dian[i][j-1] - dian[i-1][j-1];
	    bian[i][j] =  bian[i][j] + bian[i-1][j] + bian[i][j-1] - bian[i-1][j-1];
 	  }
 	for(int i = 1; i <= n; i ++)
 		for(int j = 1; j <= m; j++)
 		{
 			 if(s[i][j] == '1' && s[i][j-1] == '1') pre1[i][j]++;
			 if(s[i][j] == '1' && s[i-1][j] == '1') pre[i][j]++;
 		    pre[i][j] = pre[i][j] + pre[i-1][j] + pre[i][j-1] - pre[i - 1][j -1];
 		    pre1[i][j] = pre1[i][j] + pre1[i-1][j] + pre1[i][j-1] - pre1[i - 1][j -1];
	   }
 	for(int i = 1; i <= q; i++)
 	{
 		int x1,x2,y1,y2;
 		read(x1);read(y1);read(x2);read(y2);
 		int di  = dian[x2][y2] - dian[x2][y1-1] - dian[x1-1][y2] + dian[x1-1][y1-1];
 		int bi =  bian[x2][y2] - bian[x2][y1-1] - bian[x1-1][y2] + bian[x1-1][y1-1];
		int duobian = pre[x1][y2] - pre[x1-1][y2] - pre[x1][y1 - 1] + pre[x1-1][y1-1];
		duobian = duobian + pre1[x2][y1] - pre1[x2][y1-1] - pre1[x1-1][y1] + pre1[x1-1][y1-1]; 
		 int ans = di - (bi - duobian);
		 printf("%d\n", ans);
 	}
 	return 0;
} 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值