杭电多校2022.7.26第三场部分题解

Tzz于2022年7月26日在杭电多校联赛中使尽浑身解数终究无力回天做出0题,赛后rating评分0.00再创历史,距今已有1天,望周知,警钟长鸣

B 1002 Boss Rush

题意:你有 n 个技能,这种技能是一种灼烧技能,在len[i]秒的时间内,每秒造成d[i][j] 的伤害(j -> [1, len[i]])。每个技能有t[i]的冷却时间,在冷却时间内不能放其他技能,每个技能只能释放一次,但是会继续灼伤怪物。怪物的血量为m,请问最少多少时间可以杀死怪物。

分析:我们先看题目n的范围,n <= 18,我们发现 2^18 大约是1e6多一点点,恰好满足nlogn的二分复杂度。因此我们考虑能否二分。我们对时间进行二分,然后考虑check函数怎么写。首先可以知道的是,影响时间的因素是技能的顺序。比如一些强力的技能先释放就可以更早的干死怪物。但是我们不能贪心的让某一个技能先释放,因为技能是一段区间的影响。因此我们直接用记忆化搜索在某一个时间内按照某一顺序击杀的最短时间。我们用f[state]表示状态为state的情况下用最优顺序能对怪物造成的最大伤害。state是技能二进制表示,其中第i位为1表示这个技能被使用过了。

关于预处理,因为一个技能是会造成一串的伤害,我们要对一个技能产生的伤害进行预处理。由于我们每个技能的伤害不能打满,因为可能在某一顺序下剩余时间不够的情况,那么我们要处理处每个技能在j时间内打出的伤害,用前缀和处理即可。

ps:注意开long long和快读!!!!!!!!!!!!!!!

#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>

using namespace std;

#define int long long
const int N = 1e6 + 10, M = 100010;

int T;
int f[N];//压缩路径,二进制表示是否已经放了第i个技能
int H, n;
int d[20][M], s[20][M];
int len[20], t[20];

int dfs(int state, int mid)
{
    if (f[state] != -1) return f[state];

    int ans = 0;
    int time = 0;
    for (int i = 0; i < n; i ++ )
    {
        if (state >> i & 1) time += t[i];
    }
    if (time > mid) return f[state] = 0;

    for (int i = 0; i < n; i ++ )
    {
        if (!(state >> i & 1))
            ans = max(ans, s[i][min(len[i], mid - time)] + dfs(state | (1 << i), mid));
        // 加入我们预处理好的伤害值,但是要和mid - time剩余时间取min,因为多出来的时间在二分时间内不合法
    }
    return f[state] = ans;
}

bool check(int mid)
{
    for (int i = 0; i < 1 << n; i ++ ) f[i] = -1;
    return dfs(0, mid) >= H;
}

void solve()
{
    cin >> n >> H;
    int l = 0, r = 0;
    for (int i = 0; i < n; i ++ )
    {
        cin >> t[i] >> len[i];
        r += max(t[i], len[i]);
        for (int j = 1; j <= len[i]; j ++ )
        {
            cin >> d[i][j];
            s[i][j] = s[i][j - 1] + d[i][j];
        }
    }
    
    int limt = r;
    while (l < r)
    {
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    
    if (r >= limt)
        cout << "-1\n";
    else
        cout << r - 1 << "\n";//因为是从时间从0开始的
}


signed main()
{
    ios::sync_with_stdio(0), cin.tie(0);
    cin >> T;
    while (T -- )
    {
        solve();
    }
    return 0;
}

L 1012 Two Permutations

题意:给定两个长度为n的排列A, B和一个由两个排列组成的长度为2*n的数组C,每次从两个排列中任意一个的最左端取出一个数放到C[i]里,请问构造出C数组的方案数。

题解1:记忆化搜索,dp转移方程如下:

if(a[x] == c[x + y - 1]) f[i][ty] += dfs(x + 1, y , 0)

if(b[y] == c[x + y - 1]) f[i][ty] += dfs(x , y + 1, 0)

f[i][ty]表示c的第i-1位是由ty的数组转移而来的所有方案数。

具体的代码如下:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <map>
#include <bitset>
#include <vector>
using namespace std;

#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define x first
#define y second
const int N = 1e6 + 10, mod = 998244353;
typedef long long LL;
typedef pair<int, int> PII;
int T;
int n;
int a[N], b[N], c[N];
int f[N][2];//表示c的第i位的前面一位i-1位是来自 a数组 或 b数组 的方案数

int dfs(int x, int y, int op) //表示用a的x位和b的y位,来匹配c的x+y-1位,且前一位是从op数组进入
{
    if (x > n && y > n) return 1;
    if (f[x + y - 1][op] != -1) return f[x + y - 1][op];
    int ans = 0;
    if (a[x] == c[x + y - 1] && x <= n)
        ans = (ans + dfs(x + 1, y, 0)) % mod;
    if (b[y] == c[x + y - 1] && y <= n)
        ans = (ans + dfs(x, y + 1, 1)) % mod;
    f[x + y - 1][op] = ans;
    return ans;
}

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ ) cin >> b[i];
    for (int i = 1; i <= n + n; i ++ ) cin >> c[i];
    for (int i = 1; i <= n + n + 10; i ++ ) f[i][0] = f[i][1] = -1;
    
    int ans = 0;
    if (a[1] == c[1]) ans = (ans + dfs(2, 1, 0)) % mod;
    if (b[1] == c[1]) ans = (ans + dfs(1, 2, 1)) % mod;
    cout << ans << "\n";
}

int main()
{
    quick_cin();
    cin >> T;
    while (T -- )
    {
        solve();
    }
    return 0;
}

题解2:模拟查找,当我们取下去时,遇到a[i] != b[j]时方案唯一,而当遇到a[i] == b[j]时需要讨论,其中有三种情况:

1.找到从i和j开始的相同的前缀,即a数组和b数组分别从i和j开始两条相同的路径,ans *= 2;

2.虽然a[i] == b[j],但是a[i + 1] == c[k + 1] ,而且 b[j + 1] != c[k + 1],说明a这条路径是唯一的

3.虽然a[i] == b[j],但是b[j + 1] == c[k + 1] ,而且 a[i + 1] != c[k + 1],说明b这条路径是唯一的

寻找方案:先假设从a继续往下走,此时c数组已经处理到k位置。如果a能处理则都处理a,假如a不能继续处理了,我们再看b能否处理。如果都不行就说明先处理a可能是不对的,我们要交换i和j的指针继续判断。

具体代码如下:

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <map>
#include <bitset>
#include <vector>
using namespace std;

#define quick_cin() cin.tie(0),cout.tie(0),ios::sync_with_stdio(false)
#define x first
#define y second
const int N = 1e6 + 10, mod = 998244353;
typedef long long LL;
typedef pair<int, int> PII;
int T;
int n;
int a[N], b[N], c[N];

void solve()
{
    cin >> n;
    for (int i = 1; i <= n; i ++ ) cin >> a[i];
    for (int i = 1; i <= n; i ++ ) cin >> b[i];
    for (int i = 1; i <= n * 2; i ++ ) cin >> c[i];
    
    int ans = 1;
    int i = 1, j = 1, k = 1;
    while (k <= n + n)
    {
        if (c[k] != a[i] && c[k] != b[j])
        {
            cout << "0\n";
            return;
        }
        else if (a[i] != b[j]) //若不等于时方案唯一
        {
            if (a[i] == c[k]) i ++ , k ++ ;
            else j ++ , k ++ ;
        }
        else// 此时a[i] == b[j]
        {
            int i0 = i, j0 = j;
            i ++ , k ++ ;//假设从a开始,看是否可行
            
            while (i - i0 != j - j0) // 长度相同相当于我们可以随便选一条路
            {
                if (c[k] == b[j]) j ++ , k ++ ;
                else if (c[k] == a[i])
                {
                    if (c[k] == b[j0 + (i - i0)])
                        i ++ , k ++ ;//说明此时ab两路相同
                    else
                    {
                        i ++, k ++ ;
                        break;//a唯一
                    }
                }
                else if (c[k] == b[j0 + (i - i0)])//b唯一
                {
                    i -= i0, j -= j0;
                    swap(i, j);
                    i += i0, j += j0;
                    j ++ , k ++ ;
                    break;
                }
                else
                {
                    cout << "0\n";
                    return;
                }
                
            }
            if (i - i0 == j - j0) ans = ans * 2 % mod;
        }
    }
    
    
    cout << ans << "\n";
    
}


int main()
{
    quick_cin();
    cin >> T;
    while (T -- )
    {
        solve();
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值