2022蓝桥杯c++B组题目整理

目录

  • A、九进制转十进制
            • 解:
  • B、顺子日期
            • 解:
  • C、刷题统计
            • 解:
  • D、修剪灌木
            • 解:打表找规律
  • E、X 进制减法
            • 解:贪心
  • F、统计子矩阵
            • 解:双指针
  • G、积木画
            • 解:状压dp
  • H、扫雷
            • 解:暴力
  • I、李白打酒加强版
            • 解:记忆化搜索
            • 解:dp
  • J、砍竹子
            • 解:贪心

XCPC也就图一乐,真比赛还得蓝(bao)桥(li)杯

A、九进制转十进制

九进制正整数 (2022)9 转换成十进制等于多少?

解:

2 + 2 ∗ 9 + 2 ∗ 9 ∗ 9 ∗ 9 = 1478 2+2*9+2*9*9*9 = 1478 2+29+2999=1478

B、顺子日期

小明特别喜欢顺子。顺子指的就是连续的三个数字:123,456 等。顺子日期指的就是在日期的 yyyymmdd 表示法中,存在任意连续的三位数是一个顺子的日期。例如 20220123 就是一个顺子日期,因为它出现了一个顺子:123。而 20221023 则不是一个顺子日期,它一个顺子也没有。小明想知道在整个 2022 年份中,一共有多少个顺子日期。

解:

显然我们并不知道012算不算顺子,就当它算吧,题目里出现了210不算,那就当成顺子要升序吧,显然只有出题人知道他在想什么。

string gt(int t)//补前导0
{
    if (t < 10) return "0" + to_string(t);
    return to_string(t); //c++11的库函数,int转string
}
int main()
{
    int y = 2022, m = 1, d = 1;
    int ans = 0;
    while (y == 2022)
    {
        string s = to_string(y) + gt(m) + gt(d);
        bool f = false;
        for (int i = 0; i + 2 < s.size(); i++)
        {
            if (s[i] == s[i + 1] - 1 && s[i] == s[i + 2] - 2) 
            	f = true;
        }
        if (f) cout << s << endl;//看看找的对不对
        ans += f;
        d++;
        switch (m)
        {
            case 2:      //二月,显然2022不是闰年
                if (d == 29) m++, d = 1; break;
            case 4: case 6: case 9: case 11: //小月
                if (d == 31) m++, d = 1; break;
            case 12:      //12月
                if (d == 32) m = d = 1, y++; break;
            default:      //大月
                if (d == 32) m++, d = 1;
        }
    }
    cout << ans << endl;	//14个
}

C、刷题统计

小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a 道题目,周六和周日每天做 b 道题目。请你帮小明计算,按照计划他将在第几天实现做题数大于等于 n 题?

输入格式
输入一行包含三个整数 a,b 和 n。

输出格式
输出一个整数代表天数。

样例输入

10 20 99

样例输出

8

评测用例规模与约定
对于 50% 的评测用例, 1 ≤ a , b , n ≤ 1 0 6 1≤a,b,n≤10^{6} 1a,b,n106
对于 100% 的评测用例, 1 ≤ a , b , n ≤ 1 0 18 1≤a,b,n≤10^{18} 1a,b,n1018

解:

并没有什么技术含量

#define ll long long
int main()
{
    ll a, b, n;
    cin >> a >> b >> n;
    ll week = a * 5 + b * 2;
    ll ans = n / week * 7;
    n %= week;
    for (int i = 1; i <= 5; i++)
    {
        if (n > 0) n -= a, ans++;
    }
    for (int i = 1; i <= 2; i++)
    {
        if (n > 0) n -= b, ans++;
    }
    cout << ans << endl;
}

D、修剪灌木

爱丽丝要完成一项修剪灌木的工作。

有 N 棵灌木整齐的从左到右排成一排。爱丽丝在每天傍晚会修剪一棵灌木,让灌木的高度变为 0 厘米。爱丽丝修剪灌木的顺序是从最左侧的灌木开始,每天向右修剪一棵灌木。当修剪了最右侧的灌木后,她会调转方向,下一天开始向左修剪灌木。直到修剪了最左的灌木后再次调转方向。然后如此循环往复。

灌木每天从早上到傍晚会长高 1 厘米,而其余时间不会长高。在第一天的早晨,所有灌木的高度都是 0 厘米。爱丽丝想知道每棵灌木最高长到多高。

输入格式
一个正整数 N,含义如题面所述。

输出格式
输出 N 行,每行一个整数,第行表示从左到右第 i 棵树最高能长到多高。

样例输入

3

样例输出

4
2
4

评测用例规模与约定
对于 30% 的数据, N ≤ 10 N≤10 N10

对于 100% 的数据, 1 < N ≤ 10000 11<N10000

解:打表找规律
int n;
int a[N];
int mx[N];
int main()
{
	cin>>n;
	for(int i = 1 ; i <= n ; i++)
	{
		for(int j = 1; j <= n ; j++)a[j]++ , mx[j] = max(mx[j] , a[j]);
		a[i] = 0;
	}
	for(int i = n-1 ; i > 1 ; i--)
	{
		for(int j = 1; j <= n ; j++)a[j]++ , mx[j] = max(mx[j] , a[j]);
		a[i] = 0;
	}
	for(int i = 1 ; i <= n ; i++)
	{
		for(int j = 1; j <= n ; j++)a[j]++ , mx[j] = max(mx[j] , a[j]);
		a[i] = 0;
	}
	for(int i = 1 ; i <= n ; i++)cout<<mx[i]<<endl;
}

然后发现很明显的规律,,

4
6 4 4 6
5
8 6 4 6 8
6
10 8 6 6 8 10
7
12 10 8 6 8 10 12

so

int main()
{
	cin>>n;
	int t = n*2-2;
	for(int i = 1 ; i <= n/2 ; i ++)
	{
		cout << t << endl;
		t-=2;
	}
	if((n&1)==0)t += 2;
	for(int i = n/2+1 ; i <= n ; i ++)
	{
		cout << t << endl;
		t+=2;
	}
}

E、X 进制减法

进制规定了数字在数位上逢几进一。

X 进制是一种很神奇的进制,因为其每一数位的进制并不固定!例如说某种 X 进制数,最低数位为二进制,第二数位为十进制,第三数位为八进制,则 X 进制数 321 转换为十进制数为 65。

现在有两个 X 进制表示的整数 A 和 B,但是其具体每一数位的进制还不确定,只知道 A 和 B 是同一进制规则,且每一数位最高为 N 进制,最低为二进制。请你算出 A−B 的结果最小可能是多少。

请注意,你需要保证 A 和 B 在 X 进制下都是合法的,即每一数位上的数字要小于其进制。

输入格式
第一行一个正整数 N,含义如题面所述。

第二行一个正整数 M a M_a Ma,表示 X 进制数 A 的位数。

第三行 M a M_a Ma 个用空格分开的整数,表示 X 进制数 A 按从高位到低位顺序各个数位上的数字在十进制下的表示。

第四行一个正整数 M b M_b Mb,表示 X 进制数 B 的位数。

第五行 M b M_b Mb 个用空格分开的整数,表示 X 进制数 B 按从高位到低位顺序各个数位上的数字在十进制下的表示。

请注意,输入中的所有数字都是十进制的。

输出格式
输出一行一个整数,表示 X 进制数 A−B 的结果的最小可能值转换为十进制后再模 1000000007 的结果。
样例输入
11
3
10 4 0
3
1 2 0
样例输出
94
样例说明
当进制为:最低位 2 进制,第二数位 5 进制,第三数位 11 进制时,减法得到的差最小。此时 A 在十进制下是 108,B 在十进制下是 14,差值是 94。

评测用例规模与约定
对于 30% 的数据, N ≤ 10 , M a , M b ≤ 8 N≤10,M_a,M_b≤8 N10,Ma,Mb8
对于 100% 的数据, 2 ≤ N ≤ 1000 , 1 ≤ M a , M b ≤ 100000 , A ≥ B 2≤N≤1000,1≤M_a,M_b≤100000,A≥B 2N1000,1Ma,Mb100000,AB

解:贪心

不会吧不会吧,都2202年了,不会还有人不会算进制转换吧
108 = ( ( 10 ∗ 5 + 2 ) ∗ 2 + 0 ) ∗ 1 108 = ((10 * 5 + 2 ) * 2 + 0) * 1 108=((105+2)2+0)1
根据我们小学时所学过的减法, A − B A-B AB 只需要把 A A A B B B 对应的每一位相减,负数再向前借位就可以了。我们通过程序实现一下。

const int N = 1e6 + 7;
int n, m;
int a[N], b[N];	//从低位存,a[1]是个位,a[2]是十位...
int sub()
{
    int ans = 0;
    for (int i = n; i; i--)	//A>B -> n>=m
    {
        ans = ans * 10;
        ans +=  a[i] - b[i];	// i>m 时 b[i] = 0;
    }
    return ans;
}

因为 A > B A>B A>B,所以在上面那个过程中 a n s ans ans 一定时刻都是正数(或0)。我们想要结果最小,就只需要每一位的进制都是最小的。而这个代码和这道题只有进制是不同的,所以稍加修改就得到了答案。

const int N = 1e6 + 7;

int n, m;
int a[N], b[N];

int main()
{
    scanf("%d%d", &n, &n);
    for (int i = n; i; i--) scanf("%d", a + i);
    scanf("%d", &m);
    for (int i = m; i; i--) scanf("%d", b + i);
    ll ans = 0;
    for (int i = n; i; i--)
    {
        ans = ans * max({ 2, a[i] + 1, b[i] + 1 }) % mod;
        ans = ((ans + a[i] - b[i]) % mod + mod) % mod;
    }
    printf("%lld\n", ans);
}

F、统计子矩阵

给定一个 N×M 的矩阵 A,请你统计有多少个子矩阵(最小 1×1,最大 N×M) 满足子矩阵中所有数的和不超过给定的整数 K?

输入格式
第一行包含三个整数 N,M 和 K。
之后 N 行每行包含 M 个整数,代表矩阵 A。

输出格式
一个整数代表答案。

样例输入

3 4 10
1 2 3 4
5 6 7 8
9 10 11 12

样例输出

19

样例说明
满足条件的子矩阵一共有 19,包含:

  • 大小为 1×1 的有 10 个。
  • 大小为 1×2 的有 3 个。
  • 大小为 1×3 的有 2 个。
  • 大小为 1×4 的有 1 个。
  • 大小为 2×1 的有 3 个。

评测用例规模与约定
对于 30% 的数据, N , M ≤ 20 N,M≤20 N,M20
对于 70% 的数据, N , M ≤ 100 N,M≤100 N,M100
对于 100% 的数据, 1 ≤ N , M ≤ 500 , 0 ≤ A i , j ≤ 1000 , 1 ≤ K ≤ 250000000 1≤N,M≤500,0≤Ai,j≤1000,1≤K≤250000000 1N,M500,0Ai,j1000,1K250000000

解:双指针

首先我们有一种显然的 O ( n 2 m 2 ) O(n^2m^2) O(n2m2)的做法:枚举起点和终端并预处理二维前缀和,这可以拿 70 % 70\% 70%的分数,显然从编码的时间成本上就是最优先解法了。

但我们不能满足于这种做法,所以就有了一种 O ( n 2 m ) O(n^2m) O(n2m)的做法:
枚举 起始行 和 行数 ,然后遍历列,就得到了一个长度为 m m m 的一维的数组,在这个数组中用双指针计算时间复杂度就是 O ( n 2 ( m + m ) ) O(n^2(m+m)) O(n2(m+m))

如下图中蓝色的部分就是一次枚举得到的新数组,在这个数组中用双指针求一维的问题。
2022蓝桥杯c++B组题目整理_第1张图片
双指针的过程:记录计算过的部分,分为计算过和未计算两部分分别算。

#define ll long long
const int N = 500 + 7;

int n,m;
ll k;
int a[N][N];
ll s[N][N];
ll t[N];
signed main()
{
	scanf("%d%d%lld",&n,&m , &k);
	for(int i = 1 ; i <= n ; i++)
	{
		for(int j = 1 ; j <= m ; j ++)
		{
			scanf("%d",&a[i][j]);
			s[i][j] = s[i-1][j] + a[i][j];	//每一列的前缀和
		}
	}
	ll ans = 0;
	for(int i = 1 ; i <= n ; i++)
	{
		for(int j = 1 ; j+i-1 <= n ; j++)
		{
			for(int k = 1 ; k <= m ; k++) t[k] = s[j+i-1][k] - s[j-1][k];
			int l = 1 , r = 0, la = 0;
			ll s = 0;
			while(l <= m)
			{
				while(l <= r && s > k)s -= t[l++];
				la = max(la , l - 1);
				ans += 1ll*(r-la) * (la-l+1) + 1ll*(r-la+1) * (r-la) / 2;
				la = r;
				if(r<m)s += t[++r];
				else s -= t[l++];
			}
		}
	}
	printf("%lld\n" , ans);
}

注:被卡常了,常数优化↓

const int N = 500 + 7;

int n,m;
int k;
int a[N][N];
int s[N][N];
signed main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i = 1 ; i <= n ; i++)
    {
        for(int j = 1 ; j <= m ; j ++)
        {
            scanf("%d",&a[i][j]);
            s[i][j] = s[i-1][j] + a[i][j];
        }
    }
    ll ans = 0;
    for(int i = 1 ; i <= n ; i++)
    {
        for(int j = i ; j <= n ; j++)
        {
            for(int l = 1 , r = 1 , sum = 0 ; r<=m ; r++)
            {
                sum+=s[j][r] - s[i-1][r];
                while(sum > k)sum-=s[j][l] - s[i-1][l++];
                ans+=r-l+1;
            }
        }
    }
    printf("%lld\n" , ans);
}

G、积木画

小明最近迷上了积木画,有这么两种类型的积木,分别为 I 型(大小为 2 个单位面积)和 L 型(大小为 3 个单位面积):

2022蓝桥杯c++B组题目整理_第2张图片
同时,小明有一块面积大小为 2×N 的画布,画布由 2×N 个 1×1 区域构成。小明需要用以上两种积木将画布拼满,他想知道总共有多少种不同的方式?

积木可以任意旋转,且画布的方向固定。

输入格式
输入一个整数 N,表示画布大小。

输出格式
输出一个整数表示答案。由于答案可能很大,所以输出其对 1000000007 取模后的值。

样例输入

3

样例输出

5

样例说明
五种情况如下图所示,颜色只是为了标识不同的积木:2022蓝桥杯c++B组题目整理_第3张图片

评测用例规模与约定
对于所有测试用例, 1 ≤ N ≤ 10000000 1≤N≤10000000 1N10000000

解:状压dp

二进制表示状态:
d p [ n o w ] [ 0 ] dp[now][0] dp[now][0] 代表当前列刚好填满
d p [ n o w ] [ 1 ] dp[now][1] dp[now][1] 代表当前列填满并向下一列突出了上面的一格
d p [ n o w ] [ 2 ] dp[now][2] dp[now][2] 代表当前列填满并向下一列突出了下面的一格
d p [ n o w ] [ 3 ] dp[now][3] dp[now][3] 代表当前列填满并向下一列突出两格。
d p [ l a ] 代 表 上 一 列 dp[la] 代表上一列 dp[la]

const int mod = 1e9 + 7;
int n;
ll dp[2][4] , la , now = 1;
int main()
{
	cin>>n;
	dp[la][3] = 2;
	dp[la][0] = dp[la][1] = dp[la][2] = 1;
	while(--n)
	{
		dp[now][0] =  dp[la][3];
		dp[now][1] = (dp[la][0] + dp[la][2])%mod;
		dp[now][2] = (dp[la][0] + dp[la][1])%mod;
		dp[now][3] = (dp[la][0] + dp[la][2] + dp[la][1] + dp[la][3])%mod;
		swap(la,now);
	}
	cout<<dp[la][0]<<endl;
}

H、扫雷

小明最近迷上了一款名为《扫雷》的游戏。其中有一个关卡的任务如下,在一个二维平面上放置着 n 个炸雷,第 i 个炸雷 (xi,yi,ri) 表示在坐标 (xi,yi) 处存在一个炸雷,它的爆炸范围是以半径为 ri 的一个圆。

为了顺利通过这片土地,需要玩家进行排雷。玩家可以发射 m 个排雷火箭,小明已经规划好了每个排雷火箭的发射方向,第 j 个排雷火箭 (Xj,Yj,Rj) 表示这个排雷火箭将会在 (Xj,Yj) 处爆炸,它的爆炸范围是以半径为 Rj 的一个圆,在其爆炸范围内的炸雷会被引爆。同时,当炸雷被引爆时,在其爆炸范围内的炸雷也会被引爆。现在小明想知道他这次共引爆了几颗炸雷?

你可以把炸雷和排雷火箭都视为平面上的一个点。一个点处可以存在多个炸雷和排雷火箭。当炸雷位于爆炸范围的边界上时也会被引爆。

输入格式
输入的第一行包含两个整数 n,m。

接下来的 n 行,每行三个整数 xi,yi,ri,表示一个炸雷的信息。

再接下来的 m 行,每行三个整数 Xj,Yj,Rj,表示一个排雷火箭的信息。

输出格式
输出一个整数表示答案。

样例输入

2 1
2 2 4
4 4 2
0 0 5

样例输出

2

样例说明
示例图如下,排雷火箭 1 覆盖了炸雷 1,所以炸雷 1 被排除。炸雷 1 又覆盖了炸雷 2,所以炸雷 2 也被排除。

2022蓝桥杯c++B组题目整理_第4张图片

评测用例规模与约定
对于 40% 的评测用例: 0 ≤ x i , y i , X j , Y j ≤ 1 0 9 , 0 ≤ n , m ≤ 1 0 3 , 1 ≤ r i , R j ≤ 10 0≤x_i,y_i,X_j,Y_j≤10^9,0≤n,m≤10^3,1≤r_i,R_j≤10 0xi,yi,Xj,Yj109,0n,m103,1ri,Rj10
对于 100% 的评测用例: 0 ≤ x i , y i , X j , Y j ≤ 1 0 9 , 0 ≤ n , m ≤ 5 × 1 0 4 , 1 ≤ r i , R j ≤ 10 0≤x_i,y_i,X_j,Y_j≤10^9,0≤n,m≤5×10^4,1≤r_i,R_j≤10 0xi,yi,Xj,Yj109,0n,m5×104,1ri,Rj10

解:暴力

就普遍理性而论,雷爆炸之后就不会长出来,不然这题貌似也做不了。
所以爆炸的雷是有限的,所以用map存下所有的雷,暴力的查找每次爆炸所涉及到的所有点,如果有雷就继续炸,时间复杂度就是 O ( ( n + m ) r 2 l o g n ) O((n+m)r^2logn) O((n+m)r2logn)

#define ll long long
inline ll hs(ll x, ll y)	//哈希
{
	return x*((ll)1e9) + y;
}
//first存当前点雷的个数,second存雷的最大爆炸范围
unordered_map<ll, pair<int,int>> mp;
int n,m;
int ans;
void boom(int x , int y , int r)
{
	for(int i = max(x-r,0) ; i <= x+r ; i++)
	{
		for(int j = max(y-r,0) ; j <= y+r ; j++)
		{
			if((x-i)*(x-i)+(y-j)*(y-j)<=r*r)
			{
				ll t = hs(i,j);
				if(mp.count(t))
				{
					pair<int,int> tmp = mp[t];
					mp.erase(t);	//注意先移除再搜索,不然可能会死递归
					ans += tmp.first;
					boom(i , j , tmp.second);
				}
			}
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	while(n--)
	{
		int x,y,r;
		scanf("%d%d%d",&x,&y,&r);
		ll t = hs(x,y);
		mp[t].first++;
		mp[t].second = max(mp[t].second , r);
	}
	while(m--)
	{
		int x,y,r;
		scanf("%d%d%d",&x,&y,&r);
		boom(x,y,r);
	}
	printf("%d\n",ans);
}

注:寄,复杂度过不去,大雪菜讲解

I、李白打酒加强版

话说大诗人李白,一生好饮。幸好他从不开车。 一天,他提着酒壶,从家里出来,酒壶中有酒 2 斗。他边走边唱:

无事街上走,提壶去打酒。 逢店加一倍,遇花喝一斗。

这一路上,他一共遇到店 N 次,遇到花 M 次。已知最后一次遇到的是花,他正好把酒喝光了。

请你计算李白这一路遇到店和花的顺序,有多少种不同的可能?

注意:壶里没酒(0 斗) 时遇店是合法的,加倍后还是没酒;但是没酒时遇花是不合法的。

输入格式
第一行包含两个整数 N 和 M。

输出格式
输出一个整数表示答案。由于答案可能很大,输出模 1000000007 的结果。

样例输入

5 10

样例输出

14

样例说明
如果我们用 0 代表遇到花,1 代表遇到店,14 种顺序如下:

010101101000000
010110010010000
011000110010000
100010110010000
011001000110000
100011000110000
100100010110000
010110100000100
011001001000100
100011001000100
100100011000100
011010000010100
100100100010100
101000001010100

评测用例规模与约定
对于 40% 的评测用例: 1 ≤ N , M ≤ 10 1≤N,M≤10 1N,M10

对于 100% 的评测用例: 1 ≤ N , M ≤ 100 1≤N,M≤100 1N,M100

解:记忆化搜索

搜,都能搜,dfsyyds!

#define ll long long
const int mod = 1e9 + 7;
ll hs(int a, int b , int now)	//哈希
{
	return a * ((ll)1e8) + b*((ll)1e4)+now;
}
unordered_map<ll, int> mp;
ll dfs(int a, int b, int now)	//有a家店没有遇到,b朵花没有看,壶中有now斗酒的方案数
{
	if (a == 0 && now == b)return 1;			//酒和花相等并且没有店酒刚刚好都喝完。
	if (a < 0 || now <= 0 || now >= b)return 0;		//酒比花多就一定喝不完了。
	ll t = hs(a, b , now);
	if (mp.count(t)) return mp[t];	//记忆化
	return mp[t] = (dfs(a - 1, b, now * 2) + dfs(a, b - 1, now - 1))%mod;
}
int main()
{
	int a, b;
	cin >> a >> b;
	cout << dfs(a, b, 2) << endl;
}
解:dp

d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k] 代表 有 i i i 家店没有遇到, j j j 朵花没有看,壶中有 k k k 斗酒的方案数
k > m k>m k>m 时酒是绝对喝不完的,所以不用考虑,直接一路dp下来即可。
和搜索相比 dp 复杂度更加稳定,而且没有了递归和 map 记忆化的复杂度,所以dp属于更优解。

#define ll long long
const int mod = 1e9 + 7;
ll dp[110][110][110];
int main()
{
    int n, m;
    scanf("%d%d", &n, &m);
    dp[n][m][2] = 1;
    for (int i = n; i >= 0; i--)
    {
        for (int j = m; j >= 0; j--)
        {
            for (int k = 0; k <= m; k++)
            {
                if (i && k*2 <= m)
                    dp[i - 1][j][k*2] = (dp[i - 1][j][k*2] + dp[i][j][k]) % mod;
                if (k && j)
                    dp[i][j - 1][k - 1] = (dp[i][j - 1][k - 1] + dp[i][j][k]) % mod;
            }
        }
    }
    printf("%lld\n", dp[0][1][1]);
}

J、砍竹子

这天,小明在砍竹子,他面前有 n 棵竹子排成一排,一开始第 i 棵竹子的高度为 hi

他觉得一棵一棵砍太慢了,决定使用魔法来砍竹子。魔法可以对连续的一段相同高度的竹子使用,假设这一段竹子的高度为 H,那么使用一次魔法可以把这一段竹子的高度都变为 ⌊√(⌊H/2⌋+1)⌋,其中 ⌊x⌋ 表示对 x 向下取整。小明想知道他最少使用多少次魔法可以让所有的竹子的高度都变为 1。

输入格式
第一行为一个正整数 n,表示竹子的棵数。

第二行共 n 个空格分开的正整数 hi,表示每棵竹子的高度。

输出格式
一个整数表示答案。

样例输入

6
2 1 4 2 6 7

样例输出

5

样例说明
其中一种方案:

2 1 4 2 6 2
2 1 4 2 2 2
2 1 1 2 2 2
1 1 1 2 2 2
1 1 1 1 1 1

共需要 5 步完成。

评测用例规模与约定
对于 20% 的数据,保证 n ≤ 1000 , h i ≤ 1 0 6 n≤1000,h_i≤10^6 n1000,hi106

对于 100% 的数据,保证 n ≤ 2 × 1 0 5 , h i ≤ 1 0 18 n≤2×10^5,h_i≤10^{18} n2×105,hi1018

解:贪心

如果只能一根一根砍,我们很容易就算出来砍的次数。
然后很容易发现使用魔法来砍所减少的次数就是每对相邻竹子的公共的路程,减去即可。
对于一个竹子每次以根号的级别衰减,衰减速度是非常快的,所以一个竹子砍到1的路程上的点是非常少的,试一下1e18,果不其然只需要6次就到了1,所以枚举每个相邻的竹子所到的点即可。
longlong级别的开根会有精度差,所以手写一下。

#define long long
ll msq(ll x)	//手写开根
{
	ll r = sqrt(x)+10;
	for(ll i = max(0ll,r-20) ; i<=r ; i++)
	{
		if(i*i>x)return i-1;
	}
	return r-10;
}
int main()
{
	int n;
	scanf("%d",&n);
	vector<ll> v[2];
	int now = 0 , la = 1;
	ll ans = 0;
	while(n--)
	{
		ll t;
		scanf("%lld",&t);
		v[now].clear();
		while(t > 1)
		{
			v[now].push_back(t);
			t = msq(t/2+1);
		}
		ans += v[now].size();
		for(int i = 0 ; i < v[now].size() ; i ++)
		{
			for(int j = 0 ; j < v[la].size() ; j ++)
			{
				if(v[now][i]==v[la][j])
				{
					ans-=v[now].size() - i;
					goto g;
				}
			}
		}
		g:
		swap(now,la);
	}
	printf("%lld\n",ans);
}

推广一下:代码源
有蓝桥杯民间数据可以交~

注:ACWing,数据更强些。。。。

有错误欢迎指正

你可能感兴趣的:(蓝桥杯,算法)