Codeforces Round #243 425B 425D

十分有趣的两道题目,425D据说是非常经典的题目

425B:

给出一个n*m的01矩阵(100*100),和一个常数k(k<=10),
要求改变尽量少的元素,使得每个01块的的极大填充块都是矩形,
例如,
0 1 0
0 1 0
1 1 1
0 1 0
0 1 0
不满足这个条件,因为1的极大填充块不是矩形,而
0 1 0
0 1 0
1 0 1
0 1 0
0 1 0
满足这个条件。
如果要求改变的最少元素大于k,那么输出-1,否则输出这个答案


思路1:
如果满足最终条件,对于每个一个元素a[i][j],
[a[i][j]   a[i][j+1]  ]
[a[i+1][j] a[i+1][j+1]]
这四个元素有0,2,4个0。
并且,k最大为10,根据这个性质,只需要找出所有这样的不满足的元素,
然后进行深搜即可。

/**
 * @date    2014-05-07 16:21:44
 * @author  jasison
 * @email   jasison27@gmail.com
 * @website http://www.jiangshan27.com
 */

#include <cstdio>
#include <algorithm>
#include <vector>
#include <set>
using namespace std;
typedef pair<int,int> pii;
const int N = 105;

int n,m,k,ans;
int matrix[N][N];
set<pii>fault;
bool check(int x,int y){
	if(x>=n-1 || y>=m-1 || x<0 || y<0) {
		return true;
	}
	int cnt=0;
	for(int i=0;i<2;++i){
		for(int j=0;j<2;++j){
			cnt+=(matrix[x+i][y+j]==1);
		}
	}
	return !(cnt&1);
}
void calc(int d){
	if(fault.size()==0){
		ans=min(ans,d);
		return;
	}
	if(d>=k){
		return;
	}
	pii p=*fault.begin();
	for(int i=0;i<2;++i){
		for(int j=0;j<2;++j){
			matrix[p.first+i][p.second+j] ^= 1;
			vector<pii>added,deleted;
			for(int l=-1;l<1;++l){
				for(int m=-1;m<1;++m){
					if( check(p.first+i+l, p.second+j+m) && fault.find(pii(p.first+i+l, p.second+j+m))!=fault.end() ) {
						fault.erase(pii(p.first+i+l, p.second+j+m));
						deleted.push_back(pii(p.first+i+l, p.second+j+m));
					} else if ( !check(p.first+i+l, p.second+j+m) && fault.find(pii(p.first+i+l, p.second+j+m))==fault.end() ) {
						fault.insert(pii(p.first+i+l, p.second+j+m));
						added.push_back(pii(p.first+i+l, p.second+j+m));
					}
				}
			}
			calc(d+1);
			matrix[p.first+i][p.second+j] ^= 1;
			for(int l=0;l<added.size();++l){
				fault.erase(added[l]);
			}
			for(int l=0;l<deleted.size();++l){
				fault.insert(deleted[l]);
			}
		}
	}
}
int main() {
	while(scanf("%d%d%d",&n,&m,&k)!=EOF){
		for(int i=0;i<n;++i){
			for(int j=0;j<m;++j){
				scanf("%d",&matrix[i][j]);
			}
		}
		fault.clear();
		ans=N;
		for(int i=0;i<n;++i){
			for(int j=0;j<m;++j){
				if(!check(i,j)){
					fault.insert(pii(i,j));
				}
			}
		}
		calc(0);
		if(ans>k){
			ans=-1;
		}
		printf("%d\n",ans);
	}
	return 0;
}

思路2:
经过更加深入的观察,可以发现,如果满足最终条件,那么对于任意两行
col[i] = [a[0][i], a[1][i], ... , a[n-1][i] ]
col[j] = [a[0][j], a[1][j], ... , a[n-1][j] ]
有col[i] ^ col[j] = [0,0,..,0]或者[1,1,..,1]
根据这个性质,我们将矩阵转换成n<m的n*m矩阵
如果n<=k,那么可以枚举所有可能的列的掩码(0~(1<<n)-1),
然后计算需要改变的最少元素个数,即可。
如果n>k,那么一定有其中一列会作为标准列。
(否则每一列至少改变一个元素,使得答案大于k)
这样,我们就枚举标准列,得到最优答案。

/**
 * @date    2014-05-09 02:43:08
 * @author  jasison
 * @email   jasison27@gmail.com
 * @website http://www.jiangshan27.com
 */

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105;
int n,m,k;
int a[N][N],b[N][N];
int main() {
	while(scanf("%d%d%d",&n,&m,&k)!=EOF){
		int ele;
		for(int i=0;i<n;++i){
			for(int j=0;j<m;++j){
				scanf("%d",&ele);
				a[i][j]=ele;
				b[j][i]=ele;
			}
		}
		if(n>m){
			memcpy(a,b,sizeof(a));
			int t=n;
			n=m;
			m=t;
		}
		int cost=N;
		if(n<=k){
			for(int mask=(1<<n)-1;mask>=0;--mask){
				int cst=0;
				for(int j=0;j<m;++j){
					int cnt=0;
					for(int i=0;i<n;++i){
						if( (mask&(1<<i)?1:0 ) ^ a[i][j]){
							cnt++;
						}
					}
					cst+=min(cnt,n-cnt);
				}
				cost=min(cost,cst);
			}
		}else{
			for(int fix=0;fix<m;++fix){
				int cst=0;
				for(int j=0;j<m;++j){
					int cnt=0;
					for(int i=0;i<n;++i){
						if(a[i][j]^a[i][fix]){
							cnt++;
						}
					}
					cst+=min(cnt,n-cnt);
				}
				cost=min(cost,cst);
			}
		}
		if(cost>k){
			cost=-1;
		}
		printf("%d\n",cost);
	}
}

425D:

给出n个点(x,y),
1<=n<=100000,0<=x<=100000,0<=y<=100000
问这n个点能够组成多少个正方形?(每个点能够重复使用)


思路:
如果枚举每一个顶点作为右上角的顶点,
然后枚举同一行的顶点作为左上角的顶点,
那么可以其他两个顶点已经可以确定,二分搜索即可。
但是,同一行的顶点会有n个,
同理,同一列的顶点也会有n个,
这样做,时间复杂度是O(n*n*2*log(n))会超时。
然后考虑,可以枚举同行和同列中较少元素的那一行或者同一列,
这样做最终的复杂度会是O(n*sqrt(n)*2*log(n))。
但是,不知道怎样证明时间复杂度的正确性。

/**
 * @date	2014-05-09 01:35:21
 * @author  jasison
 * @email   jasison27@gmail.com
 * @website http://www.jiangshan27.com
 */

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100000;
vector<int>sx[N+5],sy[N+5];
vector<int>::iterator jt;
int vx[N+5],vy[N+5];
int n,ans;
int main() {
	while(scanf("%d",&n)!=EOF){
		for(int i=0;i<=N;++i){
			sx[i].clear();
			sy[i].clear();
		}
		int rx,ry;
		for(int i=0;i<n;++i){
			scanf("%d%d",&rx,&ry);
			vx[i] = rx;
			vy[i] = ry;
			sx[ry].push_back(rx);
			sy[rx].push_back(ry);
		}
		for(int i=0;i<=N;++i){
			sort(sx[i].begin(),sx[i].end());
			sort(sy[i].begin(),sy[i].end());
		}
		ans=0;
		for(int i=0;i<n;++i){
			int cnt1=lower_bound(sx[vy[i]].begin(),sx[vy[i]].end(),vx[i]) - sx[vy[i]].begin();
			int cnt2=lower_bound(sy[vx[i]].begin(),sy[vx[i]].end(),vy[i]) - sy[vx[i]].begin();
			if(cnt1>cnt2){
				for(jt=sy[vx[i]].begin(); *jt<vy[i]; ++jt){
					int len=vy[i]-(*jt);
					if(vx[i]-len>=0 && binary_search(sy[vx[i]-len].begin(),sy[vx[i]-len].end(),vy[i]) && binary_search(sy[vx[i]-len].begin(),sy[vx[i]-len].end(),*jt)){
						ans++;
					}
				}
			}else{
				for(jt=sx[vy[i]].begin(); *jt<vx[i]; ++jt){
					int len=vx[i]-(*jt);
					if(vy[i]-len>=0 && binary_search(sx[vy[i]-len].begin(),sx[vy[i]-len].end(),vx[i]) && binary_search(sx[vy[i]-len].begin(),sx[vy[i]-len].end(),*jt)){
						ans++;
					}
				}
			}
		}
		printf("%d\n",ans);
	}
}


官方题解?:
先枚举每一列,如果该列元素少于等于sqrt(n),
那么就枚举这一列每两个顶点作为顶点并且标记这一列的所有元素。
完成之后,将所有标记的顶点删除,再对每一行再做一遍(此时每一行的顶点个数已经少于等于sqrt(n))。

写完了WA,主要是在标记的时候没有处理好吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值