Comet OJ - Contest #4 A-E

题目链接:https://cometoj.com/contest/39/problems

 

A.骚动时节的少女们

水题略。

B.奇偶性

1.当k是奇数时,所有数都是奇数,所以直接输出r-l+1.

2.当k是偶数时,显然是偶数的位置是p = x*(k+1) - 1,x = 1,2,3,4....,所以对于这种情况来说只要多做个除法就好了 

#include 
using namespace std;
typedef long long ll;
const int mx = 1e5 + 10;
int a[10]; 
int main(){
	int t;scanf("%d",&t);
	while(t--){
		ll l,r,k;
		scanf("%lld%lld%lld",&l,&r,&k);
		if(k&1) printf("%lld\n",r-l+1);
		else{
			ll c =  (r+1)/(k+1) - (l)/(k+1);
			printf("%lld\n",r-l+1-c);
		}
	}
	return 0;
}

C.方块切割

假设矩形有w块非障碍物要被平分,矩形被分成了x块,那么显然x肯定要整除于w。

并且我们可以从大到小枚举横线切割的数量,那么肯定第一个满足条件的肯定是字典序最小的,因为横线越多将矩形平分的越多,第一个出现的横线下标就越小。

还有一个简单的证明结论就是当我们确定了横线和竖线的切割数时,此时切割方案肯定是唯一的,不妨将竖线抛开,我们只看横线,如果每一行都至少有一个非障碍物,那么很明显横线切割方法唯一,如果存在某些行都是障碍物,那么为了字典序小,所以肯定将它放在后面。所以切割方案肯定唯一。对于竖线也是如此。

当我们分完切割线后,就要去查看每个块的非障碍物数量是否都相等就可以了,题目给的只能一维,但是我们可以用一维数组指针模拟二维。

#include 
using namespace std;
typedef long long ll;
const int mx = 2e6 + 10;
int *a[mx],pr[mx],pc[mx];
int *f[mx],b[mx];
int n,m,ret[mx];
int q1[2010],q2[2010];
int is,sum[mx];
char s[mx];
bool vis[mx];
int get(int x1,int y1,int x2,int y2){
	x1++,y1++;
	return f[x2][y2] - f[x1-1][y2] - f[x2][y1-1] + f[x1-1][y1-1];
}
bool check(int x,int y,int siz){
	int r = siz / (x+1),id;
	int c = siz / (y+1),now,w = siz/(x+1)/(y+1);
	int hd1 = 0,hd2 = 0;
	id = now = is = 0;
	for(int i=1;i<=n;i++){
		now += pr[i];
		if(id==x) continue;
		if(now==r){
			now = 0,id++;
			ret[is++] = i,q1[++hd1] = i;
		}
	}
	if(id!=x||now!=r) return 0;
	id = now = 0;
	for(int i=1;i<=m;i++){
		now += pc[i];
		if(id==y) continue;
		if(now==c){
			now = 0,id++;
			ret[is++] = i+n-1,q2[++hd2] = i;
		}
	}
	ret[is++] = 0;
	if(id!=y||now!=c) return 0;
	q1[++hd1] = n,q2[++hd2] = m;
	for(int i=1;i<=hd1;i++){
		for(int j=1;j<=hd2;j++){
			if(get(q1[i-1],q2[j-1],q1[i],q2[j])!=w)
			return 0;
		}
	}
	return 1; 
}
int main(){
	int t,k;scanf("%d",&t);
	while(t--){
		int c = 0,*id = sum,*ip = b;
		scanf("%d%d%d",&n,&m,&k);
		for(int i=0;i<=n;i++){
			f[i] = id,a[i] = ip;
			id += m+1,ip += m+1;
			for(int j=0;j<=m;j++)
			f[i][j] = 0;
		}
		for(int i=0;i<=n;i++) pr[i] = 0;
		for(int i=0;i<=m;i++) pc[i] = 0;
		for(int i=1;i<=n;i++){
			scanf("%s",s+1);
			for(int j=1;j<=m;j++){
				a[i][j] = !(s[j] - '0');
				pr[i] += a[i][j];
				pc[j] += a[i][j];
				c += a[i][j];
			}
		}
		for(int i=1;i<=n;i++)
			for(int j=1;j<=m;j++)
			f[i][j] = a[i][j] + f[i-1][j] + f[i][j-1] - f[i-1][j-1];
		bool ok = 0;
		for(int i=k;i>=0;i--){
			if(c%((i+1)*(k-i+1))==0&&i

D.求和

没做,不过看起来应该不难

E.公共子序列

序列中值包含了123,想这种情况一般肯定是先枚举其中一个,然后剩下两个怎么办呢,总不能O(n^2)解决吧。

首先我们枚举最长公共子序列的前缀1的个数x,那么对于a和b序列还说肯定是前缀干好有x个1的位置,我们设为la,lb。

此时我们再去设定一个右边界ra和rb,使得[ra,n]和[rb,m]的区间中3的个数刚好为相同,然后在[la.ra]和[lb,rb]中的连续2的个数中取最小,然后答案就是x+z+min(s[la,ra],s[lb,rb]),但是我们要枚举z吗?我们再定义a和b的前缀2的个数的数组pa,pb。

假设s[la.ra]<=s[lb,rb],那么式子可以写为:

x - pa[la] + z + pa[ra](应该是pa[la-1]的,但是la位置肯定是1,所以pa[la]肯定等于pa[la-1])

由于la和x是关联的,z和ra也是关联的,而他们两者确实无关的,所以我们在枚举x的时候没有用一些数据结构去维护z+pa[ra]的最大值,由于pa[ra]-pa[la] <= pb[rb] - pb[lb]也就是pb[lb] - pa[la] <= pb[rb] - pa[ra],所以我们可以将pb[rb] - pa[ra]看做key, z + pa[ra]看做value,维护一个后缀最大值,然后用pb[lb] - pa[la]查询,因为x从大到小枚举,所以肯定保证key的位置在x值位置的后面。

当s[la.ra]>s[lb,rb]的情况类似。

#include 
using namespace std;
const int mx = 1e6 + 10;
int n,m,pa[mx],pb[mx];
int a[mx],b[mx];
int L1[mx],L2[mx],R1[mx],R2[mx];
int aL,aR,bL,bR,tot;
int Ma[mx<<2],Mb[mx<<2];
void add(int x,int *p,int v){
	x += tot / 2;
	while(x){
		p[x] = max(p[x],v);
		x -= x & -x;
	}
}
int getans(int x,int *p){
	int ans = 0;
	x += tot / 2;
	while(x<=tot){
		ans = max(ans,p[x]);
		x += x & -x; 
	}
	return ans;
}
int solve(){
	tot = max(n,m)*2 + 10;
	for(int i=1;i<=tot;i++) Ma[i] = Mb[i] = 0;
	int ans = 0;
	int now = 0,L = min(aL,bL),R = min(aR,bR);
	for(int i=L;i>=0;i--){
		int l1 = L1[i],l2 = L2[i];
		while(now<=R&&R1[now]>l1&&R2[now]>l2)
		{
			int r1 = R1[now],r2 = R2[now];
			add(pb[r2]-pa[r1],Ma,now+pa[r1]);
			add(pa[r1]-pb[r2],Mb,now+pb[r2]);
			now++;
		}
		ans = max(ans,getans(pb[l2]-pa[l1],Ma)+i-pa[l1]);
		ans = max(ans,getans(pa[l1]-pb[l2],Mb)+i-pb[l2]);
	}
	return ans;
}
int main(){
	int t;scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		aL = aR = bL = bR = 0;
		for(int i=1;i<=n;i++) scanf("%d",a+i);
		for(int i=1;i<=m;i++) scanf("%d",b+i);
		for(int i=1;i<=n;i++){
			pa[i] = pa[i-1] + (a[i]==2);
			if(a[i]==1) L1[++aL] = i;
		}
		for(int i=1;i<=m;i++){
			pb[i] = pb[i-1] + (b[i]==2);
			if(b[i]==1) L2[++bL] = i;
		}
		R1[0] = n+1,R2[0] = m+1;
		pa[n+1] = pa[n],pb[m+1] = pb[m];
		for(int i=n;i>=1;i--) if(a[i]==3)
		R1[++aR] = i;
		for(int i=m;i>=1;i--) if(b[i]==3)
		R2[++bR] = i;
		printf("%d\n",solve());
	}
	return 0;
}

 

你可能感兴趣的:(思维题,树状数组,建设性,贪心)