2023NOIP A层联测17 爆炸

题目大意

有一个 n × m n\times m n×m的网格图,其中一些格子上有一个炸弹,一些格子上有一个水晶,剩下的格子为空地。

炸弹可以引爆,每当一个炸弹被引爆,它必须选择:引爆它所在的这一行或者引爆它所在的这一列。水晶被引爆后会消失(即如果一个水晶所在的行列均被引爆,也只计算一次),炸弹被引爆后,会产生连锁反应,被引爆的炸弹也会选择引爆它所在的行或者列,如此下去直到没有新的炸弹被引爆。

现在你可以选择一个炸弹和其引爆的方向,请你求出最多可以引爆多少个水晶。

k k k个水晶, b b b个炸弹。

1 ≤ n , m ≤ 3000 , 0 ≤ k + b ≤ n m 1\leq n,m\leq 3000,0\leq k+b\leq nm 1n,m3000,0k+bnm

时间限制 3000 m s 3000ms 3000ms,空间限制 512 M B 512MB 512MB


题解

一个炸弹引爆之后,它的方向是固定的,如果被竖着引爆,那么应该选择横着引爆,否则选择竖着引爆。

那么,我们把每一行和每一列都看作一个点,则总共有 n + m n+m n+m个点。对于每个炸弹,设炸弹的位置为 ( x , y ) (x,y) (x,y),那么就将第 x x x行对应的点和第 y y y列对应的点连边,表示第 x x x行被炸到时,通过这个炸弹可以炸到第 y y y列。

建好了图,引爆一个炸弹就相当于遍历这个炸弹所在的行或列对应的点所在的连通块,对于所有能遍历到的点,判断这些点对应的行和列总共能涉及到哪些水晶。

当然,我们发现这种做法有一些问题,因为被引爆的炸弹的行和列对应的点是被这个炸弹作为边连接的,所以我们引爆炸弹实际上是将这个点所在的行和列都引爆了。下面,我们考虑如何减去多计算的贡献。

  • 如果一个炸弹在这一行爆炸之后可以通过若干次引爆再炸到自己这一行(也就是图上的一个点被遍历多次,等价于图上存在环),那么我们可以将引爆的炸弹设为这一行上的一个炸弹,将这个炸弹所在列引爆,那么这一行在最终还是会被引爆的,所以并没有多算贡献
  • 如果一个炸弹在这一列爆炸之后可以通过若干次引爆再炸到自己这一列,与上面相同,也没有多算贡献
  • 如果上面两个条件都不满足,也就是说这个图存在至少两个点的度为 1 1 1(这个图没有环,也就是是一棵树,而且点的数量大于等于 2 2 2,由边的总数为 n − 1 n-1 n1可得点的度的总和为 2 n − 2 2n-2 2n2,一定存在至少两个点的度为 1 1 1),则这些度为 1 1 1的点对应的行或列上就没有别的炸弹,那我们可以不选这些度为 1 1 1的点对应的行或列,对于行或列上的每个水晶,判断如果没有这次引爆这个水晶是否还会消失,用上面算出的值减去原本会消失而去掉这一行或列后不会消失的水晶数量来更新答案即可

对于第三种情况,如果引爆对应的行或列更优(也就是只得到这一行或列的水晶比连锁反应所能得到的水晶更多),那这部分的贡献怎么没有体现呢?

因为度为 1 1 1的点的数量大于等于 2 2 2,所以假如引爆度为 1 1 1的点 u u u对应的行或列会更优,则在不引爆另一个度为 1 1 1的点 v v vd对应的行或列时,点 u u u对应的行或列是会被引爆的,那它对答案的贡献就体现出来了。

所以,这样做是可行的。

那如何求一个原本会消失的水晶在去掉一行或一列之后是否还会消失呢?我们只需要记录每个水晶被引爆的次数,之后在去掉一行或一列时,遍历这一行或这一列上的每个水晶,如果水晶只被引爆了一次,则这个水晶就不会消失;否则,这个水晶还会消失。

时间复杂度为 O ( ( n + m ) 2 ) O((n+m)^2) O((n+m)2)

code

#include
using namespace std;
const int N=3000;
int n,m,K,B,tot=0,d[2*N*N+5],l[2*N*N+5],r[2*N+5];
int cl=0,fl,now,mx,ans=0,wt[2*N+5],cm[2*N+5],z[N+5][N+5],hv[N+5][N+5];
char ch;
vector<int>v[2*N+5];
void add(int xx,int yy){
	l[++tot]=r[xx];d[tot]=yy;r[xx]=tot;
}
void dfs1(int u,int fa){
	cm[u]=1;
	if(u<=n){
		for(int i=0;i<v[u].size();i++){
			if(z[u][v[u][i]]!=cl){
				z[u][v[u][i]]=cl;
				hv[u][v[u][i]]=0;
				++now;
			}
			++hv[u][v[u][i]];
		}
	}
	else{
		for(int i=0;i<v[u].size();i++){
			if(z[v[u][i]][u-n]!=cl){
				z[v[u][i]][u-n]=cl;
				hv[v[u][i]][u-n]=0;
				++now;
			}
			++hv[v[u][i]][u-n];
		}
	}
	for(int i=r[u];i;i=l[i]){
		if(cm[d[i]]){
			if(d[i]!=fa) fl=1;
			continue;
		}
		dfs1(d[i],u);
	}
}
void dfs2(int u){
	cm[u]=2;
	if(u<=n&&wt[u]==1){
		int tmp=now;
		for(int i=0;i<v[u].size();i++){
			if(hv[u][v[u][i]]==1) --tmp;
		}
		mx=max(mx,tmp);
	}
	else if(u>n&&wt[u]==1){
		int tmp=now;
		for(int i=0;i<v[u].size();i++){
			if(hv[v[u][i]][u-n]==1) --tmp;
		}
		mx=max(mx,tmp);
	}
	for(int i=r[u];i;i=l[i]){
		if(cm[d[i]]==2) continue;
		dfs2(d[i]);
	}
}
int main()
{
//	freopen("boom.in","r",stdin);
//	freopen("boom.out","w",stdout);
	scanf("%d%d%d%d",&n,&m,&K,&B);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			ch=getchar();
			while(ch!='.'&&ch!='k'&&ch!='b') ch=getchar();
			if(ch=='b'){
				add(i,n+j);add(n+j,i);
				++wt[i];++wt[n+j];
			}
			else if(ch=='k'){
				v[i].push_back(j);
				v[n+j].push_back(i);
			}
		}
	}
	for(int i=1;i<=n+m;i++){
		if(wt[i]&&!cm[i]){
			fl=0;now=0;++cl;
			dfs1(i,0);
			mx=0;dfs2(i);
			if(fl) ans=max(ans,now);
			else ans=max(ans,mx);
		}
	}
	printf("%d",ans);
	return 0;
}

你可能感兴趣的:(题解,好题,题解,c++,1024程序员节)