NOIP联测23

T1.zzy的金牌

比较妙的计数 \(dp\)。一个显然的定义 \(dp[i][j]\) 表示给前 \(i\) 个盒子分配了 \(j\) 个金牌的情况。但是发现会有重复情况出现,按照常人的思路,对于一个集合肯定是有序状态才容易判断,因为无序肉眼很难观察。所以不妨将 \(a\) 排序,并且强制让操作完之后的数组也是有序的。

定义 \(dp[i][j][k]\) 为前 \(i\) 个盒子,分配了 \(j\) 个金牌,给第 \(i\) 个盒子分配了 \(k\) 个金牌。

转移就比较显然了\(dp[i][j][k] += dp[i-1][j-k][t]\ [a_{i-1}+t \le a_i+k]\)

用前缀和优化一下即可。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <iostream>
#include <algorithm>
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std; typedef long long ll;
namespace IO
{
    const int bif = 1 << 18; char buf[bif], *p1, *p2;
    inline char getc() { if (p1 == p2) { p2 = (p1 = buf) + fread(buf, 1, bif, stdin); if (p1 == p2) return EOF; } return *p1++; }
    inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f |= c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
}; using namespace IO;
const int Z = 310; const int mod = 998244353;
inline int min(int a, int b) { return a < b ? a : b; }

int n, m, k, ans;
int a[Z], dp[Z][Z], sum[Z][Z];
sandom main()
{
    fre(orzzy, orzzy);
    n = read(), k = read();
    rep(i, 1, n) a[i] = read();
    sort(a + 1, a + 1 + n);
    sum[0][0] = 1;
    rep(i, 1, n) 
    {
        rep(j, 0, k) rep(s, 0, j)
            (dp[j][s] += sum[j - s][min(a[i] - a[i - 1] + s, j - s)]) %= mod;
        rep(j, 0, k) 
        {
            sum[j][0] = dp[j][0], dp[j][0] = 0;
            rep(s, 1, j) sum[j][s] = (sum[j][s - 1] + dp[j][s]) % mod, dp[j][s] = 0;
        }
    }
    cout << sum[k][k];
    return 0;
}

T2.口粮输送

转换:从一个点把物资运送到另一个点,等价于两个点同时往\(LCA\)处靠拢,在\(LCA\)处交接。
看到树的部分分,先考虑\(dp\)一下。如果子树缺少物资,那就向上传递需要的数量,如果多余,那就向上传递给出的数量,如果给出还不如不给,那肯定选择不给。
观察发现,如果这条边没有给出去物资,同时也不会有物资运进来,那么这条边完全可以删去,树分割成两个树。并且,如果没有了这种边,树形\(dp\)就会非常简洁\(ans=\sum\limits(a-b)-\sum\limits w_i\),发现前半部分贡献固定,后半部分显然是一个最小生成树的形式。因为删去了一些边,那么也就是最后需要构成的是最小生成树森林
发现\(n\le 15\)那就状压枚举那些点构成最小生成树,然后再由多个连通块拼成一个最小生成树森林。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <iostream>
#include <algorithm>
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std; typedef long long ll;
namespace IO
{
    const int bif = 1 << 18; char buf[bif], *p1, *p2;
    inline char getc() { if (p1 == p2) { p2 = (p1 = buf) + fread(buf, 1, bif, stdin); if (p1 == p2) return EOF; } return *p1++; }
    inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f |= c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
}; using namespace IO;
const int Z = 16;

int n, m, k, ans;
int fa[Z];
inline int find(int x) { return x == fa[x] ? x : find(fa[x]); }
int w[Z], dp[1 << Z];
struct edge
{
    int u, v, w;
    friend bool operator <(edge A, edge B) { return A.w < B.w; }
}; edge e[Z * Z];
bool MST(int s)
{
    int tot = 0, sum = 0;
    rep(i, 1, n) fa[i] = i;
    rep(i, 1, n) if (s & (1 << i - 1)) sum += w[i], tot++;
    rep(i, 1, m)
    {
        if (tot == 1) return sum >= 0;//生成树完成
        if (!(s & (1 << e[i].u - 1)) || !(s & (1 << e[i].v - 1))) continue;//不在子集中
        int u = find(e[i].u), v = find(e[i].v);
        if (u != v) fa[u] = v, sum -= e[i].w, tot--;
        if (tot == 1) return sum >= 0;//生成树完成
    }
    return false;//甚至不连通
}

sandom main()
{
    fre(trans, trans);
    int T = read();
    while (T--)
    {
        n = read(), m = read(); k = (1 << n) - 1;
        rep(i, 1, m) e[i].u = read(), e[i].v = read(), e[i].w = read();
        rep(i, 1, n) { int a = read(), b = read(); w[i] = a - b; }
        sort(e + 1, e + 1 + m);
        rep(s, 1, k)
        {
            dp[s] = MST(s);
            for (int t = s; t; t = s & (t - 1))
                dp[s] |= (dp[t] & dp[s ^ t]);//能够拼成最小生成森林
        }
        dp[k] ? puts("Yes") : puts("No");
    }
    return 0;
}

T3.作弊

作弊区间不相交相当于简化了问题。
直接暴力 \(dp\)
定义 \(dp[i]\) 为前 \(i\) 个小朋友的最大收益。\(dp[i] = \max\limits_{0\le j < i }\{dp[j] + sum(j + 1, i)\}\)

对于\(sum(l, r)\)考虑扫描线动态维护。直接维护不太方便,考虑单独一个人对\(sum\)贡献当且仅当:\(max_{l, r}\in[l_i, r_i],i\in [l, r]\)。把 \(max_{l, r}\) 拆分成 \(max\{max_{l, i}, max_{i, r}\}\),利用\(max\)的单调性,分别找到贡献范围,使得 \(l\in [Ll,Rl]\) 时,有 \(max_{l, i}\in[l_i,r_i]\)\(r\in [Lr, Rr]\),有 \(max_{i, r}\in[l_i, r_i]\)。当右端点处于 \([i, Lr]\) 时,左端点需要处于 \([Ll, Rl]\) 才有贡献;当右端点处于 \([Lr, Rr]\) 时,左端点处于 \([Ll, i]\) 都有贡献;此外无贡献。扫描线维护 \(dp\) 最大值即可。

代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <iostream>
#include <cmath>
#include <vector>
#define rep(i, a, b) for (int i = (a); i <= (b); ++i)
using namespace std; typedef long long ll;
namespace IO
{
    const int bif = 1 << 18; char buf[bif], *p1, *p2;
    inline char getc() { if (p1 == p2) { p2 = (p1 = buf) + fread(buf, 1, bif, stdin); if (p1 == p2) return EOF; } return *p1++; }
    inline int read() { int x = 0, f = 0; char c = getc(); while (!isdigit(c)) f |= c == '-', c = getc(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getc(); return f ? -x : x; }
}; using namespace IO;
const int Z = 1e5 + 10;
inline int max(int a, int b) { return a > b ? a : b; } inline int min(int a, int b) { return a < b ? a : b; }

int n, m, k, ans;
int a[Z], l[Z], r[Z];
#define mid (L + R >> 1)
#define lk (rt << 1)
#define rk (rt << 1 | 1)
struct tree { int max, lz; } tr[Z << 2];
inline void change(int rt, int v) { tr[rt].lz += v, tr[rt].max += v; }
inline void pushup(int rt) { tr[rt].max = max(tr[lk].max, tr[rk].max); }
inline void pushdown(int rt) { if (tr[rt].lz) change(lk, tr[rt].lz), change(rk, tr[rt].lz), tr[rt].lz = 0; }
void update(int rt, int L, int R, int l, int r, int v)
{
    if (l > r) return;
    if (l <= L && r >= R) { change(rt, v); return; }
    pushdown(rt);
    if (l <= mid) update(lk, L, mid, l, r, v);
    if (r > mid) update(rk, mid + 1, R, l, r, v);
    pushup(rt);
}
struct ST
{
    int mx[20][Z];
    inline void init()
    {
        rep(i, 1, n) mx[0][i] = a[i];
        int t = log2(n);
        rep(j, 1, t) for (int i = 1; (i + (1 << j) - 1) <= n; ++i)
            mx[j][i] = max(mx[j - 1][i], mx[j - 1][i + (1 << j - 1)]);
    }
    inline int RMQ(int l, int r)
    {
        if (l > r) swap(l, r);
        int t = log2(r - l + 1);
        return max(mx[t][l], mx[t][r - (1 << t) + 1]);
    }
}; ST st;
inline int work1(int L, int R, int i)//第一个<=
{
    int ans = i + 1;
    while (L <= R)
    {
        if (st.RMQ(i, mid) <= r[i]) ans = mid, R = mid - 1;
        else L = mid + 1;
    }
    return ans;
}
inline int work2(int L, int R, int i)//最后一个>=
{
    int ans = 0;
    while (L <= R)
    {
        if (st.RMQ(i, mid) >= l[i]) ans = mid, L = mid + 1;
        else R = mid - 1;
    }
    return ans;
}
inline int work3(int L, int R, int i)//第一个>=
{
    int ans = n + 1;
    while (L <= R)
    {
        if (st.RMQ(i, mid) >= l[i]) ans = mid, R = mid - 1;
        else L = mid + 1;
    }
    return ans;
}
inline int work4(int L, int R, int i)//最后一个<=
{
    int ans = i - 1;
    while (L <= R)
    {
        if (st.RMQ(i, mid) <= r[i]) ans = mid, L = mid + 1;
        else R = mid - 1;
    }
    return ans;
} 
struct nod { int l, r, k; };  vector <nod> opt[Z];

sandom main()
{
    fre(cheat, cheat);
    n = read(); m = n + 1;
    rep(i, 1, n) a[i] = read();
    st.init();
    rep(i, 1, n) l[i] = read(), r[i] = read();
    rep(i, 1, n)
    {
        int ll = work1(1, i, i), lr = work2(1, i, i);
        int rl = work3(i, n, i), rr = work4(i, n, i);
        opt[i].push_back(nod{ll, lr, 1});
        opt[rl].push_back(nod{lr + 1, i, 1});
        opt[rr + 1].push_back(nod{ll, i, -1});
    }
    rep(i, 1, n)
    {
        update(1, 1, m, i, i, tr[1].max);
        for (auto j : opt[i]) update(1, 1, m, j.l, j.r, j.k);
    }
    cout << tr[1].max;
    return 0;
}
posted @ 2022-11-09 14:13  sandom  阅读(32)  评论(6编辑  收藏  举报