2020年第十一届蓝桥杯C组决赛

一、美丽的 2

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

小蓝特别喜欢 2,今年是公元 2020 年,他特别高兴。 他很好奇,在公元 1 年到公元 2020 年(包含)中,有多少个年份的数位中包含数字 2?

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:直接枚举+判断即可。

#include <iostream>
using namespace std;

bool hasTwo(int num)
{
  while(num)
  {
    if(num % 10 ==2)
    {
      return true;
    }
    num /= 10;
  }
  return false;
}

int main()
{
  int year, ans = 0;
  for(year = 1; year <= 2020; ++ year)
  {
    if(hasTwo(year))
    {
      ++ ans;
    }
  }
  cout << ans;
  return 0;
}

二、合数个数

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

一个数如果除了 1 和自己还有其他约数,则称为一个合数。例如:1, 2, 3 不是合数,4, 6 是合数。

请问从 1 到 2020 一共有多少个合数。

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:直接枚举+判断。

#include <iostream>
using namespace std;

bool isPrime(int num)
{
  for(int i = 2; i * i <= num; ++ i)
  {
    if(num % i == 0)
    {
      return false;
    }
  }
  return true;
}

int main()
{
  int ans = 0, num;
  for(num = 1; num <= 2020; ++ num)
  {
    if(!isPrime(num))
    {
      ++ ans;
    }
  }
  cout << ans;
  return 0;
}

更快的方式是直接用欧拉筛的过程求出1~2020有多少个素数,用2020减去即可。

#include <iostream>
using namespace std;

const int maxn = 2020;
bool isNotPrime[maxn + 7];
int Primes[maxn + 7], cnt;

void euler()
{
  int i, j;
  for(i = 2; i <= maxn; ++ i)
  {
    if(!isNotPrime[i])
    {
      Primes[cnt++] = i;
    }
    for(j = 0; j < cnt && Primes[j] * i <= maxn; ++ j)
    {
      isNotPrime[Primes[j] * i] = true;
      if(i % Primes[j] == 0)
      {
        break;
      }
    }
  }
}

int main()
{
  // 请在此输入您的代码
  euler();
  cout << 2020 - cnt - 1;
  return 0;
}

减去1是因为本题把1当作素数,但是实际上欧拉筛对于1的处理是并不视它为素数。

三、扩散

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

小蓝在一张无限大的特殊画布上作画。

这张画布可以看成一个方格图,每个格子可以用一个二维的整数坐标表示。

小蓝在画布上首先点了一下几个点:(0, 0), (2020, 11), (11, 14), (2000, 2000)。

只有这几个格子上有黑色,其它位置都是白色的。

每过一分钟,黑色就会扩散一点。具体的,如果一个格子里面是黑色,它就会扩散到上、下、左、右四个相邻的格子中,使得这四个格子也变成黑色(如果原来就是黑色,则还是黑色)。

请问,经过 2020 分钟后,画布上有多少个格子是黑色的。

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:BFS即可,需要注意的是为防止索引为负产生越界,需要将所有的点进行偏移,横纵坐标各加2020,然后再看数组(画布)应该设置为多大。

#include <iostream>
#include <queue>
using namespace std;
struct Point{
  int x, y, step;
};

int dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};

bool used[6100][6100];

int BFS(queue<Point>& pointQue)
{
  int pointNum = 4, x, y, step, i, n_x, n_y;
  Point p;
  while(!pointQue.empty()){
    p = pointQue.front();
    pointQue.pop();

    x = p.x;
    y = p.y;
    step = p.step;

    if(step == 2020)
    {
      break;
    }

    for(i = 0; i < 4; ++ i)
    {
      n_x = x + dir[i][0];
      n_y = y + dir[i][1];
      if(!used[n_x][n_y])
      {
        ++ pointNum;
        used[n_x][n_y] = true;
        pointQue.push(Point{n_x, n_y, step + 1});
      }
    }

  }
  return pointNum;
}

int main()
{
  // 请在此输入您的代码
  queue<Point> pointQue;
  const int offset = 2020;
  pointQue.push(Point{0 + offset, 0 + offset, 0});
  pointQue.push(Point{2020 + offset, 11 + offset, 0});
  pointQue.push(Point{11 + offset, 14 + offset, 0});
  pointQue.push(Point{2000 + offset, 2000 + offset, 0});
  used[0][0] = used[2020][11] = used[11][14] = used[2000][2000] = true;
  cout << BFS(pointQue);
  return 0;
}

四、阶乘约数

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

定义阶乘 n! = 1 × 2 × 3 × · · · × nn!=1×2×3×⋅⋅⋅×n。

请问 100!100! (100100 的阶乘)有多少个正约数。

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:使用素数分解定理,任意一个数一定可以表示为

num=\prod_{i=0}^{k}P_i^{Q_i},P_i\ is\ prime

对于P_i,它出现的次方数的可能情况为0-Q_iQ_i+1种情况,因此总的因子数量可以用乘法原理表示:

ans=\prod_{i=0}^{k}(1+Q_i)

#include <iostream>
#include <unordered_map>
using namespace std;
int main()
{
  unordered_map<int, int> hash_count;
  int temp, factor, i;
  long long c;
  for(factor = 2; factor <= 100; ++ factor)
  {
    temp = factor;
    for(i = 2; temp != 1; ++ i)
    {
      c = 0;
      while(temp % i == 0)
      {
        temp /= i;
        ++ c;
      }
      hash_count[i] += c;
    }
  }
  c = 1;
  for(unordered_map<int, int>::iterator iter = hash_count.begin(); iter != hash_count.end(); ++ iter)
  {
    c = c * (iter -> second + 1);
  }
  cout << c;
  return 0;
}

五、本质上升序列

题目描述

本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。

小蓝特别喜欢单调递增的事物。

在一个字符串中,如果取出若干个字符,将这些字符按照在字符串中的顺序排列后是单调递增的,则成为这个字符串中的一个单调递增子序列。

例如,在字符串 lanqiao 中,如果取出字符 n 和 q,则 nq 组成一个单调递增子序列。类似的单调递增子序列还有 lnq、i、ano 等等。 小蓝发现,有些子序列虽然位置不同,但是字符序列是一样的,例如取第二个字符和最后一个字符可以取到 ao,取最后两个字符也可以取到 ao。小蓝认为他们并没有本质不同。

对于一个字符串,小蓝想知道,本质不同的递增子序列有多少个? 例如,对于字符串 lanqiao,本质不同的递增子序列有 21 个。它们分别是 l、a、n、q、i、o、ln、an、lq、aq、nq、ai、lo、ao、no、io、lnq、anq、lno、ano、aio

请问对于以下字符串(共 200200 个小写英文字母,分四行显示):

tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhf
iadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqij
gihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmad
vrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl

本质不同的递增子序列有多少个?

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:本题和上升子序列很像,但麻烦在于本题要求出上升子序列的个数,并且还要满足上升子序列的去重问题。

1.首先考虑上升子序列,用dp[i]表示在第i个位置结束的所有上升子序列的个数,那么有:

dp[i]=\sum_{j=0}^{i-1}dp[j]\ (ch[i] > ch[j])

2.接下来考虑去重,对于上面的公式,以acab为例,当我们要求以b结尾的子序列的时候,会计算出两个a(1)b,a(2)b,a(1)表示第一次出现的a,a(2)类似。

很显然的一点是,在序列******a(1)*******a(2)***b中,如果存在一个序列***a(1),那么必然可以通过把a(1)换成a(2)得到同样的结果串,也就是说,以a(1)结束的上升子序列,一定会被包含在以a(2)结束的上升子序列的答案集合中,因此,我们需要重新定义转移方程。

dp[i]=\sum_{c='a'}^{c<ch[i]}dp[last[c]]

dp[i]只会累加那些离i最近的且满足ch[i]<ch[j]的dp[j],且每一个ch[j]都不能相同。

#include <iostream>
#include <cstring>
using namespace std;
int main()
{
  // 正向解:对于每一个字符,只需要计算从他第一次出现开始,可以产生的递增序列数量即可。a*****a****,显然第一个a产生的序列一定包含第二个a产生的序列
  // 反向解:对于每一个字符,计算以他结束的递增序列数量,只计算最靠近开头的字符
  // 去重 al***lk,第一个l贡献的答案一定被包含在第二个l里
  string str = "tocyjkdzcieoiodfpbgcncsrjbhmugdnojjddhllnofawllbhfiadgdcdjstemphmnjihecoapdjjrprrqnhgccevdarufmliqijgihhfgdcmxvicfauachlifhafpdccfseflcdgjncadfclvfmadvrnaaahahndsikzssoywakgnfjjaihtniptwoulxbaeqkqhfwl";
  
  int dp[205], last[30];
  int i, j, ans = 0;
  bool vis[30];
  memset(vis, 0, sizeof(vis));
  memset(dp, 0, sizeof(dp));
  memset(last,  -1, sizeof(last));
  dp[0] = 1;
  last[str[0] - 'a'] = 0;
  for(i = 1;i < str.length(); ++ i)
  {
    dp[i] = 1;
    last[str[i] - 'a'] = i;
    for(j = 0; j < str[i] - 'a'; ++ j)
    {
      if(last[j] != -1)
      {
        dp[i] += dp[last[j]];
      }
    }
  }
  for(i = str.length() - 1; i >=0; -- i)
  {
    if(!vis[str[i] - 'a'])
    {
      vis[str[i] - 'a'] = true;
      ans += dp[i];
    }
  }
  cout << ans;
  return 0;
}

六、天干地支

题目描述

古代中国使用天干地支来记录当前的年份。

天干一共有十个,分别为:甲(jiǎ)、乙(yǐ)、丙(bǐng)、丁(dīng)、戊(wù)、己(jǐ)、庚(gēng)、辛(xīn)、壬(rén)、癸(guǐ)。

地支一共有十二个,分别为:子(zǐ)、丑(chǒu)、寅(yín)、卯(mǎo)、辰(chén)、巳(sì)、午(wǔ)、未(wèi)、申(shēn)、酉(yǒu)、戌(xū)、 亥(hài)。

将天干和地支连起来,就组成了一个天干地支的年份,例如:甲子。

2020 年是庚子年。

每过一年,天干和地支都会移动到下一个。例如 2021 年是辛丑年。

每过 60 年,天干会循环 6 轮,地支会循环 5 轮,所以天干地支纪年每 60 年轮回一次。例如 1900 年,1960 年,2020 年都是庚子年。

给定一个公元纪年的年份,请输出这一年的天干地支年份。

输入描述

输入一行包含一个正整数,表示公元年份。

其中有 ,输入的公元年份为不超过 99999999 的正整数。

输出描述

输入一行包含一个正整数,表示公元年份。

输入输出样例

示例

输入

2020

输出

gengzi

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:取余算偏移,没有什么可说的。唯一值得说的大概就是对于负数的处理借助循环把倒退改为前进的方式。

#include <iostream>
#include <string>
using namespace std;

string sky[10] = {"jia","yi","bing","ding","wu","ji","geng","xin","ren","gui"}, ground[12] = {"zi", "chou", "yin", "mao", "chen", "si", "wu", "wei", "shen", "you", "xu", "hai"};

int main()
{
  // 请在此输入您的代码
  int skyIndex, groundIndex, year;
  cin >> year;
  skyIndex = ((year - 2020) % 10 + 10) % 10;
  groundIndex = ((year - 2020) % 12 + 12) % 12;
  cout << sky[(6 + skyIndex) % 10] << ground[(groundIndex)];
  return 0;
}

七、皮亚诺曲线距离

题目描述

皮亚诺曲线是一条平面内的曲线。

下图给出了皮亚诺曲线的 1 阶情形,它是从左下角出发,经过一个 3 × 3 的方格中的每一个格子,最终到达右上角的一条曲线。

图片描述

下图给出了皮亚诺曲线的 2 阶情形,它是经过一个 3^2 × 3^2 的方格中的每一个格子的一条曲线。它是将 1 阶曲线的每个方格由 1 阶曲线替换而成。

图片描述

下图给出了皮亚诺曲线的 3 阶情形,它是经过一个 3^3 × 3^3 的方格中的每一个格子的一条曲线。它是将 2 阶曲线的每个方格由 11 阶曲线替换而成。

图片描述

皮亚诺曲线总是从左下角开始出发,最终到达右上角。

我们将这些格子放到坐标系中,对于 kk 阶皮亚诺曲线,左下角的坐标是(0, 0),右上角坐标是 (3^k − 1, 3^k − 1),右下角坐标是 (3^k − 1, 0),左上角坐标是(0, 3^k − 1)。

给定 k 阶皮亚诺曲线上的两个点的坐标,请问这两个点之间,如果沿着皮亚诺曲线走,距离是到少?

输入描述

输入的第一行包含一个正整数 k,皮亚诺曲线的阶数。

第二行包含两个整数 x_1, y_1,表示第一个点的坐标。

第三行包含两个整数 x_2, y_2,表示第二个点的坐标。

其中有 ,0 ≤ k ≤ 100, 0 ≤ x_1, y_1, x_2, y_2 < 3^k, x_1, y_1, x_2, y_2 ≤ 10^{18}。数据保证答案不超过 10^{18}。

输出描述

输出一个整数,表示给定的两个点之间的距离。

输入输出样例

示例

输入

1
0 0
2 2

输出

8

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

算法解析见链接第六题:https://blog.csdn.net/qq_39304630/article/details/117598818

#pragma comment(linker, "/STACK:10240000,10240000") 
#include <iostream>
#include <string>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
 
const int maxn = 1000;
 
struct bign{
    int d[maxn], len;
 
	void clean() { while(len > 1 && !d[len-1]) len--; }
 
    bign() 			{ memset(d, 0, sizeof(d)); len = 1; }
    bign(int num) 	{ *this = num; } 
	bign(char* num) { *this = num; }
    bign operator = (const char* num){
        memset(d, 0, sizeof(d)); len = strlen(num);
        for(int i = 0; i < len; i++) d[i] = num[len-1-i] - '0';
        clean();
		return *this;
    }
    bign operator = (int num){
		char s[1000] = { 0 }; 
		sprintf_s(s, "%d", num);
        *this = s;
		return *this;
    }
 
    bign operator + (const bign& b){
        bign c = *this; int i;
        for (i = 0; i < b.len; i++){
        	c.d[i] += b.d[i];
        	if (c.d[i] > 9) c.d[i]%=10, c.d[i+1]++;
		}
		while (c.d[i] > 9) c.d[i++]%=10, c.d[i]++;
		c.len = max(len, b.len);
		if (c.d[i] && c.len <= i) c.len = i+1;
        return c;
    }
    bign operator - (const bign& b){
        bign c = *this; int i;
        for (i = 0; i < b.len; i++){
        	c.d[i] -= b.d[i];
        	if (c.d[i] < 0) c.d[i]+=10, c.d[i+1]--;
		}
		while (c.d[i] < 0) c.d[i++]+=10, c.d[i]--;
		c.clean();
		return c;
    }
    bign operator * (const bign& b)const{
        int i, j; bign c; c.len = len + b.len; 
        for(j = 0; j < b.len; j++) for(i = 0; i < len; i++) 
			c.d[i+j] += d[i] * b.d[j];
        for(i = 0; i < c.len-1; i++)
            c.d[i+1] += c.d[i]/10, c.d[i] %= 10;
        c.clean();
		return c;
    }
    bign operator / (const bign& b){
    	int i, j;
		bign c = *this, a = 0;
    	for (i = len - 1; i >= 0; i--)
    	{
    		a = a*10 + d[i];
    		for (j = 0; j < 10; j++) 
				if (a < b*(j+1)) break;
    		c.d[i] = j;
    		a = a - b*j;
    	}
    	c.clean();
    	return c;
    }
    bign operator % (const bign& b){
    	int i, j;
		bign a = 0;
    	for (i = len - 1; i >= 0; i--)
    	{
    		a = a*10 + d[i];
    		for (j = 0; j < 10; j++) if (a < b*(j+1)) break;
    		a = a - b*j;
    	}
    	return a;
    }
	bign operator += (const bign& b){
        *this = *this + b;
        return *this;
    }
 
    bool operator <(const bign& b) const{
        if(len != b.len) return len < b.len;
        for(int i = len-1; i >= 0; i--)
            if(d[i] != b.d[i]) return d[i] < b.d[i];
        return false;
    }
    bool operator >(const bign& b) const{return b < *this;}
    bool operator<=(const bign& b) const{return !(b < *this);}
    bool operator>=(const bign& b) const{return !(*this < b);}
    bool operator!=(const bign& b) const{return b < *this || *this < b;}
    bool operator==(const bign& b) const{return !(b < *this) && !(b > *this);}
 
    string str() const{
        char s[maxn]={};
        for(int i = 0; i < len; i++) s[len-1-i] = d[i]+'0';
        return s;
    }
    
    int toInt() const{
    	int num = 0;
    	for(int i = 0; i < len; i++) num = num * 10 + d[i];
        return num;
	}
};
 
istream& operator >> (istream& in, bign& x)
{
    string s;
    in >> s;
    x = s.c_str();
    return in;
}
 
ostream& operator << (ostream& out, const bign& x)
{
    out << x.str();
    return out;
}

typedef long long LL;
const int maxLen = 110;
bign p_len[maxLen], side_len[maxLen];

int dir_one_loc[3][3] = 
{
  {0, 1, 2},
  {5, 4, 3},
  {6, 7, 8},
}, dir_two_loc[3][3] = 
{
  {6, 7, 8},
  {5, 4, 3},
  {0, 1, 2},
}, dir_three_loc[3][3] = 
{
  {2, 1, 0},
  {3, 4, 5},
  {8, 7, 6},
}, dir_four_loc[3][3] = 
{
  {8, 7, 6},
  {3, 4, 5},
  {2, 1, 0},
}, 
dir_one[9] = {1, 2, 1, 3, 4, 3, 1, 2, 1},//左下
dir_two[9] = {2, 1, 2, 4, 3, 4, 2, 1, 2},//右下
dir_three[9] = {3, 4, 3, 1, 2, 1, 3, 4, 3},//左上
dir_four[9] = {4, 3, 4, 2, 1, 2, 4, 3, 4}//右上
;

//求解 (x,y) 在 k阶图 上 距离(0,0)的距离
bign get_len(bign x, bign y, int k, int preDir)
{
  if(k == 0) return 0;
  int col = (x / side_len[k - 1]).toInt();
  int row = (y / side_len[k - 1]).toInt();
  //printf("阶:%d col:%d row:%d\n", k, col, row);
  int block, nextDir;
  //求出前面有多少块
  switch(preDir)
  {
    case 1:block = dir_one_loc[col][row];
          nextDir = dir_one[block];
          break;
    case 2:block = dir_two_loc[col][row];
          nextDir = dir_two[block];
          break;
    case 3:block = dir_three_loc[col][row];
          nextDir = dir_three[block];
          break;
    case 4:block = dir_four_loc[col][row];
          nextDir = dir_four[block];
          break;
  }
  //cout << "有" << block << "块," << "下一个方向为:" << nextDir << ",当前阶数:" << k << ",块大小:" << p_len[k - 1] << endl;
  return p_len[k - 1] * block + (k > 1 ? block : 0) + get_len(x % side_len[k - 1], y % side_len[k - 1], k - 1, nextDir);
}

int main()
{
  int k, i;
  bign dis_one, dis_two, x, y;
  cin >> k;
  p_len[0] = 1;
  p_len[1] = 8;
  side_len[0] = 1;
  side_len[1] = 3;
  //生成1~k-1阶的皮亚诺曲线的总长度
  for(i = 2; i < k; ++ i)
  {
    side_len[i] = side_len[i - 1] * 3;
    p_len[i] = p_len[i - 1] * 9 + 8; // p_len[i] = side_len[i] * side_len[i] - 1;
    //cout << "阶:" << i << " " << side_len[i] << " " << p_len[i] << endl;
  }
  cin >> x >> y;
  dis_one = get_len(x, y, k, 1);
  //cout << "dis_one:" << dis_one << endl;
  cin >> x >> y;
  dis_two = get_len(x, y, k, 1);
  //cout << "dis_two:" << dis_two << endl;
  cout << (dis_one > dis_two ? dis_one - dis_two : dis_two - dis_one);
  return 0;
}

八、蓝肽子序列

题目描述

L 星球上的生物由蛋蓝质组成,每一种蛋蓝质由一类称为蓝肽的物资首尾连接成一条长链后折叠而成。

生物学家小乔正在研究 L 星球上的蛋蓝质。她拿到两个蛋蓝质的蓝肽序列,想通过这两条蓝肽序列的共同特点来分析两种蛋蓝质的相似性。

具体的,一个蓝肽可以使用 1 至 5 个英文字母表示,其中第一个字母大写,后面的字母小写。一个蛋蓝质的蓝肽序列可以用蓝肽的表示顺序拼接而成。

在一条蓝肽序列中,如果选取其中的一些位置,把这些位置的蓝肽取出,并按照它们在原序列中的位置摆放,则称为这条蓝肽的一个子序列。蓝肽的子序列不一定在原序列中是连续的,中间可能间隔着一些未被取出的蓝肽。

如果第一条蓝肽序列可以取出一个子序列与第二条蓝肽序列中取出的某个子序列相等,则称为一个公共蓝肽子序列。

给定两条蓝肽序列,找出他们最长的那个公共蓝肽子序列的长度。

输入描述

输入两行,每行包含一个字符串,表示一个蓝肽序列。字符串中间没有空格等分隔字符。

其中有 ,两个字符串的长度均不超过 1000。

输出描述

输出一个整数,表示最长的那个公共蓝肽子序列的长度。

输入输出样例

示例

输入

LanQiaoBei
LanTaiXiaoQiao

输出

2

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:先来理清题意,对于样例来说,两个序列共有的蓝肽为Lan、Qiao,因此最长的序列长度为2。本题和最长公共子序列有点类似但不完全一样,最长公共子序列是按照字符比较,本题是按照蓝肽(一个字符串)比较,其他的完全一样。因此我们需要先将蓝肽链分割成多个蓝肽,再对蓝肽运行最长公共子序列。

#include <iostream>
#include <vector>
#include <string>
#include <cstring>
#include <algorithm>
using namespace std;

vector<string> split(string str)
{
  int i, n = str.length();
  string sub_str = "";
  vector<string> strs_ans;
  for(i = 0; i < n; ++ i)
  {
    if(str[i] >= 'A' && str[i] <= 'Z')
    {
      if(sub_str != "")
      {
        strs_ans.push_back(sub_str);
        sub_str = "";
      }
    }
    sub_str.append(1, str[i]);
  }
  if(sub_str != "")
  {
    strs_ans.push_back(sub_str);
    sub_str = "";
  }
  return strs_ans;
}

int main()
{
  string strA, strB;
  cin >> strA >> strB;

  if(strA == "" || strB == "")
  {
    cout << 0;
    return 0;
  }

  vector<string> vecA = split(strA), vecB = split(strB);
  int n = vecA.size(), m = vecB.size(), dp[n + 5][m + 5], i, j, maxLen = 0;

  memset(dp, 0, sizeof(dp));
  dp[0][0] = vecA[0] == vecB[0];
  for(i = 1; i < n; ++ i)
  {
    dp[i][0] = vecA[i] == vecB[0] ? 1 : dp[i - 1][0];
  }
  for(j = 1; j < m; ++ j)
  {
    dp[0][j] = vecA[0] == vecB[j] ? 1 : dp[0][j - 1];
  }
  for(i = 1; i < n; ++ i)
  {
    for(j = 1; j < m; ++ j)
    {
      if(vecA[i] == vecB[j])
      {
        dp[i][j] = dp[i - 1][j - 1] + 1;
      }
      else
      {
        dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
      }
      maxLen = max(maxLen, dp[i][j]);
    }
  }
  cout << maxLen;
  return 0;
}

九、画廊

题目描述

小蓝办了一个画展,在一个画廊左右两边陈列了他自己的作品。为了使画展更有意思,小蓝没有等距陈列自己的作品,而是按照更有艺术感的方式陈列。

在画廊的左边陈列了 L 幅作品,在画廊的右边陈列了 R 幅作品,左边的作品距离画廊的起点依次为 u_1, u_2, · · · , u_L​,右边的作品距离画廊起点依次为 v_1, v_2, · · · ,v_R。

每周,小蓝要整理一遍自己的每一幅作品。整理一幅作品的时间是固定的,但是要带着沉重的工具。从一幅作品到另一幅作品之间的距离为直线段的长度。

小蓝从画廊的起点的正中央(左右两边的中点)出发,整理好每一幅画,最终到达画廊的终点的正中央。已知画廊的宽为 w。

请问小蓝最少带着工具走多长的距离?

输入描述

输入的第一行包含四个整数 L, R, d, w,表示画廊左边和右边的作品数量,以及画廊的长度和宽度。

第二行包含 L 个正整数 u_1, u_2, · · · , u_L​,表示画廊左边的作品的位置。

第三行包含 R 个正整数 v_1, v_2, · · · , v_R​,表示画廊右边的作品的位置。

其中有 ,1 ≤ L, R ≤ 500, 1 ≤ d ≤ 10^5, 1 ≤ w ≤ 10^5,0 ≤ u_1 < u_2 < · · · < u_L ≤ d, 0 ≤ v_1 < v_2 < · · · < v_R ≤ d。

输出描述

输出一个实数,四舍五入保留两位小数,表示小蓝最少带着工具走的距离。

输入输出样例

示例

输入

3 3 10 2
1 3 8
2 4 6

输出

14.71

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:首先理清题意,走廊示意图如下,题目要求我们选取一种方案,使得我们在经过这些点的路径总长度最短。

 法1:直接贪心,每次对于左右走廊,都选取离各自起点最近的点移动。

 反例:如上图所示,蓝线和红线都可以让我们走完U1、U2、V1、V2,但是不见得蓝线就会比红线短。

法2:我们现在知道法1的贪心是有问题的,但是我们能不能从中得出点启发,为什么我们会想到去贪心离走廊起点更近的点呢?因为直觉上,这样做得到的距离代价会更小。

尽管每次都选择离走廊起点最近的点走的这种直觉不一定成立,但是如果我们只在左边或者右边行走就会发现这样是对的。也就是说,我们最优方案的序列上,一定会有U1<U2<U3<...<UL且V1<V2<V3<...<VR,但是Ui和Vj之间的顺序是没有约束的。

现在我们知道了Ui可以由Ui-1且Vj可以由Vj-1得到,因此我们可以约定状态:

dp[i][j][t]表示左边经过了i个点,右边经过了j个点,t=0表示当前落在左边,t=1表示当前落在右边。

状态转移:

A.对于状态 dp[i][j][0],可能的来源为:

dp[i-1][j][0]+dis(Ui-1,Ui):由上一步落在左边i-1号位置(Ui-1)转移而来

dp[i-1][j][1]+dis(Vi,Ui):由上一步落在右边i号位置(Vi)转移而来

A.对于状态 dp[i][j][1],可能的来源为:

dp[i][j-1][1]+dis(Vj-1,Vj):由上一步落在右边i号的位置(Vj-1)转移而来

dp[i][j-1][0]+dis(Ui,Vi):由上一步落在左边i号的位置(Ui)转移而来

#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn = 507;

int U[maxn], V[maxn];
double dp[maxn][maxn][2];

int main()
{
  int L, R, d, w, i, j;
  scanf("%d%d%d%d", &L, &R, &d, &w);
  for(i = 1; i <= L; ++ i)
  {
    scanf("%d", &U[i]);
  }
  for(j = 1; j <= R; ++ j)
  {
    scanf("%d", &V[j]);
  }
  for (i = 0; i <= L; ++i)
  {
    for (j = 0; j <= R; ++j)
    {
      dp[i][j][0] = dp[i][j][1] = 1e12;
    } 
  } 
  dp[1][0][0] = hypot(w / 2.0, U[1]);
  dp[0][1][1] = hypot(w / 2.0, V[1]);
  for(i = 0; i <= L; ++ i)
  {
    for(j = 0; j <= R; ++ j)
    {
      if(i + j > 1)//合法状态
      {
        if(i)
        {
          dp[i][j][0] = min(dp[i - 1][j][0] + U[i] - U[i - 1], dp[i - 1][j][1] + hypot(w, abs(U[i] - V[j])));
        }
        if(j)
        {
          dp[i][j][1] = min(dp[i][j - 1][1] + V[j] - V[j - 1], dp[i][j - 1][0] + hypot(w, abs(U[i] - V[j])));
        }
      }
    }
  }
  printf("%.2lf", min(dp[L][R][0] + hypot(w / 2.0, d - U[L]), dp[L][R][1] + hypot(w / 2.0, d - V[R])));
  return 0;
}

十、答疑

题目描述

有 n 位同学同时找老师答疑。每位同学都预先估计了自己答疑的时间。

老师可以安排答疑的顺序,同学们要依次进入老师办公室答疑。 一位同学答疑的过程如下:

  1. 首先进入办公室,编号为 i 的同学需要 s_i​ 毫秒的时间。
  2. 然后同学问问题老师解答,编号为 i 的同学需要 a_i 毫秒的时间。
  3. 答疑完成后,同学很高兴,会在课程群里面发一条消息,需要的时间可以忽略。
  4. 最后同学收拾东西离开办公室,需要 e_i 毫秒的时间。一般需要 10 秒、20 秒或 30 秒,即 e_i 取值为 10000,20000 或 30000。

一位同学离开办公室后,紧接着下一位同学就可以进入办公室了。

答疑从 0 时刻开始。老师想合理的安排答疑的顺序,使得同学们在课程群里面发消息的时刻之和最小。

输入描述

输入第一行包含一个整数 n,表示同学的数量。

接下来 nn 行,描述每位同学的时间。其中第 ii 行包含三个整数 s_i, a_i, e_i​,意义如上所述。

其中有 ,1 ≤ n ≤ 1000,1 ≤ s_i ≤ 60000,1 ≤ a_i ≤ 10^6, e_i ∈ {10000, 20000, 30000},即 e_i 一定是 10000、20000、30000 之一。

输出描述

输出一个整数,表示同学们在课程群里面发消息的时刻之和最小是多少。

输入输出样例

示例

输入

3
10000 10000 10000
20000 50000 20000
30000 20000 30000

输出

280000

运行限制

  • 最大运行时间:3s
  • 最大运行内存: 128M

思路:我们先来看一下对于每一个人要求的是什么,对于每一个i号同学,他发消息的时刻为

T_i=\sum_{j=1}^{i-1}(s_j+a_j+e_j)+s_i+a_i

我们会发现,对于第i和i后面的同学,无论前面i-1个人如何调换位置,对i和i后面的同学的发送消息时刻是完全不会有影响的。

假设我们现在有了最优的排列方式,现在我们交换i与j,比较一下交换前后这两人的发送时刻。我们只比较那些发生变化的部分

交换前:

i的贡献:s_i+e_i

i+1~j-1的贡献:(j-i-1)*(s_i+a_i+e_i)

j的贡献:(s_i+a_i+e_i)+(s_j+a_j)

交换后:

i的贡献:s_j+e_j

i+1~j-1的贡献:(j-i-1)*(s_j+a_j+e_j)

j的贡献:(s_j+a_j+e_j)+(s_i+a_i)

i(i,j)j
交换前s_i+e_i(j-i-1)*(s_i+a_i+e_i)(s_i+a_i+e_i)+(s_j+a_j)
交换后s_j+e_j(j-i-1)*(s_j+a_j+e_j)(s_j+a_j+e_j)+(s_i+a_i)

将交换前后的总贡献求和:

贡献值
交换前(j-i)*(s_i+a_i+e_i)+s_i+e_i+s_j+e_j
交换后(j-i)*(s_j+a_j+e_j)+s_j+e_j+s_i+e_i

消去相同的值,我们会发现,表格变为如下:

贡献值
交换前(j-i)*(s_i+a_i+e_i)
交换后(j-i)*(s_j+a_j+e_j)

 所以我们就知道,要想得到最优情况,必须满足s_i+a_i+e_i小的放在前。

#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;

struct Stu{
  long long s, a, e;
  bool operator < (const Stu stu) const {
    int pre = s + a + s + a + e + stu.s + stu.a;
    int p = stu.s + stu.a + stu.s + stu.a + stu.e + s + a;
    return pre < p;
  }
};

int main()
{
  Stu stus[1010];
  memset(stus, 0, sizeof(stus));
  long long n, i, timeSum = 0, timeStart = 0;
  cin >> n;
  for(i = 1; i <= n; ++ i)
  {
    cin >> stus[i].s >> stus[i].a >> stus[i].e;
  }
  sort(stus + 1, stus + 1 + n );
  for(i = 1; i <= n; ++ i)
  {
    timeStart += stus[i].s + stus[i].a;
    timeSum += timeStart;
    timeStart += stus[i].e;
  }
  cout << timeSum;
  return 0;
}
  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值