浙江农林大学第十届程序设计大赛 解题报告

  星期六的比赛,本应该早写出来的,但因为贪玩(包括陪庄神吃饭),一直拖到今天才把它写完,实在是太晚了。

  本次题目是由xmm,xwx,zjl,zl,zz(排名不分前后,按字典序排)五位学长出的,由于是校赛,题目并不是很难。

  题目已经添加到浙江林学院oj的1459-1467上。欢迎大家来AC!

 

一:Balance Tree    难易程度:简单

  求高度为n的平衡二叉树最小包含的结点数,在纸上画画就可以推出来的,注意一点就是拥有最小结点的平衡二叉树并不是一个完全二叉树

  dp[1] = 1,dp[2] = 2

  dp[i] = dp[i-1] + dp[i-2] + 1(i > 2)。

 

二:Internet Protocol     难易程度:简单

  这是一道水题,就是对四个数,看从十进制转换到十六进制时余数有没有大于等于10的,如果有,就YES,否则NO。

 

三:Simple Game   难易程度:较难

  这题本不是一道难题,但鉴于我们学校拿犹如林黛玉般脆弱的DP水平,在加上比赛时没有任何人过的情况,把它算较难吧。

  比赛时没人过,应该是大家都把这题想简单了,简单的以为利用贪心每次把相邻和最小的两堆合并就行了,看起来就想Huffman编码一样,但这是不对的,Huffman只是求平均权益最小,并不是求最优解。

  这题是一个简单DP,假如你要求从 1 到 n 堆得沙子的最优解,按照最优子结构来讲,你应该在 1 到 n 中找一个分界点 k(1<k<n),合并这两组,  如果这两组是最优解,那么合并后的解也是最优解。一层一层递归下去,可得。 分析以后,你会发现,这题跟数塔是很像的。

  动态转移方程DP[i][j] = DP[i][k] + DP[k+1][j] + sum[i][j](i<k<j).

  其中DP[i][j]数组表示从 i 到 j 堆沙子的最优解,sum[i][j]数组表示从 i 到 j 堆沙子的重量总和。最后DP[1][n]就为答案。

  递归的时候如果用记忆化搜索写,会更简单明了。

 

四:DR.Layton    难易程度:中等

  又是一道在比赛中赤裸裸的悲剧题,比赛时没一个人出,比上一题还更悲剧。

  看数据范围,每次有六种情况可以改变杯子的状态,对这六种情况做BFS,同时对杯子出现过的状态进行记录。不断递归下去,直到到达指定的状态,或再也找不到新的状态为止,输出。

  注意的一点就是空杯子和满杯的倒出和导入是不会再改变状态的,应该排除掉。

 

五:The Longest Queue     难易程度:较简单

  一道水题,也是比赛中出的并列第一多的题,就是对男生队伍和女生队伍分别可以分组的最大和最小数组进行讨论就可以了。想想就可以出来,不需要任何算法的支持!

 

六:A String Problem     难易程度:难

  这题应该是所有题目中第二难的题目了,不过好像数据比较弱,被一个人水过了。

  可以把字符串当成数字来看,按字典序赋一个数值,那么这题就变成了求在 i 前面不比num[i]小的数的个数。 就变成了树状数组的入门题。

  这题的问题就是时间不够,如果每次往前找,时间复杂度O(n^2),肯定会超时。想到树状数组那logn的统计和修改功能,简直就是天作之合。

  从第一个数开始,每次在树状数组的数组C中查找比num[i]小的数的个数,然后再把num[i]的个数(1)添加到C数组中去。

  线扫O(n),查找和添加都是O(logn),总时间O(nlogn)。

 

七:Rome   难易程度:中等

  一道模拟题,并不是很复杂,比赛的时候由于大家一开始都没怎么做,出的比较慢,所以最后做出来的人不是很多。

  可以用三个数组分表存储百,十,个的表示方式,对每个三位数分别输出。

  注意的是当某位为0的时候注意要符合读写规范,不要000的时候输出$$$。

 

八:The War Of The Old Acmers    难易程度:简单

  本次比赛第一水题,题目把所有有点难度和歧义的地方都排除了,还能讲什么!

 

九:Key Number   难易程度:难

  看样子是本次校赛拒绝AK题吧,由Lost大神出题,比较由难度,对LIS的一个应用!

  题目意思,求序列的最长子序列中不可分割元素的数目。不可分割元素,肯定属于某一个最长子序列,首先做的就是把属于最长子序列的数提取出来,减小查找范围。怎么提取?可以用LIS(最长递增子序列)和LDS(最长递减子序列)。对序列,从前往后,求以每个数 a[i] 为底最长子序列数组dp1。从后往前,求以每个数a[i]为底的最长递减子序列数组dp2。线扫一遍,如果dp1[i] + dp2[i] == length+1(length表示最长子序列的长度),那么这个数就是属于某一个最长子序列的。

  提取出来以后,对每个数a[i],看它所对应的dp1[i] 在整个数组中出现几次,如果只出现一次,那么表示这个数是不可分割元素。最后输出!

  注意一点:求LIS和LDS时要用O(nlogn)的做法,不能用O(n^2)的,不然会超时!

 

代码
   
     
#include < iostream >
using namespace std;
const long maxn = 100050 ;

int n,ans;
int a[maxn];
int b[maxn],bb[maxn];
int dp1[maxn],dp2[maxn];
int vis[maxn];
int Hash[maxn];

void Init()
{
int i;
scanf(
" %d " , & n);

for (i = 1 ;i <= n;i ++ )
scanf(
" %d " , & a[i]);
}

void LIS()
{
// 从前往后求最长递增子序列
int i;
int len = 0 ;
for (i = 1 ;i <= n;i ++ )
{
int l = 1 ;
int h = len;

while (l <= h)
{
int mid = (l + h) >> 1 ;
if (b[mid] < a[i])
{
l
= mid + 1 ;
}
else
h
= mid - 1 ;
}

if (l > len)
len
++ ;

b[l]
= a[i];
dp1[i]
= l;
}
}

void LDS()
{
// 从后往前求最长递减子序列
int i;
int len = 0 ;

for (i = n;i >= 1 ;i -- )
{
int l = 1 ;
int h = len;

while (l <= h)
{
int mid = (l + h) >> 1 ;
if (bb[mid] > a[i])
{
l
= mid + 1 ;
}
else
h
= mid - 1 ;
}

if (l > len)
len
++ ;

bb[l]
= a[i];
dp2[i]
= l;
}
}

int Play()
{
int i;
memset(vis,
0 , sizeof (vis));
memset(Hash,
0 , sizeof (Hash));

int length = 0 ;
for (i = 1 ;i <= n;i ++ )
{
// 求最长子序列的长度
if (dp1[i] > length)
length
= dp1[i];
}

for (i = 1 ;i <= n;i ++ )
{
// 淘汰出不属于任何最长子序列的数
if (dp1[i] + dp2[i] == length + 1 )
vis[i]
= 1 ;
}

for (i = 1 ;i <= n;i ++ )
{
// 统计一个长度出现几次
if (vis[i] == 1 )
{
Hash[dp1[i]]
++ ;
}
}

ans
= 0 ;
for (i = 1 ;i <= n;i ++ )
{
if (vis[i] == 1 && Hash[dp1[i]] == 1 )
{
// 如果一个长度只出现一次,那么它就是不可分割元素
ans ++ ;
}
}

return ans;
}

void print()
{
printf(
" %d\n " ,ans);
}

int main()
{
int t;
scanf(
" %d " , & t);
while (t -- )
{
Init();
LIS();
LDS();
Play();
print();
}
return 0 ;
}

 

你可能感兴趣的:(程序设计)