Codeforces Round #229 (Div. 2) 解题报告

390 A. Inna and Alarm Clock

题目:http://codeforces.com/problemset/problem/390/A

题意比较简单,每次操作消除一整行或一整列的点,问最少的操作次数。

但是题目给了一个限制,所有操作要么全部都是消除一整行,要么都是消除一整列。

有这个限制就好处理了,求出不同横坐标的个数和不同纵坐标的个数,两者取较小者就是答案。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n, i, j, a, b, x[101], y[101];
int main(){
	while(~scanf("%d", &n)){
		memset(x,0,sizeof(x));
		memset(y,0,sizeof(y));
		while(n--){
			scanf("%d %d", &i, &j);
			x[i]++;
			y[j]++;
		}
		a=b=0;
		for(i=0; i<=100; i++){
			if(x[i])	a++;
			if(y[i])	b++;
		}
		printf("%d\n", min(a,b));
	}
	return 0;
}


390 B. Inna, Dima and Song

题目:http://codeforces.com/problemset/problem/390/B

题目给两个序列A和B,对于Bi,要找到一对xi和yi,使得xi+yi=Bi,并且1<=xi, yi<=Ai。

然后有一个值C,初始为0。

如果找到就把xi*yi加进去,否则就C-1。

问C最大能多少。

由xi和yi的条件能得到2<=xi+yi<=2*Ai,所以只要Bi能落在这个区间就能找到合适的xi和yi。

而要使xi*yi尽可能大,就得让xi和yi尽可能接近。所以令xi=[b/2],yi=bi-xi。

第一次交的时候忘记判断bi等于1的情况WA掉了,> <

#include<cstdio>
int n, a[100000], b, i, j;
long long ans, tmp1, tmp2;
int main(){
	while(~scanf("%d", &n)){
		ans=0;
		for(i=0; i<n; i++){
			scanf("%d", a+i);
		}
		for(i=0; i<n; i++){
			scanf("%d", &b);
			if(b==1){
				ans--;
				continue;
			}
			j=b>>1;
			b-=j;
			if(j>a[i] || b>a[i])	ans--;
			else{
				tmp1=j; tmp2=b;
				ans+=tmp1*tmp2;
			}
		}
		printf("%I64d\n", ans);
	}
	return 0;
}

390 C. Inna and Candy Boxes

题目:http://codeforces.com/problemset/problem/390/C

给定一个长度N的01串S,再给一个整数K。

然后有W个询问,每个询问给一对整数L和R,问对原串在[L,R]这个区间上要修改多少个位置使得在L+K-1,L+2K-1,L+3K-1,...,R都是1,而其它位置都是0。题目保证R-L+1能够被K整除。

对于一次询问,首先求出原串中对应区间上1的个数X,和落在指定位置上1的个数Y,以及指定位置的个数Z。

那么X-Y就是需要将1修改为0的个数,Z-Y就是需要将0修改为1的个数,两者相加就是当前询问的答案。

对于X的计算,因为每次询问都不会对原串做修改,所以可以用一个数组a[]预处理前缀和,a[i]代表[1,i]这个区间上1的个数,X=a[R]-a[L-1]。

Z的计算也很简单,等差数列求项数而已,Z=(R-L+1)/k。

剩下的就是Y的计算,因为K不超过10,而指定位置肯定是模k同余,所以可以预处理每个位置i,S[i]如果是0就忽略,如果是1,就在b[i%k][i]这个位置赋值为1,然后对于每个模的值也计算前缀和,对于询问,Y=b[r%k][r]-b[r%k][l-1]。

这样就能求出答案了。

#include<cstdio>
#include<cstring>
int n, k, w, i, j, l, r, a[100010], b[10][100010];
char s[100010];
int main(){
	while(~scanf("%d %d %d", &n, &k, &w)){
		scanf("%s", s);
		memset(b,0,sizeof(b));
		a[0]=0;
		for(i=1; i<=n; i++){
			if(s[i-1]=='0')	a[i]=0;
			else{
				a[i]=1;
				b[i%k][i]=1;
			}
		}
		for(i=2; i<=n; i++)	a[i]+=a[i-1];
		for(i=0; i<k; i++){
			for(j=2; j<=n; j++)	b[i][j]+=b[i][j-1];
		}
		while(w--){
			scanf("%d %d", &l, &r);
			i = a[r]-a[l-1];
			j = b[r%k][r]-b[r%k][l-1];
			l = (r-l+1)/k;
			printf("%d\n", i-j+l-j);
		}
	}
	return 0;
}

390 D. Inna and Sweet Matrix

题目:http://codeforces.com/problemset/problem/390/D

给一个N*M的矩阵,初始全部为空,然后要在上面放K(K<=M*N)个糖果,对于放糖果的位置(X,Y),必须将糖果从(1,1)位置开始移动过去,并且移动路径不能有其他糖果,每次移动的消耗就是路径长度。问放置所有糖果的最小消耗,放糖果的位置是任意的。

输出最小的消耗和K个糖果的移动路径。

要让消耗最小就必须让所有糖果的位置是距离(1,1)最近的K个位置。又因为放置的糖果不能影响到后面糖果的移动,所以在K个位置中越远的越要先放。

我采取的策略是从(1,1)开始BFS,每次向右走或者向下走,并且标记访问到的点,访问K个点的时候停止搜索。这样访问到的K个点就是可以放的并且消耗最小的。

然后从后面往前扫用于标记的数组,遇到一个访问的就输出路径,这样就能保证先放的点不会妨碍到后放的点。

#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct Point{
	int x, y, d;
	Point(){}
	Point(int _x, int _y, int _d){
		x=_x; y=_y; d=_d;
	}
}p;
int n,m,k,t,ans,i,j,a,b;
int xl[2]={0,1};
int yl[2]={1,0};
bool vis[51][51];
void print(int x, int y){
	for(int xx=1; xx<=y; xx++)	printf("(1,%d) ", xx);
	for(int xx=2; xx<=x; xx++)	printf("(%d,%d) ", xx, y);
	puts("");
}
int main(){
	while(~scanf("%d %d %d", &n, &m, &k)){
		memset(vis,0,sizeof(vis));
		ans=1;
		t=1;
		queue<Point> Q;
		Q.push(Point(1,1,1));
		vis[1][1]=1;
		while(t<k){
			p = Q.front(); Q.pop();
			for(i=0; i<2; i++){
				a=p.x+xl[i];
				b=p.y+yl[i];
				if(a>n || b>m)	continue;
				if(vis[a][b])	continue;
				vis[a][b]=1;
				ans+=p.d+1;
				t++;
				Q.push(Point(a,b,p.d+1));
				if(t>=k)	break;
			}
		}
		printf("%d\n", ans);
		for(i=n; i>0; i--){
			for(j=m; j>0; j--){
				if(vis[i][j]){
					print(i,j);
				}
			}
		}
	}
	return 0;
}

390 E. Inna and Large Sweet Matrix

题目:http://codeforces.com/problemset/problem/390/E

又是一个N*M的矩阵,初始全部为0;

有两种操作:

1、对于(x1,y1)到(x2,y2)这个矩形,所有值增加v;

2、求出(x1,y1)到(x2,y2)这个矩形内数字的和A,所有满足p<x1或p>x2并且q<y1或q>y2的点的和B,输出A-B;

咋一看像二维线段树,可是N和M可以达到4*6^10,好吧,放弃这个念头,换种思路。

举样例来说,对于第四个查询(1,2,3,3,4),可以得到下面的情况:


蓝色部分是我们要的A,绿色部分是B,把红色部分(蓝色也算进去)记为C,黄色部分(蓝色也算进去)记为D。

那么C就是当前矩阵所有满足x1<=x<=x2的点的和,D是所有满足y1<=y<=y2的点的和。

把当前矩阵内所有点的和记为sum,我们可以得到下面的等式:

C+D-A+B=sum,

整理可以得到:C+D-sum=A-B,而题目要我们求的正是A-B,所以问题可以转化成求C+D-sum。

对于sum,对于每次操作1,sum+=(x2-x1+1)*(y2-y1+1)*v,这个很好求;

而C,如果用S[i]来表示所有x=i的格子内数字的和,我们可以知道对于每个操作1,对于每个[x1,x2]上的i,S[i]+=(y2-y1+1)*v;

这样对于C的处理就变成一个区间更新和区间查询的问题了。

D也可以同样处理。

可是这题目N和M太大啦,比赛的时候开两个线段树就给MLE了。

记得以前看过可以用树状数组解决这种问题,可尼玛地不懂写啊= =

于是趁这机会学一下,虽然比赛的最后还是赶不上。。。

#include<cstdio>
#include<cstring>
#define LL long long
#define MAXN 4000001
int n, m, q, k, op, x1, x2, y1, y2, lim;
LL sum, s1[2][MAXN], s2[2][MAXN];
inline int lowbit(int x){
	return x&(-x);
}
void add(LL a[], int x, LL v){
	for(;x<=lim;x+=lowbit(x))	a[x]+=v;
}
void update(int l, int r, LL v){
	add(s1[k], l, v);
	add(s1[k], r+1, -v);
	add(s2[k], l, v*l);
	add(s2[k], r+1, -v*(r+1));
}
LL cal(LL a[], int x){
	LL re=0;
	for(;x;x-=lowbit(x))	re+=a[x];
	return re;
}
LL query(int l, int r){
	LL tmp1= (r+1)*cal(s1[k], r) - cal(s2[k], r);
	LL tmp2= l*cal(s1[k], l-1) - cal(s2[k], l-1);
	return tmp1-tmp2;
}
inline void GN(int& x){
	char c=getchar();
	x=0;
	while(c<48 || c>57)	c=getchar();
	while(c>=48 && c<=57){
		x=x*10+c-48;
		c=getchar();
	}
}
inline void GN(LL& x){
	char c=getchar();
	x=0;
	while(c<48 || c>57)	c=getchar();
	while(c>=48 && c<=57){
		x=x*10+c-48;
		c=getchar();
	}

}
int main(){
	LL v;
	while(~scanf("%d %d %d", &n, &m, &q)){
		sum=0;
		while(q--){
			GN(op); GN(x1); GN(y1); GN(x2); GN(y2);
			if(op){
				k=0; lim=n;
				LL tmp1=query(x1,x2);
				k=1; lim=m;
				LL tmp2=query(y1,y2);
				printf("%I64d\n", tmp1+tmp2-sum);
			}
			else{
				GN(v);
				LL tmp1=x2-x1+1;
				LL tmp2=y2-y1+1;
				sum+=tmp1*tmp2*v;
				k=0; lim=n;
				update(x1,x2,v*tmp2);
				k=1; lim=m;
				update(y1,y2,v*tmp1);
			}
		}
	}
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值