2022“杭电杯”中国大学生算法设计超级联赛(3)

更好的阅读体验请转移至我的博客

1002 Boss Rush

题意

有n ( ≤ 18 ) (\le18) (18)个技能和一个有H血量的boss,每个技能在释放后的 t i t_i ti秒内不能释放别的技能(若在 x x x秒释放技能,则在 x + t i x+t_i x+ti秒时可以释放下一个技能),每个技能释放后的 l e n i len_i leni秒后每秒都会对boss造成一定的伤害,问至少多少秒能击败boss,若不能击败输出-1

思路

比赛期间写了很久发现思路错了,正解是二分答案+状压

枚举T秒是否能击败boss,再将问题转化为T秒内能打出的最大伤害,通过状压枚举,感觉像是dp

ll t[N], len[N], d[N], n, H, sum[N];
vector<ll> tot[N];
bool check(int T) {
    for (int i = 0; i < (1 << n); i++) d[i] = -1;
    //d[i]表示i状态下T时间内的最大伤害
    d[0] = 0;
    for (int i = 0; i < (1 << n); i++) {
        if (d[i] >= H) return 1;//能击败
        if (sum[i] > T) continue;
        //状态释放总时间大于T
        assert(d[i] >= 0);
        int now = sum[i];//now为当前状态释放的时间
        for (int j = 0; j < n; j++) {
            if (!(i >> j & 1)) {//在加上第i个技能
                if (now + len[j] - 1 <= T) d[i | (1 << j)] = max(d[i | (1 << j)], d[i] + tot[j].back());
                else d[i | (1 << j)] = max(d[i | (1 << j)], d[i] + tot[j][T - now]);
                //类似dp
            }
        }

    }
    return 0;
}
int main() {
    IOS;
    int T;
    cin >> T;
    while (T--) {
        cin >> n >> H;
        for (int i = 0; i < n; i++) {
            cin >> t[i] >> len[i];
            for (int j = 0; j < len[i]; j++) {
                int x;
                cin >> x;
                tot[i].push_back(x);
            }
            for (int j = 1; j < len[i]; j++) tot[i][j] += tot[i][j - 1];
        }
        sum[0] = 0;
        for (int i = 1; i < (1 << n); i++)//每个状态的技能释放总时间
            sum[i] = sum[i - lowbit(i)] + t[__lg(lowbit(i))];
        int l = 0, r = 1e6, ans = -1;
        while (l <= r) {
            int mid = l + r >> 1;
            if (check(mid)) ans = mid, r = mid - 1;
            else l = mid + 1;
        }
        cout << ans << endl;
        for (int i = 0; i <= n; i++) tot[i].clear();

    }
    return 0;
}

1009 Package Delivery

题意

一共有n件快递,每个快递都必须在 [ l i , r i ] [l_i,r_i] [li,ri]时间内领取,小Q一次最多可以拿k件,问小Q最少可以跑几次

思路

按照每个区间的右端点排序,对于某个快递,若其左端点大于前面所有的右端点,则其必须单独取件,答案+1,并将其右端点插入mp中,否则,则意味着该快点后面有某个区间的右端点,此快递可以与之一起取,记录此时取的快递数量+1,若数量达到k则从mp中删除。

int main() {
    int T;
    cin >> T;
    while (T--) {
        int n, k;
        cin >> n >> k;
        for (int i = 1; i <= n; i++)
            cin >> a[i].first >> a[i].second;
        sort(a + 1, a + 1 + n, [](pii A, pii B) {
            if (A.second == B.second) return A.first < B.first;
            return A.second < B.second;
            });
        map<int, int> mp;
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            auto pos = mp.lower_bound(a[i].first);
            if (pos == mp.end()) {
                if (k == 1) {
                    ans++;
                    continue;
                }
                mp[a[i].second] = 1;
                ans++;
            }
            else {
                mp[pos->first]++;
                if (mp[pos->first] == k) mp.erase(pos);
            }
        }
        cout << ans << endl;
    }
    return 0;
}

1011 Taxi

题意

给定n个点的坐标,每个点都有个点权 w i w_i wi,定义两点间的路径长度为 m i n ( w k , ∣ x i − x ′ ∣ + ∣ y i − y ′ ∣ ) min(w_k,|x_i-x'|+|y_i-y'|) min(wk,xix+yiy),每次询问一个点 ( x ′ , y ′ ) (x',y') (x,y),求距离它最远的点的距离。

思路

不考虑 w i w_i wi 的影响下,题目便是询问最远点的曼哈顿距离,感性理解一下便是询问和左上角,右上角,左下角,右下角的点中最远的一个。详细可以通过枚举展开 ∣ x i − x ′ ∣ + ∣ y i − y ′ ∣ |x_i-x'|+|y_i-y'| xix+yiy,发现只要维护每个点的 x i + y i , x i − y i , − x i + y i , − x i − y i x_i+y_i,x_i-y_i,-x_i+y_i,-x_i-y_i xi+yi,xiyi,xi+yi,xiyi即可,分别对应该点是询问点的右上角,右下角,左上角,左下角。每次询问输出最大值即可。

但是题目中加入了 w i w_i wi,选项,我们把所有点按照 w i w_i wi排序并维护每个点的后缀最大 x i + y i , x i − y i , − x i + y i , − x i − y i x_i+y_i,x_i-y_i,-x_i+y_i,-x_i-y_i xi+yi,xiyi,xi+yi,xiyi,便可以二分寻找答案了:

以下用 d d d代表两点间曼哈顿距离。

m i d mid mid点处 w i w_i wi> d d d,则 d d d产生贡献,更优的答案只会出现在左边

m i d mid mid点处 d > w i d>w_i d>wi,则 w i w_i wi产生贡献,更优的答案只会出现在右边

由此便可确定最优解。

struct node {
    ll x, y, w;
    bool operator<(const node A) {
        return w < A.w;
    }
}a[N];
ll zs[N], zx[N], ys[N], yx[N];
int main() {
    int T;
    cin >> T;
    while (T--) {
        int n, m;
        cin >> n >> m;
        for (int i = 1; i <= n; i++) cin >> a[i].x >> a[i].y >> a[i].w;
        sort(a + 1, a + 1 + n);
        zs[n + 1] = ys[n + 1] = zx[n + 1] = yx[n + 1] = -INF;
        for (int i = n; i >= 1; i--) {
            ll x = a[i].x, y = a[i].y;
            zs[i] = max(zs[i + 1], -x - y);
            ys[i] = max(ys[i + 1], y - x);
            zx[i] = max(zx[i + 1], x - y);
            yx[i] = max(yx[i + 1], x + y);
        }
        while (m--) {
            int x, y;
            cin >> x >> y;
            int l = 1, r = n; ll ans = -INF;
            while (l <= r) {
                int mid = l + r >> 1;
                ll mx = max({ zs[mid] + x + y,ys[mid] - y + x,zx[mid] - x + y,yx[mid] - x - y });
                if (a[mid].w > mx) r = mid - 1, ans = max(ans, mx);
                else l = mid + 1, ans = max(ans, a[mid].w);
            }
            cout << ans << endl;
        }
    }
    return 0;
}

1012 Two Permutations

题意

给定两个排列 P , Q P,Q P,Q和一个数组 S S S,每次可以进行如下操作

  • P P P中第一个数字弹出插入 S ′ S' S
  • Q Q Q中第一个数字弹出插入 S ′ S' S

询问最终有多少方案可以使 S ′ = = S S'==S S==S

思路

d p [ i ] [ p o s ] dp[i][pos] dp[i][pos]表示 S S S数组中第 i i i个数字的前一位(也就是第 i − 1 i-1 i1位)取自第 p o s pos pos个排列时的方案数( p o s = = 1 ∣ ∣ p o s = = 2 pos==1||pos==2 pos==1∣∣pos==2),使用记忆化搜索降低复杂度。

int a[N], b[N], c[N], n;
ll dp[N][2];

int dfs(int x, int y, int pos) {
    if (x > n && y > n) return 1;
    if (dp[x + y - 1][pos] != -1) return dp[x + y - 1][pos];
    ll res = 0;
    if (x <= n && a[x] == c[x + y - 1]) {
        res += dfs(x + 1, y, 0);
    }
    if (y <= n && b[y] == c[x + y - 1]) {
        res += dfs(x, y + 1, 1);
    }
    dp[x + y - 1][pos] = res;
    return res % MOD;
}
int main() {
    int T;
    cin >> T;
    while (T--) {
        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 = 0; i <= n + n; i++) dp[i][0] = dp[i][1] = -1;
        ll ans = 0;
        if (a[1] == c[1]) ans += dfs(2, 1, 0);
        if (b[1] == c[1]) ans += dfs(1, 2, 1);
        cout << ans % MOD << endl;
    }
    return 0;
}

隔壁大佬的双指针写法,若当前两排列指针指向的数字不同,则方案是唯一的,直到某刻两指针指向的数字相同,判断出相同多少位,再判断出中间是否在 S S S中是连续出现的,若是连续出现的,则两指针的移动顺序是可以相互交换的。

这个方法的代码看起来虽然脑溢血,但是实际效率要更高一些。

int n, a[N], b[N], c[N];
void so() {
    int l1 = 1, l2 = 1, l3 = 1; ll ans = 1;
    while (l1 <= n || l2 <= n) {
        if (l1 > n) {
            while (l2 <= n && b[l2] == c[l3]) l2++, l3++;
            if (l2 != n + 1) {
                cout << 0 << endl; return;
            }
            else {
                cout << ans << endl; return;
            }
        }
        else if (l2 > n) {
            while (l1 <= n && a[l1] == c[l3]) l1++, l3++;
            if (l1 != n + 1) {
                cout << 0 << endl; return;
            }
            else {
                cout << ans << endl; return;
            }
        }
        else if (a[l1] != b[l2]) {
            if (l1 <= n && a[l1] == c[l3]) l1++, l3++;
            else if (l2 <= n && b[l2] == c[l3]) l2++, l3++;
            else {
                cout << 0 << endl;
                return;
            }
        }
        else {
            int ed1 = l1, ed2 = l2, init1 = l1, init2 = l2;
            map<int, int> mp;
            while (ed1 <= n && ed2 <= n && a[ed1] == b[ed2]) ed1++, ed2++;
            int id = 0;

            while (l1 <= ed1 && l2 <= ed2) {
                if (l1 <= n && a[l1] == c[l3]) {
                    mp[a[l1]]++;
                    if (mp[a[l1]] % 2 == 0) id--;
                    else id++;
                    if (id == 0) ans = ans * 2 % MOD;
                    l1++; l3++;
                }
                else if (l2 <= n && b[l2] == c[l3]) {
                    mp[b[l2]]++;
                    if (mp[b[l2]] % 2 == 0) id--;
                    else id++;
                    if (id == 0) ans = ans * 2 % MOD;
                    l2++; l3++;
                }
                else {
                    if (l1 == ed1) {
                        int len1 = l1 - init1, len2 = l2 - init2;
                        l1 = init1 + len2; l2 = init2 + len1;
                    }
                    break;
                }
            }
        }
    }
    cout << ans << endl;
}
int main() {
    IOS;
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 1; i <= n; i++) cin >> a[i];
        for (int j = 1; j <= n; j++) cin >> b[j];
        map<int, int> mp;
        for (int i = 1; i <= n * 2; i++) cin >> c[i], mp[c[i]]++;
        for (int i = 1; i <= n; i++) {
            if (mp[i] != 2) {
                cout << 0 << endl;
                goto h;
            }
        }
        so();
    h:;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值