HGOI11.4集训题解

这篇博客介绍了HGOI11.4集训的三道题目:地砖设计采用贪心策略解决,从左往右枚举并拓展;量子二叉堆通过递推求解不同节点数的二叉堆方案数;作画鬼才问题使用二维差分和前缀和来计算最小差异度。
摘要由CSDN通过智能技术生成

题解

又是做 NOI.AC 的题,题目质量还行,但估计不是原创的
洛谷上面第一题找得到原题,只是改了题面而已


第一题——地砖设计

【题目描述】

  • 要求用正方形边长的颜色填满 m ∗ n m*n mn的地图,相同颜色不能相邻,要求输出地图的字典序最小。
  • 颜色用ABCD等填满

  • 这个我以为是无脑贪心,结果有点小难度,贪心策略不正确。
  • 正确的贪心策略是从左往右一个个节点枚举,在枚举到当前节点的时候向右下进行拓展,然后再看能不能用字典序更小的合法解进行替换,从而使得字典序变得更小。
  • 然后不知道怎么证明。但是颜色是超不过D的。

#include <bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int mp[N][N];
char ch[5]={'A','B','C','D','E'};
bool check(int color,int i,int j){
	if(mp[i-1][j]==color) return false;
	if(mp[i][j+1]==color) return false;
	if(mp[i+1][j]==color) return false;
	if(mp[i][j-1]==color) return false;
	return true;
}
bool work(int x,int y,int color){
	bool flag=false;
	int tx=x,ty=y;
	for(int i=1;i<=n;i++){
		if(tx>n||ty>m) break;
		if(mp[x][ty]==-1&&mp[tx][y]==-1&&check(color,x,ty)&&check(color,tx,y)){
			int minn=0;
			for(int j=1;j<color;j++){
				if(check(j,x,ty)){
					minn=j;
					break;
				}
			}
			if(minn) break;
			flag=true;
			tx++,ty++;
		}else break;
	}
	for(int i=x;i<tx;i++)
		for(int j=y;j<ty;j++)
			mp[i][j]=color;
	return flag;
}

int main(){
	scanf("%d%d",&n,&m);
	memset(mp,-1,sizeof(mp));
	
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			if(mp[i][j]!=-1)continue;
			for(int color=1;color<=5;color++){
				if(work(i,j,color))break;
			}
		}
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			printf("%c",ch[mp[i][j]-1]);
		}
		printf("\n");
	}
}

第二题——量子二叉堆

【题目描述】

  • 要求出n个各不相同的节点组成的各不相同的二叉堆(大根小根都算)的方案数有多少

  • 这个其实是递推的。

  • 显然一个小根堆的根是一定是这些元素当中最小的,然后左右子树互不影响,每一个节点数固定的堆的左右子树也是节点数固定的,那么转移决策也可以 O ( 1 ) O(1) O(1)进行转移

  • 唯一比较难想的是左右子树的方案由于元素不同,组成也会不同,那么就是由当前这些元素当中,选择左子树节点的方案数乘一乘就好了

  • 那么很快就可以得出对于小根堆的方程 f [ i ] = f [ l e f t ] ∗ f [ i − l e f t − 1 ] ∗ C i − 1 l e f t f[i]=f[left]*f[i-left-1]*C_{i-1}^{left} f[i]=f[left]f[ileft1]Ci1left

  • 然后记得求逆元,1的大根堆小根堆是一样的,只算一次

#include <bits/stdc++.h>
#define LL long long
using namespace std;
const LL MOD=1e9+7;
const int N=5*1e6+10;
int n;
int lg2[N];
LL f[N];
LL p[N],q[N];
LL C(int m,int n){
	return p[m]*q[n]%MOD*q[m-n]%MOD;
}
int main(){
	scanf("%d",&n);
	f[1]=1;
	f[2]=1;
	p[0]=p[1]=q[0]=q[1]=1;
	for(int i=1;i<=n;i++){
		lg2[i]=lg2[i-1];
		if((1<<lg2[i])*2<=i) lg2[i]++;
	}
	for(int i=2;i<N;i++){
		p[i]=i*p[i-1]%MOD;
		q[i]=(MOD-MOD/i)*q[MOD%i]%MOD;
	}
	for(int i=2;i<N;i++)q[i]=q[i]*q[i-1]%MOD;
	
	for(int i=3;i<=n;i++){
		int temp=(1<<(lg2[i]-1))-1;
		int l=temp+min(i-2*temp-1,temp+1);
		f[i]=f[l]*f[i-1-l]%MOD*C(i-1,l)%MOD;
	}
	if(n==1)printf("%d",1);
	else printf("%lld",2*f[n]%MOD);
}

第三题——作画鬼才

【题目描述】

  • 有一张n行m列的像素画。每个像素位置上都有一个颜色,用小写字母来表示(最多s种颜色)。 有k个副本,每个副本都是在原来的像素画的基础上选取一个矩形并覆盖上了同一种新的颜色。 将这新的k幅选择一幅画,使得该封面与其他k-1幅画差异度之和最小。
  • 定义差异度为:
    d i s ( A , B ) = ∑ i = 1 n ∑ j = 1 m ∣ A i , j − B i , j ∣ dis(A,B)=\sum_{i=1}^n\sum_{j=1}^m |Ai,j−Bi,j| dis(A,B)=i=1nj=1mAi,jBi,j

  • 这个题暴力都很难打orz
  • 考虑二维差分,对于每一个节点,对答案做出贡献的就是当前节点颜色出现次数的贡献
  • 考虑记录每个坐标上每个颜色出现了几次,并由此算出每个颜色在这个坐标上的贡献。观察每个副本的答案,一定是原图的答案扣去矩形的答案,再加上那个矩形里同一种颜色的贡献。
  • 这个部分可以用二维前缀和维护。对于每个坐标上每个颜色出现了几次,需要用到二维差分。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int N=1010;
const int K=3*1e5+10;
int n,m,k,s;
char ch[N][N],col[K];
int xx1[K],xx2[K],yy1[K],yy2[K];
LL cnt[N][N],mp[N][N][30],val[N][N][30],sum;
#define vc col[i]-'a'
#define c_mmp ch[i][j]-'a'
LL query(int A,int B,int C,int D,int cc){
	LL ret=sum;
	for(int c=0;c<s;c++){
		ret+=abs(c-cc)*(mp[C][D][c]-mp[C][B-1][c]-mp[A-1][D][c]+mp[A-1][B-1][c]);
		ret-=val[C][D][c]-val[C][B-1][c]-val[A-1][D][c]+val[A-1][B-1][c];
	}
	return ret;
}
int main(){
	scanf("%d%d%d%d",&n,&m,&k,&s);
	for(int i=1;i<=n;i++)scanf("%s",ch[i]+1);
	bool flag=false;
	for(int i=1;i<=k;i++){
		scanf("%d%d%d%d %c",&xx1[i],&yy1[i],&xx2[i],&yy2[i],&col[i]);
		mp[xx1[i]][yy1[i]][vc]++;mp[xx2[i]+1][yy2[i]+1][vc]++;
		mp[xx1[i]][yy2[i]+1][vc]--;mp[xx2[i]+1][yy1[i]][vc]--;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int cc=0;cc<s;cc++){
				mp[i][j][cc]+=mp[i-1][j][cc]+mp[i][j-1][cc]-mp[i-1][j-1][cc];
				cnt[i][j]+=mp[i][j][cc];
			}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			mp[i][j][c_mmp]+=k-cnt[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int cc=0;cc<s;cc++){
				val[i][j][cc]=abs(cc-ch[i][j]+'a')*mp[i][j][cc];
				sum+=val[i][j][cc];
			}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int cc=0;cc<s;cc++){
				mp[i][j][cc]+=mp[i-1][j][cc]+mp[i][j-1][cc]-mp[i-1][j-1][cc];
				val[i][j][cc]+=val[i-1][j][cc]+val[i][j-1][cc]-val[i-1][j-1][cc];
			}
	LL ans=(1ll<<60);
	int id;
	for(int i=1;i<=k;i++){
		LL tt=query(xx1[i],yy1[i],xx2[i],yy2[i],vc);
		if(ans>tt){
			id=i;
			ans=tt;
		}
	}
	cout<<ans<<' '<<id;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值