5.18下午集训队训练题解报告

        第一次写题解,然后今天怀着玄学的心去洛谷签了个到,宜:打模拟赛,AK碾压全场,虽然水平有限,但是今天做题居然真的出人意料的顺,拿了好几个深绿,来看详细内容:

        B:

        你有55种不同面值的硬币,每种硬币的价值等于前55个三角数中的一个: 1, 3, 6, 10, 和 15。这些硬币种类数量充足。你的目标是找到需要的最少硬币数量,使它们的总价值恰好为𝑛。

        我们可以证明答案总是存在。

输入

        第一行包含一个整数𝑡(1≤t≤10^{4})— 测试用例的数量。接下来是每个测试用例的描述。

        每个测试用例的第一行包含一个整数n𝑛(1≤n≤10^{9})— 目标价值。

输出

        对于每个测试用例,输出一个数字 — 需要的最少硬币数量。

示例

InputcopyOutputcopy
14
1
2
3
5
7
11
12
14
16
17
18
20
98
402931328
1
2
1
3
2
2
2
3
2
3
2
2
8
26862090

         初一看,是十分简单的DP题,我也是很快码出了代码,如下:

#include<bits/stdc++.h>

using namespace std;

const int N = 1e8 + 10;	
const int inf = 0x3f3f3f3f;
int type[5] = {1,3,6,10,15};		
int dp[N];

void solve()
{
	memset(dp, inf, sizeof(dp));
		
	dp[0] = 0;
	
	for (int i = 0; i < 5; i++) 
		for (int j = type[i]; j <= N; j++) 
			dp[j] = min(dp[j], dp[j - type[i]] + 1);
}

int main() 
{
	solve();
  
	int T;
	cin >> T;
  
	while (T --)
	{
		int n;
		cin >> n;
		
		cout << dp[n] << endl;
	}
}

        然后就MLE了,一看这数据确实有点大,然后我开始脑子一抽,一片空白,寄了。

        后来写了5题后突然想到我为什么要一点一点算,数据不都有现成的吗,于是醍醐灌顶,大改了一番:

//经过实践得知先提前预处理一遍是不行的,因此我采用了舍弃大部头已知部分的只需要求取余量就行了,总的来说还是脑子不好使 
#include <bits/stdc++.h>

using namespace std;

const int N = 1e6+10;

int T, n, a[100];

void solve()
{
	cin >> n;
	
    int ans = n;
    ans = min(ans, n / 15 + a[n % 15]);
    ans = min(ans, n / 10 + a[n % 10]);
    ans = min(ans, n / 6 + a[n % 6]);
    ans = min(ans, n / 3 + a[n % 3]);
    
    int k = (n - 15) / 15 * 15, m = n - k;
    ans = min(ans, k / 15 + a[m]);
    
    cout << ans << endl;
}

int main()
{
    for(int i = 1; i <= 30; i ++)    
		a[i] = 100;
    a[1] = 1, a[3] = 1, a[6] = 1, a[10] = 1, a[15] = 1;
    
    for(int i = 1; i <= 30; i ++)
    {
        if(i > 1)
            a[i] = min(a[i], a[i - 1] + 1);
        if(i > 3)
            a[i] = min(a[i], a[i - 3] + 1);
        if(i > 6)
            a[i] = min(a[i], a[i - 6] + 1);
        if(i > 10)
            a[i] = min(a[i], a[i - 10] + 1);
    }
    
    cin >> T;
    
    while(T --)
        solve();
}

        这样一改,数据处理量大幅下降,因为如果一个数是对应某一个三角数的倍数,那么只需要在原来的基础上乘上倍数就可以了,DP还是要用的。

 C:

        给定一个包含 n𝑛 个元素的数组 a𝑎,找出表达式的最大值:

                                |𝑎𝑖−𝑎𝑗|+|𝑎𝑗−𝑎𝑘|+|𝑎𝑘−𝑎𝑙|+|𝑎𝑙−𝑎𝑖|

        其中 𝑖、𝑗、𝑘 和 𝑙 是数组 𝑎 的四个 不同 下标,满足 1≤𝑖,𝑗,𝑘,𝑙≤𝑛。

        这里 |𝑥| 表示 𝑥 的绝对值。

输入

        第一行包含一个整数 t𝑡 (1≤𝑡≤500) — 表示测试用例的数量。接下来是每个测试用例的描述。

        每个测试用例的第一行包含一个整数 𝑛 (4≤𝑛≤100) — 给定数组的长度。

        每个测试用例的第二行包含 𝑛 个数 𝑎1,𝑎2,…,𝑎𝑛 (-10^{6}≤𝑎𝑖≤10^{6})。

输出

        对于每个测试用例,输出一个整数 — 最大值。

示例 

InputcopyOutputcopy
5
4
1 1 1 1
5
1 1 2 2 3
8
5 1 3 2 -3 -1 10 3
4
3 3 1 1
4
1 2 2 -1
0
6
38
8
8

        这题确实是一题水题,只需要简单排序一下然后取队首,队尾的值对应就行了:

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 10;

int T, n, a[N];

int main()
{
    cin >> T;
    
    while(T --)
    {
    	cin >> n;
    	
    	for(int i = 1; i <= n; i ++)
    		cin >> a[i];
    		
    	sort(a + 1, a + n + 1);
    	
    	cout << abs(a[n]-a[1])+abs(a[1]-a[n-1])+abs(a[n-1]-a[2])+abs(a[2]-a[n]) << endl;
	}
}

E:

        有一个长度为𝑛的一维网格。网格的第i𝑖个单元格包含一个字符𝑠𝑖,它要么是'<',要么是'>'。

当一个弹球被放置在其中一个单元格上时,它按照以下规则移动:

  • 如果弹球在第𝑖个单元格上且𝑠𝑖是'<',则下一秒弹球向左移动一个单元格。如果𝑠𝑖是'>',则向右移动一个单元格。
  • 弹球移动后,字符𝑠𝑖被反转(即如果𝑠𝑖原来是'<',则变为'>',反之亦然)。
  • 当弹球离开网格时停止移动:要么从左边界离开,要么从右边界离开。

        你需要回答𝑛个独立的查询。在第i𝑖个查询中,一个弹球将被放置在第𝑖个单元格上。请注意,我们总是将弹球放在初始网格上。

        对于每个查询,计算弹球离开网格需要多少秒。可以证明弹球总会在有限步内离开网格。

输入

        每个测试包含多个测试用例。第一行包含测试用例的数量t𝑡(1≤𝑡≤10^{5})。接下来是测试用例的描述。

        每个测试用例的第一行包含一个整数n𝑛(1≤𝑛≤5⋅10^{5})。

        每个测试用例的第二行包含一个长度为n𝑛的字符串𝑠1𝑠2…𝑠𝑛,由字符'<'和'>'组成。

        保证所有测试用例中𝑛的总和不超过5⋅10^{5}

输出

        对于每个测试用例,对于每个𝑖(1≤𝑖≤𝑛),输出当一个弹球最初放置在第i𝑖个单元格时的答案。

示例1

InputcopyOutputcopy
3
3
><<
4
<<<<
6
<><<<>
3 6 5 
1 2 3 4 
1 4 7 10 8 1 

注意

        在第一个测试用例中,弹球的移动情况如下图所示。弹球离开网格需要3秒。

        弹球的移动情况如下图所示。弹球离开网格需要6秒。

        说来也巧,这道题我之前看过题解,是CF上有一次的题,我之前闲着看的时候无意间看到过,这题其实是挺考验思维的,而我就挺会刷小聪明的,如果不是之前看过大佬的题解,可能这题也开不出来。

        先附上代码:

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1000010;

int T, n;
ll a[N], b[N], aa[N], bb[N];
char s[N];

int f(int x) {
    int L = 0, R = n + 1, M;
    while (L + 1 != R) {
        M = (L + R) >> 1;
        if (b[M] < x)
            L = M;
        else
            R = M;
    }
    return R;
}

int ff(int x) {
    int L = 0, R = n + 1, M;
    while (L + 1 != R) {
        M = (L + R) >> 1;
        if (a[n] - a[M - 1] < x) R = M;
        else L = M;
    }
    return L;
}

int main() {
    scanf("%d", &T);
    while (T--) {
        scanf("%d %s", &n, s);
        for (int i = 1; i <= n; i++) 
		{
            b[i] = b[i - 1] + (s[i - 1] == '>');
            a[i] = a[i - 1] + (s[i - 1] == '<');
            bb[i] = bb[i - 1] + i * (s[i - 1] == '>');
            aa[i] = aa[i - 1] + i * (s[i - 1] == '<');
        }
        for (int i = 1; i <= n; i++) 
		{
            if (s[i - 1] == '>') 
			{
                if (b[i] > a[n] - a[i]) 
				{
                    int p = f(b[i] - (a[n] - a[i]));
                    printf("%lld ", 2 * ((aa[n] - aa[i]) - (bb[i] - bb[p - 1])) + i + (n + 1));
                } 
				else 
				{
                    int p = ff((a[n] - a[i]) - b[i] + 1);
                    printf("%lld ", 2 * ((aa[p] - aa[i]) - (bb[i] - bb[0])) + i);
                }
            } 
			else 
			{
                if (b[i] >= a[n] - a[i - 1]) 
				{
                    int p = f(b[i] - (a[n] - a[i - 1]) + 1);
                    printf("%lld ", 2 * ((aa[n] - aa[i - 1]) - (bb[i] - bb[p - 1])) - i + (n + 1));
                } else 
				{
                    int p = ff((a[n] - a[i - 1]) - b[i]);
                    printf("%lld ", 2 * ((aa[p] - aa[i - 1]) - (bb[i] - bb[0])) - i);
                }
            }
        }
        printf("\n");
    }
}

        之前我特别研究过这道题,因此看到的时候也是得心应手,这题采用的是前缀和加二分搜索进行实现的,通过前缀和进行快速的数据处理,将一致的命令信号归到一起,然后通过二分搜索快速处理,维护。因为这涉及到两个方面的知识,所以之后我会优先更新这两方面的内容。这里不做过多讲解。

F:

        给定一个由零和一组成的2×𝑛 网格。设第 𝑖 行和第 𝑗 列的交点处的数字为 𝑎𝑖𝑗。

        有一只蚱蜢位于左上角的单元格 (1,1),只能向右或向下跳一格。它想要到达右下角的单元格 (2,𝑛)。考虑由路径上的单元格中的数字构成的长度为 𝑛+1 的二进制字符串,保持其顺序不变。

你的目标是:

  1. 找到通过选择任意可用路径获得的字典序最小的†字符串;
  2. 找到产生这个字典序最小字符串的路径数量。

        † 如果两个字符串 𝑠 和 𝑡 长度相同,则 𝑠 在第一个位置上与 𝑡 不同时,如果 𝑠 的元素比 𝑡 中对应的元素小,则 𝑠 字典序小于 𝑡。

输入

每个测试包含多个测试用例。第一行包含测试用例的数量 t𝑡 (1≤𝑡≤10^{4})。接下来是测试用例的描述。

每个测试用例的第一行包含一个整数 𝑛 (2≤𝑛≤2⋅10^{5})。

每个测试用例的第二行包含一个二进制字符串 𝑎11𝑎12…𝑎1𝑛 (a1i𝑎1𝑖 要么是 00,要么是 11)。

每个测试用例的第三行包含一个二进制字符串 𝑎21𝑎22…𝑎2𝑛 (a2i𝑎2𝑖 要么是 00,要么是 1)。

保证所有测试用例中的 𝑛 总和不超过 2⋅10^{5}

输出

对于每个测试用例,输出两行:

  1. 通过选择任意可用路径获得的字典序最小的字符串;
  2. 产生这个字符串的路径数量。

示例 1

InputcopyOutputcopy
3
2
00
00
4
1101
1100
8
00100111
11101101
000
2
11000
1
001001101
4

注意

在第一个测试用例中,字典序最小的字符串是 000000。有两条路径可以产生这个字符串:

在第二个测试用例中,字典序最小的字符串是 1100011000。只有一条路径可以产生这个字符串:

         初看这题,仿佛需要用到搜索方面的知识,但是通过简单的观察和思考,其实也挺水的。由于每一次只能往下和往右,所以其实我们只需要找到一个特殊点,从这个点往下走即可。如果右侧字符小于下面字符,一定向右走,这个位置记为𝑥。如果右侧字符大于下面字符,此时一定得向下走,这个位置记为𝑦。

        我们在[𝑥+1,𝑦]之间随时都可以向下走,这样得到的都是最小字典序字符串。

        因此:

#include<bits/stdc++.h>

using namespace std;

int T, n, x, y;

int main() 
{
    cin >> T;
    while (T --) 
	{
		string a, b, ans;
        cin >> n;
        cin >> a >> b;
        a = ' ' + a;
        b = ' ' + b;
        y = n, x = 0;
        
        for (int i = 1; i < n; i++) 
		{
            if (b[i] < a[i + 1]) 
			{
                y = i;
                break;
            }
            if (b[i] > a[i + 1]) 
                x = i;
        }
        if (y == n && x == 0) 
		{
            ans = a + b.back();
            cout << ans << endl;
            cout << n << endl;
        } 
		else 
		{
            ans = a.substr(1, y) + b.substr(y);
            cout << ans << endl;
            if (!x) 
                cout << y << endl;
            else 
                cout << y - x << endl;
        }
    }
}

G:       

        给定一个数组 𝑎1,𝑎2,…,𝑎𝑛。初始时,𝑎𝑖=𝑖 每个 1≤𝑖≤𝑛。

        对于整数 𝑘≥2,操作 swap(𝑘) 定义如下:

  • 设 𝑑 是 𝑘 的最大非本身约数。然后交换元素 𝑎𝑑 和 𝑎𝑘。

假设按照给定顺序对每个𝑖=2,3,…,𝑛 执行 swap(𝑖)。找到 1 在结果数组中的位置。换句话说,找到这样的 j𝑗,使得执行这些操作后 𝑎𝑗=1。

† 整数 𝑥 是 𝑦 的约数,如果存在整数 𝑧 满足 𝑦=𝑥⋅𝑧。

输入

每个测试包含多个测试用例。第一行包含测试用例的数量 𝑡 (1≤𝑡≤10^{4})。接下来是测试用例的描述。

每个测试用例的唯一行包含一个整数 𝑛 (1≤𝑛≤10^{4}) — 数组 𝑎 的长度。

输出

对于每个测试用例,输出 11 在结果数组中的位置。

示例 1

InputcopyOutputcopy
4
1
4
5
120240229
1
4
4
67108864

注意

在第一个测试用例中,数组是 [1],没有执行任何操作。

在第二个测试用例中,𝑎 的变化如下:

  • 初始时,𝑎 是[1,2,3,4]。
  • 执行 swap(2) 后,𝑎 变为 [2_,1_,3,4] (被划线的元素被交换)。
  • 执行 swap(3) 后,𝑎 变为 [3_,1,2_,4]。
  • 执行 swap(4) 后,𝑎 变为 [3,4_,2,1_]。

最终,元素 1 位于索引 4 (即,𝑎4=1)。因此,答案是 4。

        应该是最水的一题,我们不难发现 1 每次只会移动到2的n次幂位置上,我们可以用位运算,十分简洁:

#include<bits/stdc++.h>

using namespace std;

int T, n;

int main() 
{
    cin >> T;
    
    while (T --) 
	{
        cin >> n;
        for (int i = 30; i >= 0; i--)
		{
            if (n >= (1 << i)) 
			{
                cout << (1 << i) << endl;
                break;
            }
        }
    }
}

        本来这场是8开6的,但是有一个交互题是我去网上现场学习的,凭借自己浅薄的认识居然闯过去了,从本质上我并没有将其深刻铭记,所以不予评价,到有一天我真正搞明白后我在来讲这交互题。

  • 34
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值