【2015.12.25】圣诞节欢乐赛赛后分析

本文总结了一次圣诞主题的编程比赛经历,包括三道题目的解题思路与代码实现:霸气的车牌、转盘停车场和货物搬运工。分享了解题技巧、优化方法及赛后反思。

写在分析前的一点废话:

这次圣诞节欢乐赛答得稍微有点着急,T3的正解还没有想出来就急着交卷,于是丢了50分,成绩不是很理想。另外小细节也丢了30分……讲真是有点遗憾的orz。另外这次考试让我很想出题_(:з」∠)_我准备等我过生日的时候报复社会。恩,就这么愉快的决定了。


第一题【霸气的车牌】:

霸气的车牌(car)
【题目描述】
6Bit 开车总能碰到一些霸气的车牌,这不,等红灯的功夫就又遇到了两个。
当然还有kiki 的APL777 也是很霸气的。
6Bit 认为车牌号只要里面有回文部分,就是比较霸气的,回文就是指车牌号
轴对称,回文的长度越长,其霸气度就越大。
A4444A 的霸气度就是6,霸气回文段就是A4444A。
A33K33 的霸气度就是5,霸气回文段就是33K33。
APL777 的霸气度就是3,霸气回文段就是777。
然而6Bit 的车牌ACZ607 就不霸气……
现在给你一个车牌号,让你帮忙算出此车牌的霸气值是多少,并且输出对应的霸气回文段。如果车牌
不霸气,则直接输出-1。


考试时思路:
总之是要求回文数的长度还有具体方案,而且车牌的长度一定为6,数据很小,所以可以枚举每一个字符,先把它当做一个回文数的起点,从后向前找终点,如果找到一个和它相同的数,就暂定为终点,再从起点与终点向内验证这个字串是否是回文字串,如果是,则返回这个字串的长度,同时边验证边存方案。

考试后分析:
其实这个思路比较难写,要注意的小细节很多,比如要判断这个字串的长度是奇数还是偶数,向内验证字串是否是回文的时候两个字符的下标是否相同,以及判断到哪里之后就不用再求了。
我在考试时就是在判断是否需要继续向下求时少些了一个等号,丢了10分_(:з」∠)_心情复杂。【老师你的数据果然有点水啊喂【X

贴一下丑丑的代码……
#include<stdio.h>
#include<string.h>
char car[10];
char prin[10];
int max(int a,int b)
{
	if(a>b)return a;
	return b;
}
int work(int n)
{
	int ans = 0,sum = 0;
	for(int i = 6;i>n;i--)
	{
		if(sum>i-n+1)break;
		if(car[i]==car[n])
		{
			int tmpi = i,tmpn = n;
			while(tmpi>=tmpn)
			{
				if(car[tmpi]==car[tmpn]&&tmpi==tmpn)
				{
					ans++;
					prin[ans/2+1] = car[tmpi];
				}
				else if(car[tmpi]==car[tmpn])
				{
					ans = ans+2;
					prin[ans/2] = car[tmpi];	
				} 
				else if(car[tmpi]!=car[tmpn])
				{
					ans = 0;
					break;
				}
				tmpi--;
				tmpn++;
				
			}
			sum = max(ans,sum);
			ans = 0;
		}
	}
	return sum;
}

int main()
{
	freopen("car.in","r",stdin);
	freopen("car.out","w",stdout);
	scanf("%s",car+1);
	int tans = 0;
	for(int i = 1;i<= 6;i++)
	{
		if(tans>=6-i)break;
		tans = max(tans,work(i));
	}
	if(tans==0){printf("-1");return 0;}
	else
	{
		printf("%d\n",tans);
		int end = tans/2+1,step = 1;
		if(tans%2==0)
		{
			end = tans/2;
			step = 0;
		}
		for(int i = 1;i<= end;i++)
		{
			printf("%c",prin[i]);
		}
		for(int i = end-step;i>=1;i--)
		{
			printf("%c",prin[i]);
		}
	}
	return 0;
}

tips:读入数组时+1,写的时候思路更直观更清晰。


第二题【转盘停车场】:

2. 转盘停车场(circle)
【题目描述】
6Bit开车来到了一个超大的转盘停车场,这个转盘停车场里可以停满N辆汽车,现在有M种车辆,每种车有无数辆。这里的停车管理员有个奇怪的癖好,就是一定要把停车场N个车位停满,而且相邻的两个车位还不能是相同种品牌的车。该转盘的入口为1号车位,转一圈后的出口处是N号车位,入口和出口相邻(即1号车位和N号车位是相邻的)。问,这个停车场可以停出多少种不同方案?答案可能会很大,最后结果模上23333333333输出。


考试时思路:
这是一个递推+优化,纯递推可以过60%的数据。
递推过程:
这个转盘停车场相当于一个环,相邻两个车的种类不能相同。我们可以把这个环扯开,变成一个首尾车辆种类不能相同的列。一辆辆向这个成列的停车场停车。因为之前所有的情况都是合法的,所以现在第i-1辆车的颜色与第一辆车的颜色不同,所以第i个车位可以有m-2种情况,即f[i-1]*(m-2)。
但是在这同时,有一些以前不合法的情况,在第i辆车停进来之后可以合法了,这些情况就是第一辆车与第i-1辆车种类相同的情况,有f[i-2]种,这时候第i辆车可以停的种类有(m-1)种,所以这些状况合起来是f[i-2]*(m-1)种。
这两种情况互不包含,所以最后的递推式为f[i] = f[i-1]*(m-2)+f[i-2]*(m-1);
递推完成后,我们可以发现100%的数据n为10的12次方,所以需要一个优化,要是问用什么方法优化递推,那么我想到的就是矩阵乘法【毕竟只学了矩阵乘法优化递推
优化过程:
我所推得的递推式需要由两个状态推得,所以初始矩阵就是(f[i-1],f[i-2])。我想要求得的结果是f[i],为了方便递推矩阵的乘法运算,推得的矩阵应该为(f[i],f[i-1])。由一个1×2的矩阵乘上一个矩阵得到一个1×2的矩阵,那么这个中间矩阵就是2×2的矩阵。这时候我们只需要按照递推式子填空就好了。最后得到的递推矩阵为
m-21
m-10
接下来我们要做的工作就是把这个矩阵乘n次方再乘上初始矩阵即可。另外需要①注意中间过程要用快速乘,不然会爆炸②f[3]是一种特殊情况,不能直接由递推矩阵推得。
考试后分析:
正解是正解了,但是之中还有另外一个小问题给数取模的时候犯了错误,如果用+=的时候,mod也是应该%=……恩,因此wa了一个点,伤心(;′⌒`)。
另外此题的递推还有另一种推法,先不考虑两端的车辆,情况就是m*(m-1)*(m-1)*……*(m-1)。当然这之中有一些方案不合法,就是两端的车辆种类相同的时候,这种情况也即是f[i-1],所以从上面的情况中减去f[i-1]即可。优化的过程也差不多,在这里就不再推一遍了_(:з」∠)_
另:还有一种数学推法orz先未完待续着吧。

贴一下代码
#include<stdio.h>
#include<string.h>
typedef long long LL;
LL mod = 23333333333ll;
LL f[4];
LL quick_plus(LL x,LL y)//防止数据爆炸的快速乘 
{
	if(y==0)return 0;
	LL tmp = quick_plus(x,y/2);
	if(y%2==0)return (tmp+tmp)%mod;
	else return ((tmp+tmp)%mod+x)%mod;
}
struct Martix{
	LL map[3][3];
	Martix()
	{
		memset(map,0,sizeof(map));
	}
	Martix operator*(const Martix &s)const//为了方便重载了*这个运算符 
	{
		Martix ans;
		for(int i = 1;i<= 2;i++)
		{
			for(int j = 1;j<= 2;j++)
			{
				for(int k = 1;k<= 2;k++)
				{
					ans.map[i][j]=(ans.map[i][j]+quick_plus(s.map[i][k],map[k][j]))%mod;
				}
			}
		}
		return ans;
	}
};
Martix quick(Martix a,LL n)//矩阵的快速幂 
{
	Martix zero;
	if(n==0)
	{
		for(int i = 1;i<= 2;i++)
			zero.map[i][i] = 1;//初始化单位矩阵 
		return zero;
	}
	Martix tmp = quick(a,n/2);
	if(n%2==0)return tmp*tmp;
	else return tmp*tmp*a;
}
int main()
{
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	LL n,c;
	scanf("%I64d%I64d",&n,&c);
	if(c==2)//为了防止爆零的二十分特判_(:з」∠)_ 
	{
		if(n%2==0||n==1)printf("2\n");
		else printf("0\n");
		return 0;
	}else
	{
		Martix fx;
		Martix trans;
		f[1] = c%mod;
		f[2] = quick_plus(c,(c-1))%mod;
		f[3] = quick_plus(f[2],(c-2))%mod;
		if(n<=3)//3以下需要特判 
		{
			printf("%I64d\n",f[n]);
			return 0;
		}
		fx.map[1][1] = f[3];
		fx.map[1][2] = f[2];
		trans.map[1][1]=c-2;
		trans.map[2][1]=c-1;
		trans.map[1][2]=1;
		trans.map[2][2]=0;
		Martix ans = quick(trans,n-3)*fx;
		printf("%I64d",ans.map[1][1]); 
	}
	return 0;
}

tips:模以的数要输对,注意模数的方式,对不同递推式有不同的值需要特判。


第三题【货物搬运工】:

3. 货物搬运工(goods)
【题目描述】
6Bit把车停好下车后碰到了一个物流公司正在装运货物,大货车最高承载为W,总共有N件货物,每件货物有一个重量Wi,每件货物都有对应的价值Ci,老板准备去请一个搬运工,去把这些货物搬到货车上,但想让货车所承载的价值尽可能的高,现在老板应该请一个臂力多大的搬运工才可以呢?当搬运工的臂力小于物品重量时,则搬不动该物品,无法将此物品搬上大货车。
请你帮忙求一下,如果要使货车所承载的货物价值最高,则搬运工的臂力最小不能小于多少?

考试时思路:
一开始没啥思路,但总之可以先01背包一边,记录方案,然后找这个方案中wi最大的值输出。这个方法50分。

考试后分析:
这个比较遗憾啦,交卷之后10分钟,我又再看了一遍题目。如果要使货车所承载的货物价值最高,则搬运工的臂力最小不能小于多少”——这尼玛明显是二分啊QuQ……宝宝心里苦啊……本宝宝可能多半是瞎……这么明显的二分都看不出来……
总之设x为这个搬运工的臂力,y为火车的最高价值,我们可以得到这样一个图像
那么我们要求的就是第一个达到最高价值的臂力。于是二分+01背包验证即可。


#include<stdio.h>
#include<string.h>
int max(int a,int b)
{
	if(a>b)return a;
	return b;
}
int min(int a,int b)
{
	if(a>b)return b;
	return a;
}
int maxc,n,we;
int w[1005];
int c[1005];
int f[10005];
int judge(int x)//验证搬运工臂力为x的时候,能达到的最大价值为多少
{
	memset(f,0,sizeof(f));
	for(int i = 1;i<= n;i++)
	{
		for(int j = we;j>=w[i];j--)
		{
			if(f[j-w[i]]+c[i]>f[j]&&w[i]<=x)
			{
				f[j] = f[j-w[i]]+c[i];
			}
		}
	}
	return f[we];
}
int main()
{
	freopen("goods.in","r",stdin);
	freopen("goods.out","w",stdout);
	scanf("%d%d",&n,&we);
	int l = 0x7f;
	int r = 0;
	for(int i = 1;i<= n;i++)
	{
		scanf("%d%d",&w[i],&c[i]);
		l = min(l,w[i]);
		r = max(r,w[i]);
	}
	for(int i = 1;i<= n;i++)
	{
		for(int j = we;j>= w[i];j--)
		{
			f[j] = max(f[j],f[j-w[i]]+c[i]);
		}
	}//先求一遍能达到的最大价值
	maxc = f[we];
	r = r+1;
	while(l<r)
	{
		int mid = (l+r)>>1;
		if(judge(mid)<maxc)l = mid+1;
		else r = mid;
	}//二分求解
	printf("%d",l);
	return 0;
}

另:
①这个思路有一个优化,因为搬运工的臂力必定为某一个物品的重量,那么我们可以按照物品重量排一下序然后二分物品的下标。
②要orz一下wyx同学的做法,他先排序,然后边做背包,边求到达第i种物品时能达到的最大价值,然后利用重量的单调找到第一个价值==最大价值的……然后输出这个物品的重量即可。
③还要orz一下llq老师的做法,在做背包的过程中维护一个g数组,表示G[j]为到达重量j的,最小的最大值,最后输出g[w]即可……分类讨论的时候要注意选不选i物品最大价值都一样的时候。

以上,圣诞节欢乐赛;D总之还是很欢乐的,晒一张get到的巧克力

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值