2022“杭电杯” 中国大学生算法设计超级联赛32 9题解

1002-Boss rush

题目大意:
Boss有H血量,现在有n个技能(1<=n<=18),每个技能都有持续伤害时间,持续时间内每秒都会造成伤害,同时释放每个技能后会进入冷却时间,冷却时间结束后才可以释放另一个技能,每个技能只能释放一次,技能在释放后就会造成伤害。问最少需要多少时间将Boss击败。

思路:
对于每个技能,先预处理伤害的前缀和。
因为技能只有18个,因此可以用二进制01表示一个技能是否释放。
但是这样无法引入时间的维度,因此考虑用二分枚举时间。
于是用dp[i]表示i中为对应位为1的技能释放所能造成的最大伤害,冷却总时间和技能释放顺序无关,可以根据i中1的位来算出,状态转移方程也就很容易得到了。

AC代码

#include <bits/stdc++.h>
const int N = 1e5 + 5;
using namespace std;

long long n, h;
int t[20], len[20];
long long dsum[20][N], dp[(1 << 18) + 5]; // dp[i]中为1的位表示释放了对应的技能

inline int get_t(int x)
{
    int res = 0;
    for (int i = 0; i < n; i++)
        if ((1 << i) & x) res += t[i + 1];
    return res;
}

bool check(int tm)
{
    int bit, ts;
    for (int i = 1; i < (1 << n); i++)
        dp[i] = -1;
    for (int i = 0; i < (1 << n); i++)
    {
        if (dp[i] < 0) continue;
        if (dp[i] >= h) return 1;
        ts = get_t(i);
        if (ts >= tm) continue;
        for (int j = 1; j <= n; j++)
        {
            bit = 1 << (j - 1);
            if (!(bit & i))
            {
                if (ts + len[j] <= tm)
                    dp[i | bit] = max(dp[i] + dsum[j][len[j]], dp[i | bit]);
                else
                    dp[i | bit] = max(dp[i] + dsum[j][tm - ts], dp[i | bit]);
            }
        }
    }
    return 0;
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    for (int i = 0; i < 20; i++)
        dsum[i][0] = 0;
    dp[0] = 0;
    int T;
    cin >> T;
    while (T--)
    {
        int tsum = 0, maxlen = 0;
        cin >> n >> h;
        for (int i = 1; i <= n; i++)
        {
            cin >> t[i] >> len[i];
            tsum += t[i];
            maxlen = max(len[i], maxlen);
            for (int j = 1; j <= len[i]; j++)
            {
                cin >> dsum[i][j];
                dsum[i][j] += dsum[i][j - 1]; //伤害前缀和
            }
        }
        int l = 1, r = tsum + maxlen + 1, mid, ans = -1;
        while (l <= r)
        {
            mid = (l + r) / 2;
            if (check(mid))
            {
                r = mid - 1;
                ans = mid;
            }
            else
                l = mid + 1;
        }
        cout << (ans == -1 ? -1 : ans - 1) << endl;
    }
    return 0;
}

1009-Package Delivery

题目大意:
有n个快递,每个快递有到达时间和最迟取件时间,去一次驿站能取k件快递,问最少几次可以把快递取完。

思路:
可以将快递看作区间[l,r]
贪心地想,每次选取r最小的时间点去取快递一定是最优的。
因此分别对rl进行排序,当取定一个ri时,选取所有l小于ri的区间,用一个堆来维护,将其中r最小的k个区间取出(也可能不足k)。然后重复以上过程直到快递取完。

AC代码

#include <bits/stdc++.h>
const int N = 1e5 + 5;
using namespace std;

struct node
{
    int r, idx;
};
bool operator<(node a, node b) { return a.r > b.r; }
pair<int, int> p[N];
int l[N], r[N];
bool vis[N];
bool cmpl(int a, int b) { return p[a].first < p[b].first; }   //按照左端点对下标进行排序
bool cmpr(int a, int b) { return p[a].second < p[b].second; } //按照右端点对下标进行排序

void solve()
{
    int n, k, ans = 0;
    priority_queue<node> q;
    cin >> n >> k;
    for (int i = 1; i <= n; i++)
    {
        cin >> p[i].first >> p[i].second;
        l[i] = i;
        r[i] = i;
        vis[i] = 0;
    }
    sort(l + 1, l + n + 1, cmpl); //按左端点对下标进行排序
    sort(r + 1, r + n + 1, cmpr); //按右端点对下标进行排序
    for (int i = 1, j = 1; i <= n; i++)
    {
        if (vis[r[i]]) continue;
        while (j <= n && p[l[j]].first < p[r[i]].second) //将所有左端点小于当前右端点的快递入队
        {
            q.push({p[l[j]].second, l[j]});
            j++;
        }
        ans++;
        int cnt = k;
        while (q.size() && cnt--) //取出k个快递
        {
            vis[q.top().idx] = 1;
            q.pop();
        }
    }
    cout << ans << "\n";
}
signed main()
{
    ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
    int T;
    cin >> T;
    while (T--)
        solve();
    return 0;
}

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...
win11本地账户怎么改名?win11很多操作都变了样,用户如果想要...