小试4解析

题目质量不错。

T1:应该能看出是一个裸(so easy)的贪心+排序吧?

这里我们考虑一个dp,设dp[i][j]表示前i位,使用了j次变换,能获得的最小距离。

那么dp[i][j]=min(dp[i-1][j]+abs(n[i]-m[i]),dp[i-1][j-1]+(n[i]==m[i])),还是很好推的。

不过由于数据范围太太太太小了,所以n^4什么的dp都可以水过去。

参考代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
const int K=61;
char n[K],m[K];
int k;
int f[K][K];
int main(){
	freopen("dis.in","r",stdin);
	freopen("dis.out","w",stdout);
	scanf("%s%s",n+1,m+1);
	int l=strlen(n+1);
	scanf("%d",&k);
	memset(f,0x3f,sizeof(f));
	f[0][0]=0;
	for (int i=1;i<=l;i++){
		f[i][0]=f[i-1][0]+abs(n[i]-m[i]);
		for (int j=1;j<=k && j<=i;j++)
			f[i][j]=min(f[i-1][j]+abs(n[i]-m[i]),f[i-1][j-1]+(n[i]==m[i]));
	}
	printf("%d",f[l][k]);
	return 0;
}



T2:一个找方形联通块的问题。至于判Bad placement的,我们可以先找到一个#,然后把这个#向四个方向延伸,然后在延伸得到的范围矩形中看看是否有'.',有则肯定是Bad,没有则再将边界上的#向矩形外扩散一个单位距离,看看是否还有#,有则肯定也是Bad,如果没有Bad,拿个计数器累和一下就行了。

参考代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=1010;
const int dx[4]={0,1,0,-1};
const int dy[4]={1,0,-1,0};
char a[N][N];
bool vis[N][N];
int n,m,ans=0;
void print(){printf("Bad placement.");exit(0);}
void BFS(int sx,int sy){
	for (int k=0;k<4;k++){
		int nx=sx+dx[k],ny=sy+dy[k];
		if (nx>0 && nx<=n && ny>0 && ny<=m && a[nx][ny]=='#' && vis[nx][ny])print();
	}
	int tx,ty;
	for (tx=sx+1;tx<=n;tx++)if (a[tx][sy]=='.')break;else if (vis[tx][sy])print();tx--;
	for (ty=sy+1;ty<=m;ty++)if (a[sx][ty]=='.')break;else if (vis[sx][ty])print();ty--;
	for (int i=sx;i<=tx;i++)
		for (int j=sy;j<=ty;j++){
			if (a[i][j]=='.')print();
			vis[i][j]=true;
		}
}
int main(){
	freopen("battle.in","r",stdin);
	freopen("battle.out","w",stdout);
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
		scanf("%s",a[i]+1);
	memset(vis,0,sizeof(vis));
	for (int i=1;i<=n;i++)
		for (int j=1;j<=m;j++)
			if (a[i][j]=='#' && !vis[i][j])ans++,BFS(i,j);
	printf("There are %d ships.",ans);
	return 0;
}


T3:很巧妙的Dp,当我们用普通的线性方程dp[i]很好的无法表示方案数时,我们考虑加入一个维度来表示一些不完美状态(即不是答案要求的状态),然后通过这些不完美状态来进行更方便的相互之间的转移,和向完美状态的转移。这个思想是非常实用的。

对于此题,由于一个L字型的砖块,一定会是第一行与第二行产生(或消除)一个单位砖块的差距。那么我们就设dp[i][0]表示第一行与第二行恰好平齐。

dp[i][1]表示第一行比第二行多一个单位砖块。

dp[i][2]表示第二行比第一行多一个单位砖块。

那么转移方程就很好(真的很好写)写了(枚举所有的情况就行了)。可以自己思考一下。

参考代码:

#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int Mod=10000;
const int N=1000100;
int n;
int f[N][3];
int main(){
	freopen("wall.in","r",stdin);
	freopen("wall.out","w",stdout);
	scanf("%d",&n);
	//up-down=0->0 1->1 -1->2
	f[0][1]=f[0][0]=f[0][2]=0;
	f[1][0]=1;f[1][1]=f[1][2]=0;
	f[2][0]=2;f[2][1]=f[2][2]=1;
	for (int i=3;i<=n;i++){
		f[i][0]=f[i-1][1]+f[i-1][2]+f[i-1][0]+f[i-2][0];
		f[i][1]=f[i-2][0]+f[i-1][1];
		f[i][2]=f[i-2][0]+f[i-1][2];
		f[i][0]%=Mod;f[i][1]%=Mod;f[i][2]%=Mod;
	}
	printf("%d",f[n][0]);
}



T4:一个数学问题。首先我们可以很容易看出,只有2^p个瓶子才能变成一个瓶子。所以我们考虑将n拆分成不超过k个数字,使得每个数字到第一个比它大的2的次幂的差的和最小(很绕口)。既然有2的次幂,我们就把n在2进制下考虑。

例如(10000)2=10011100010000

我们考虑一个贪心策略:直接把n的二进制的前k-1个1拆分依次出来,那么这一部分对答案就是没有贡献的了。然后直接把第k个1减去第k+1~....个1的和。可以试试,答案就是15808。

我们再想想这个贪心策略为什么是对的?

首先,如果第1个1我们不直接取走的话,我们一定会对答案产生至少第1个1../2的贡献,这个贡献是致命的。因为不论你后面怎么取,这个答案都会大于第1个1../2,所以我们必须把第1个1直接取走,后面同理。然后把第k+1个1直接补齐到第k个1就行了。

这么讲似乎很模糊,建议自己动手算一算。


参考代码:

#include<cstdio>
#include<algorithm>
#include<functional>
using namespace std;
int n,k,tot=0;
int a[50];
int main(){
	freopen("water.in","r",stdin);
	freopen("water.out","w",stdout);
	scanf("%d%d",&n,&k);
	for (int m=n;m;m-=m&(-m))//用树状数组的lowbit技巧直接取1是非常方便的
		a[++tot]=m&(-m);
	sort(a+1,a+tot+1,greater<int>());
	int t1=a[k],t2=0;
	for (int i=k;i<=tot;i++)t2+=a[i];
	if (k>=tot)putchar(48);
		else printf("%d",2*t1-t2);
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值