每周解题报告 (1st, 8/3~8/6)

RT. 接下来打算在 11 月份之前刷个 200 题的算法题(包括线上和线下)……然后想着就是开个帖子来整理和记录题解什么的,大概能碰到电脑的话那就每周一篇这样子。

这一阶段的话主要是挑 HDUOJ 的题目来做(因为可以找到详细的题目分类),会从简单的题目开始(比如 A+B Problem),然后辅以一些 Codeforces 的题。之前那个 LeetCode 的记录帖就这么弃坑了(

1200, HDU1003, Max Sum

题意大概是给一个很长的数列,有正有负,然后要求这个数列的子序列的和的最大值。然后范围大概是 1e5 以内。有个比较迷的地方就是不仅要求最大值,而且还要求出取得最大值的时候,子区间的位置。时间 1s, 内存 64M.

第一反应是不是区间 DP 的题啊,然后看了一下数据范围果断把这个白痴的想法扔回肚子里去了。子序列最大值倒是会求啊,但是一开始没有反应过来要怎么统计位置。后来想了一下,开不下二维数组那我开一维的总可以了吧。所以这就是一道最大连续子区间和的动态规划问题了。

dp[i] 表示以 a[i] 为区间终点时能取到的和的最大值,然后开两个辅助数组 left[i]right[i] 来表示当取到这个最大值的时候的区间左界和右界分别在哪里,然后最后 for 一遍统计最大值就做完了。需要注意这里一个地方就是数据可正可负,那么考虑一种情况就是假设 dp[i-1] 小于 0 的时候,那么无论 a[i] 是正数还是负数,显然只取 a[i] 的结果会比取 d[i-1] + a[i] 的结果更优,所以遇到这种情况就要重置一下区间的左右界。

还有一个要注意的地方就是输出的格式,当时做的时候各种 PE……

转移方程为 dp[i] = max(dp[i-1] + a[i], a[i]).

Code:

#include <bits/stdc++.h> 
const int INF = 1e9 + 7;
const int MAXN = 1e5 + 10;

int n, sn;
int a[MAXN];
int dp[MAXN];
int left[MAXN], right[MAXN];

void init()
{
  for (int i = 0; i < MAXN; i++)
  {
    dp[i] = -INF;
    left[i] = 0;
    right[i] = 0;
  }
}

int main()
{
  scanf("%d", &n);
  sn = n;
  
  int tmp = 1;
  while (sn--)
  {
    printf("Case %d:\n", tmp++);
    init();
    
    int num;
    scanf("%d", &num);
    for (int i = 0; i < num; i++)
    {
      scanf("%d", &a[i]);
    }
    
    dp[0] = a[0], left[0] = 0, right[0] = 0;
    for (int i = 1; i <= num; i++) 
    {
      // 遇到负数的时候重新计数 
      if (dp[i-1] < 0)
      {
        left[i] = right[i] = i;
        dp[i] = a[i];
        continue;
      }

      dp[i] = dp[i-1] + a[i];
      left[i] = left[i-1];
      right[i] = i;
    }
    
    int maximum = -INF;   // 维护最大值
    int l, r;
    for (int i = 0; i < num; i++) 
    {
      if (dp[i] > maximum)
      {
        maximum = dp[i];
        l = left[i], r = right[i];
      }
    }
    
    printf("%d %d %d\n", maximum, l+1, r+1);
    
    if (tmp-1 != n)
      printf("\n");
  }
  return 0;
}

2200, HDU1004, Let the Balloon Raise

题意就是已知有 n 个气球和它们的颜色,然后要问最受欢迎的气球(也就是出现次数最多的颜色种类)是哪个,颜色用字符串表示且长度不超过 15,有多个子任务,且对于每个子任务保证只有一个合法答案。

嘛,简单的字符串计数嘛。这个用什么方法达到都可以的,你开心就好了。在这里我是用一个奇怪的做法,首先用 STL 的 string 保存每个气球的颜色,然后再按字典序用 sort() 排序一下,最后就可以很方便地统计出出现次数最多的那种气球来了。

Code:

#include <bits/stdc++.h> 
using namespace std;
const int MAXN = 1010;
string balloons[MAXN];
int main()
{
  int num;
  int maximum, tmp, handle;
  
  while (scanf("%d", &num) != EOF && num != 0)
  { 
    for (int i = 0; i < num; i++)
    {
      cin >> balloons[i];
    }
    sort(balloons, balloons + num);
    
    maximum = 0, tmp = 0, handle = 0;
    for (int i = 1; i < num; i++)
    {
      if (balloons[i] != balloons[i-1])
      {
        tmp = 1;
      }
      else
      {
        tmp++;
      }
      
      if (tmp > maximum)
      {
        maximum = tmp;
        handle = i;
      }
    }
    cout << balloons[handle]  << endl;
  }
  
  return 0;
}

3200, HDU1005, Number Sequence

题意是对于一个函数 f(n), 满足 f(1) = 1, f(2) = 1, f(n) = (A * f(n-1) + B * f(n-2)) % 7, 然后求对于给定的 A, B 和 N, f(N) 的值是多少。1 <= n <= 1e8, 有多个子任务。

看数据范围我想没人敢一个一个算过去吧……而且还有好多个子任务,直接就 T 了。

这题的话一看我就猜到是递推……手推了 7 个之后发现好像没有什么规律……开始怀疑人生.jpg 后来才知道,因为结果要模 7,那么对于 f(n-1) 有 7 种可能的值 (0-6),对于 f(n-2) 也是有 7 种可能的值,那么 f(n-1) 和 f(n-2) 一共有 7^2=49 种不同的组合可能,也就是最多 49 次就会出现一个循环节……找到循环节,接下来就可以直接算 f(N) 了,然后就做完了。

做的时候不知道为啥各种 RE,把计算输出的那部分改了一下就 AC 了。

#include <bits/stdc++.h> 
int arr[55];
int main()
{
  arr[0] = 1, arr[1] = 1;
  
  int a, b, n;
  int pot = 0;
  while (scanf("%d%d%d", &a, &b, &n) != EOF && n != 0)
  {
    int pot;
    for (int i = 2; i <= 54; i++)
    {
      arr[i] = (a * arr[i-1] + b * arr[i-2]) % 7;
      if (arr[i] == 1 && arr[i-1] == 1)
      {
        pot = i;
        break;
      }
    }
    
    n = n % (pot-1);
    
    if (n == 0)
    {
      printf("%d\n", arr[pot-2]);
    }
    else
    {
      printf("%d\n", arr[n-1]);
    }
  }
  return 0;
}