2022杭电中超第三场补题

杭电中超第三场补题

2022.7.27

I Package Delivery

在这里插入图片描述

题意:有多个快递需要拿,每次至多可以拿k个,每个快递有送达时间L与退回时间R,需要在[L,R]内的任意时间取走,问至少需要去拿多少次。注意每天可以去很多次。

思路:如果要尽可能少去拿快递的同时且要全部拿完,则拿快递的最佳时间就是卡在当前最早要退回的快递的那一天去拿。即只有不得不取快递的时候才去取,每次取尽可能多取,但只去一次。

根据思路,每次取快递的时刻都是当前最早到达退回时间的快递的退回时刻,每次最多取k个。这一时刻只去一次。

数组a保存到达时间顺序从早到晚的序列,数组b保存退回时间从早到晚的序列。

for (int i = 1; i <= n; i++)
{
    cin >> a[i].first >> a[i].second;
    b[i].first = a[i].first, b[i].second = a[i].second;
}
sort(a + 1, a + 1 + n, [] (const PII& a, const PII& b) {
    return a.first < b.first;
});
sort(b + 1, b + 1 + n, [] (const PII& a, const PII& b) {
    return a.second < b.second;
});

遍历退回时间最早的快递,若已经被取走则遍历下一个。

st.clear(); //st代表当前快递是否被取走
int ans = 0; //记录拿快递的次数
int t = 1;
priority_queue<PII, vector<PII>, greater<PII>>q; //优先取走退回时间最早的快递
for (int i = 1; i <= n; i++) //从早到晚遍历退回时间,当且仅当不得不取的时候才去取
{
    if (st[b[i]])
    {
        continue;
    }
    ans++;
    while (t <= n && a[t].first <= b[i].second)
    {
        q.push({a[t].second, a[t++].first}); //右端点作为first元素,堆排序默认first从小到大
    }
    int temp = k;
    while (temp--) //每次最多拿k个
    {
        if (!q.size())
        {
            break;
        }
        st[{q.top().second, q.top().first}] = 1; //记为已取走
        q.pop();
    }
}

AC代码:1060ms

#include <iostream>
#include <stdio.h>
#include <unordered_map>
#include <cmath>
#include <queue>
#include <string>
#include <cstring>
#include <cctype>
#include <sstream>
#include <fstream>
#include <cstdio>
#include <stack>
#include <vector>
#include <map>
#include <iomanip>
#include <algorithm>
using namespace std;
#define ll long long
#define PII pair<int, int>
#define PDD pair<double, double>
#define PLL pair<ll, ll>
#define cao                      \
    ios::sync_with_stdio(false); \
    cin.tie(0);                  \
    cout.tie(0)
const int N = 1000010;
int _;
int n, k;
PII a[N], b[N];
map<PII, int> st;

int main(){
    cao;
    cin >> _;
    while (_--)
    {
        cin >> n >> k;
        for (int i = 1; i <= n; i++)
        {
            cin >> a[i].first >> a[i].second;
            b[i].first = a[i].first, b[i].second = a[i].second;
        }
        sort(a + 1, a + 1 + n, [] (const PII& a, const PII& b) {
            return a.first < b.first;
        });
        sort(b + 1, b + 1 + n, [] (const PII& a, const PII& b) {
            return a.second < b.second;
        });
        st.clear();
        int ans = 0;
        int t = 1;
        priority_queue<PII, vector<PII>, greater<PII>> q;
        for (int i = 1; i <= n; i++)
        {
            if (st[b[i]])
            {
                continue;
            }
            ans++;
            while (t <= n && a[t].first <= b[i].second)
            {
                q.push({a[t].second, a[t].first});
                t++;
            }
            int temp = k;
            while (temp--)
            {
                if (!q.size())
                {
                    break;
                }
                st[{q.top().second, q.top().first}] = 1;
                q.pop();
            }
        }
        cout << ans << endl;
    }


    return 0;
}

L Two Permutations

在这里插入图片描述

题意:用已知的两个n个数字的排列,每次只能从两个排列之一的左端取一个数字,最终用两个排列组成一个新的2n长度的与序列S相同的序列R,问有多少种取法。

思路:序列长度最长为30w,考虑线性的或nlogn复杂度的算法。
记状态(i, j, k)表示序列p匹配到下标i位置,q匹配到下标位置,显然s匹配到i + j位置,而k = 0, 1表示当前位置匹配了p的元素还是q的元素时的方案数量。如果i, j均大于等于n,即都匹配完成,则当前状态表示且仅表示了1种匹配方法。记录当前状态使用p序列元素匹配当前位置的新状态为a,使用q序列元素的新状态为b,则当前状态为a + b;

int dfs(int i, int j, int u)
{
    if (i >= n && j >= n)
    {
        return 1;
    }
    if (~f[i + j][u]) //剪
    {
        return f[i + j][u];
    }
    int ans = 0;
    if (i < n && p[i] == s[i + j]) //匹配p序列元素的方案
    {
        ans = (ans + dfs(i + 1, j, 0)) % mod;
    }
    if (j < n && q[j] == s[i + j]) //匹配q序列元素的方案
    {
        ans = (ans + dfs(i, j + 1, 1)) % mod;
    }
    f[i + j][u] = ans;
    return ans;
}

AC代码 1996ms

#include <bits/stdc++.h>
using namespace std;
const int N = 300010;
int p[N], q[N], s[N << 1];
int n, _;
const int mod = 998244353;
int f[N << 1][2];

int dfs(int i, int j, int u)
{
    if (i >= n && j >= n)
    {
        return 1;
    }
    if (~f[i + j][u])
    {
        return f[i + j][u];
    }
    int ans = 0;
    if (i < n && p[i] == s[i + j])
    {
        ans = (ans + dfs(i + 1, j, 0)) % mod;
    }
    if (j < n && q[j] == s[i + j])
    {
        ans = (ans + dfs(i, j + 1, 1)) % mod;
    }
    f[i + j][u] = ans;
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> _;
    while (_--)
    {
        cin >> n;
        memset(f, -1, sizeof f);
        for (int i = 0; i < n; i++)
        {
            cin >> p[i];
        }
        for (int i = 0; i < n; i++)
        {
            cin >> q[i];
        }
        for (int i = 0; i < n << 1; i++)
        {
            cin >> s[i];
        }
        cout << dfs(0, 0, 0) % mod << endl;
    }
    return 0;
}

如果序列下标从1开始,则无法表示第一个状态,即i + j > 1,考虑从第2n位开始反向匹配,因为1 < i + j <= 2n
对于序列下标从0开始,满足0 <= i + j < 2n,应该从第0位开始正向匹配

反向匹配AC,1981ms

#include <bits/stdc++.h>
using namespace std;
const int N = 300010;
int p[N], q[N], s[N << 1];
int n, _;
const int mod = 998244353;
int f[N << 1][2];

int dfs(int i, int j, int u)
{
    if (i <= 0 && j <= 0)
    {
        return 1;
    }
    if (~f[i + j][u])
    {
        return f[i + j][u];
    }
    int ans = 0;
    if (i && p[i] == s[i + j])
    {
        ans = (ans + dfs(i - 1, j, 0)) % mod;
    }
    if (j && q[j] == s[i + j])
    {
        ans = (ans + dfs(i, j - 1, 1)) % mod;
    }
    f[i + j][u] = ans;
    return ans;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> _;
    while (_--)
    {
        cin >> n;
        memset(f, -1, sizeof f);
        for (int i = 1; i <= n; i++)
        {
            cin >> p[i];
        }
        for (int i = 1; i <= n; i++)
        {
            cin >> q[i];
        }
        for (int i = 1; i <= n << 1; i++)
        {
            cin >> s[i];
        }
        cout << dfs(n, n, 0) % mod << endl;
    }
    return 0;
}

K Taxi

在这里插入图片描述

题意:网格上有多个点,每个点有一个权值。现在从网格上任意坐标位置出发,到达每个点的费用是该点权值与当前坐标到该点的曼哈顿距离的最小值。每次询问输出费用的最小值。

思路:根据曼哈顿距离公式可以O(1)时间内算出任意点到该点集的曼哈顿距离。将点集按照权值从小到大排序,维护点集后缀的曼哈顿距离的最大值。如果对于点k,k的权值可以用于更新答案,则k的左侧即到k - 1位置的前缀一定没有可以用于更新的点,因为左侧的点权值均小于等于点k的权值,其答案一定≤k。反之,如果k的权值不能用于更新答案,则需要曼哈顿距离更大的点,答案应该在k的左侧。

曼哈顿距离:即横纵坐标之差的绝对值。

曼哈顿距离公式:
在这里插入图片描述
根据这一公式就可以在O(1)时间内算出任意坐标到所有点的曼哈顿距离最大值。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, q;
struct node {
	int x, y;
	int w;
} p[N];
int a[N], b[N], c[N], d[N];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int _;
	scanf("%d", &_);
	while (_--) {
		scanf("%d%d", &n, &q);
		for (int i = 1; i <= n; i++)
		{
			scanf("%d%d%d", &p[i].x, &p[i].y, &p[i].w);
		}
		a[n + 1] = b[n + 1] = c[n + 1] = d[n + 1] = INT32_MIN;
		sort(p + 1, p + n + 1, [](const node& a, const node& b)
		{
			return a.w < b.w;
		});
		for (int i = n; i > 0; i--)
		{
			a[i] = max(a[i + 1], -p[i].x - p[i].y);
			b[i] = max(b[i + 1], -p[i].x + p[i].y);
			c[i] = max(c[i + 1], p[i].x - p[i].y);
			d[i] = max(d[i + 1], p[i].x + p[i].y);
		}
		while (q--) 
		{
			int x, y;
			scanf("%d%d", &x, &y);
			int ans = 0;
			int l = 1, r = n, mid;
			while (l <= r) 
			{
				mid = l + r >> 1;
				int tmp = max(max(x + y + a[mid], x - y + b[mid]), max(-x + y + c[mid], -x - y + d[mid]));
				if (p[mid].w < tmp) 
				{
					l = mid + 1;
					ans = max(ans, p[mid].w);
				}
				else
				{
					r = mid - 1;
					ans = max(ans, tmp);
				}
			}
			printf("%d\n", ans);
		}
	}
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值