题目质量不错。
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
#include
#include
#include
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
#include
#include
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
#include
#include
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
#include
#include
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 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;
}