DMY 周作业 46 简要题解

D

考虑枚举两个区间最终的 \(\text{MEX}\),发现固定 \(\text{MEX} = x\) 时,所有 \(< x\) 的数都要被包含,所有 \(=x\) 的数都不能被包含。合法区间 \((l, r)\) 相当于一个二维平面上的矩形,求其面积即可。

时间复杂度 \(O(n)\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 200005, inf = 0x3f3f3f3f;
int n, a[N], b[N], posa[N], posb[N];
ll ans;
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        posa[a[i]] = i;
    }
    for(int i = 1; i <= n; i++)
    {
        cin >> b[i];
        posb[b[i]] = i;
    }
    int prela = inf, prera = -inf, prelb = inf, prerb = -inf;
    int tmplen = max(posa[1], posb[1]) - min(posa[1], posb[1]) - 1;
    ans = 1 + 1ll * tmplen * (tmplen + 1) / 2;
    tmplen = min(posa[1], posb[1]) - 1;
    ans += 1ll * tmplen * (tmplen + 1) / 2;
    tmplen = n - max(posa[1], posb[1]);
    ans += 1ll * tmplen * (tmplen + 1) / 2;    
    prela = min(prela, posa[1]);
    prera = max(prera, posa[1]);
    prelb = min(prelb, posb[1]);
    prerb = max(prerb, posb[1]);    
    for(int i = 2; i <= n; i++)
    {
        int la = prela, lb = prelb, ra = prera, rb = prerb;
        int lx = min(prela, prelb);
        int rx = max(prera, prerb);
        if(!(lx <= posa[i] && posa[i] <= rx) && !(lx <= posb[i] && posb[i] <= rx))
        {
            int lt = 1, rt = n;
            if(posa[i] < lx) lt = max(lt, posa[i] + 1);
            else rt = min(rt, posa[i] - 1);
            if(posb[i] < lx) lt = max(lt, posb[i] + 1);
            else rt = min(rt, posb[i] - 1);
            ans += 1ll * (lx - lt + 1) * (rt - rx + 1);
        }
        prela = min(prela, posa[i]);
        prera = max(prera, posa[i]);
        prelb = min(prelb, posb[i]);
        prerb = max(prerb, posb[i]);    
    }
    cout << ans;
    return 0;
}

E

Sol.1

对深度启发式合并,然后随便做。时间复杂度 \(O(n\log n)\)

Sol.2

对每个节点的每个深度分别考虑,发现有贡献的情况始终有 \(2^{n - 1}\) 种。具体而言,对于子树内特定层的节点,假设这样的节点有 \(k\) 个,则一共有 \(2^{k - 1}\) 种方案(可以通过二项式定理并错位相减、计数转概率、对称性构造等多种方式证明);对于不在子树内特定层的节点,怎么染色都没有影响,方案数为 \(2^{n - k}\)

于是只需要求每个点子树内的最大深度即可。时间复杂度 \(O(n)\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 200005;
const ll mod = 1e9 + 7;
int n;
ll ans, pw, dp[N];
vector<int> g[N];
void dfs(int u, int fa)
{
    for(auto v : g[u])
    {
        if(v == fa) continue;
        dfs(v, u);
        dp[u] = max(dp[u], dp[v]);
    }
    dp[u]++;
    ans = (ans + dp[u] * pw % mod) % mod;
}
void solve()
{
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        g[i].clear();
        dp[i] = 0;
    }
    pw = 1;
    for(int i = 1; i < n; i++)
    {
        int u, v;
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
        pw = (pw * 2) % mod;
    }
    ans = 0;
    dfs(1, 0);
    cout << ans << "\n";
}
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    int t;
    cin >> t;
    while(t--) solve();
    return 0;
}

F

800 题,建议出题人少玩原神。

静态问题是经典的插入 DP,考虑每个元素插入时在集合中的排名,两个方案不同当且仅当某次插入的排名不同。因此 \(\texttt{<,>}\) 给答案的系数为 \(1\)\(\texttt{?}\) 给答案的系数为 \(i - 1\)。这个可以直接动态维护。注意特判第一个位置不能为 \(\texttt{?}\),否则无解。

线性预处理逆元,时间复杂度 \(O(n)\)

Bonus:如果模数不为质数,则可能出现不存在逆元的情况。此时可以使用线段树进行维护不可减信息。

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 300005;
const ll mod = 998244353;
int n, q;
ll inv[N], ans = 1;
char s[N];
void outp()
{
    if(s[1] == '?') cout << "0\n";
    else cout << ans << "\n";
}
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> q >> s + 1;
    inv[1] = inv[0] = 1;
    for(int i = 2; i < N; i++)
        inv[i] = (mod - mod / i) * inv[mod % i] % mod;
    for(int i = 2; i < n; i++)
        if(s[i] == '?') ans = (ans * (i - 1)) % mod;
    outp();
    while(q--)
    {
        int x; char c;
        cin >> x >> c;
        if(s[x] == '?') ans = (ans * inv[x - 1]) % mod;
        s[x] = c;
        if(s[x] == '?' && x != 1) ans = (ans * (x - 1)) % mod;
        outp();
    }
    return 0;
}

I

Sol.1

没有利用任何 \(\varphi\) 的性质。先线性筛求出 \(\varphi\) 的表,建树,然后容易发现操作 \(2\) 等价于求 \(\sum_{u =l}^r dep_u - dep_{\text{LCA}}\)

维护 \(\sum dep\) 是容易的。只需要考虑如何求 \(dep_{\text{LCA}}\) 即可。

根据树上查询的经典结论,我们求任意相邻两个节点的 \(\text{LCA}\),区间内任意两个相邻节点 \(\text{LCA}\) 的深度的最小值即为整个区间 \(\text{LCA}\) 的深度。

向上跳节点分为两部分,第一部分是两个都没有跳到 \(\text{LCA}\) 处,\(\text{LCA}\) 不变。第二部分是一个跳到了 \(\text{LCA}\) 处,此时深度更浅的那个节点就是 \(\text{LCA}\)

可能要用吉司机维护 \(\sum dep\),因为要始终保证 \(dep\ge 1\)。时间复杂度 \(O(n\log^2n)\)

Sol.2

延续 Sol.1 的做法。但是不用树上查询的结论。

结论 \(1\):一个点集的 \(\text{LCA}\),为 \(dfn\) 最小的点,与 \(dfn\) 最大的点的 \(\text{LCA}\)

可能还是要吉司机。时间复杂度 \(O(n\log^2n)\)

Sol.3

结论 \(2\):值为 \(V\) 的数,每次操作后变为 \(\varphi(V)\),最多执行 \(O(\log V)\) 次操作就能回到 \(1\)

于是直接势能线段树维护即可。

时间复杂度 \(O(n\log n\log V)\)

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc (p << 1)
#define rc ((p << 1) | 1)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 100005, V = 5000005;
int n, q, cnt, a[N], prm[V], phi[V], b[N];
bitset<V> vis;
struct Edge{
    int v, ne;
} e[V];
int h[V], idx;
void add(int u, int v)
{
    e[++idx] = {v, h[u]};
    h[u] = idx;
}
void seive()
{
    vis[0] = vis[1] = phi[1] = 1;
    for(int i = 2; i < V; i++)
    {
        if(!vis[i])
        {
            prm[++cnt] = i;
            phi[i] = i - 1;
        }
        for(int j = 1; j <= cnt && i * prm[j] < V; j++)
        {
            int v = i * prm[j];
            vis[v] = 1;
            if(i % prm[j]) phi[v] = (prm[j] - 1) * phi[i];
            else
            {
                phi[v] = prm[j] * phi[i];
                break;
            }
        }
        add(phi[i], i);
    }
}
int fa[V], dep[V], top[V], son[V], sz[V];
void dfs1(int u, int father)
{
    fa[u] = father; dep[u] = dep[fa[u]] + 1; sz[u] = 1;
    for(int i = h[u]; i ; i = e[i].ne)
    {
        int v = e[i].v;
        if(v == fa[u]) continue;
        dfs1(v, u);
        sz[u] += sz[v];
        if(sz[son[u]] < sz[v]) son[u] = v;
    }
}
void dfs2(int u, int tp)
{
    top[u] = tp; 
    if(!son[u]) return;
    dfs2(son[u], tp);
    for(int i = h[u]; i ; i = e[i].ne)
    {
        int v = e[i].v;
        if(v == fa[u] || v == son[u]) continue;
        dfs2(v, v);
    }
}
int getlca(int u, int v)
{
    while(top[u] != top[v])
    {
        if(dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    if(dep[u] < dep[v]) swap(u, v);
    return v;
}
struct Node{
    ll l, r, mnv, mndep, mxdep, smdep;
};
struct Segtree{
    Node tr[4 * N];
    void pushup(Node &p, Node ls, Node rs)
    {
        p.mnv = min(ls.mnv, rs.mnv);
        p.mndep = min(ls.mndep, rs.mndep);
        p.mxdep = max(ls.mxdep, rs.mxdep);
        p.smdep = ls.smdep + rs.smdep;
    }
    void build(int p, int ln, int rn)
    {
        tr[p] = {ln, rn, b[ln], dep[a[ln]], dep[a[ln]], dep[a[ln]]};
        if(ln == rn) return;
        int mid = (ln + rn) >> 1;
        build(lc, ln, mid);
        build(rc, mid + 1, rn);
        pushup(tr[p], tr[lc], tr[rc]);
    }
    void update(int p, int ln, int rn)
    {
        if(tr[p].mxdep == 1) return;
        if(tr[p].l == tr[p].r)
        {
            tr[p].mndep--;
            tr[p].mxdep--;
            tr[p].smdep--;
            return;
        }
        int mid = (tr[p].l + tr[p].r) >> 1;
        if(ln <= mid) update(lc, ln, rn);
        if(rn >= mid + 1) update(rc, ln, rn);
        pushup(tr[p], tr[lc], tr[rc]);
    }
    Node query(int p, int ln, int rn)
    {
        if(ln <= tr[p].l && tr[p].r <= rn) return tr[p];
        int mid = (tr[p].l + tr[p].r) >> 1;
        if(rn <= mid) return query(lc, ln, rn);
        if(ln >= mid + 1) return query(rc, ln, rn);
        Node tmp;
        pushup(tmp, query(lc, ln, rn), query(rc, ln, rn));
        return tmp;
    }
} tr1;
int main()
{
    //freopen("sample.in", "r", stdin);
    //freopen("sample.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    seive();
    dfs1(1, 0);
    dfs2(1, 1);
    cin >> n >> q;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        if(i > 1) b[i - 1] = dep[getlca(a[i], a[i - 1])];
    }
    tr1.build(1, 1, n);
    while(q--)
    {
        int opt, l, r;
        cin >> opt >> l >> r;
        if(opt == 1)
            tr1.update(1, l, r);
        else
        {
            Node res = tr1.query(1, l, r);
            ll smdep = res.smdep, mndep = res.mndep;
            if(l <= r - 1) mndep = min(mndep, tr1.query(1, l, r - 1).mnv);
            cout << smdep - mndep * (r - l + 1) << "\n";
        }
    }
    return 0;
}

J

很快就想到了钦定最小值然后状压,但是没注意到每个颜色连续成段,于是糊了个神秘 \(O(n\log^2n + 2^mm\log n)\) 的 ST 表二分 + 状压的小丑做法,喜提 TLE #9。卡常一晚上拼尽全力无法通过。

注意到每个组的人数限制与其最小值有关,于是容易想到钦定最小值的做法。\(m\le20\) 启示我们可以使用状压来表示每个组的最小值是否已经被钦定。

注意到随着一个组的最小值不断变小,其限制的最小人数不断增加,但是始终可以找到一个最大的最小值 \(A_j\) 使得 \(pre + 1\sim j\) 里全是一个组。于是可以将 \(A\) 从大到小排序后 DP:\(dp_{msk}\) 表示当前已钦定的组为 \(msk\),需要的 \(A\) 的最小前缀长度。这个 DP 同样可以先暴力设完状态后,通过交换维度理解。

如果是刷表法转移,则可以转化为对于每一个 \((i, j)\),找到最小的 \(k\),使得 \(i + size_{j, k} \le k\)。其中 \(i\) 表示当前要转移的 DP 值,\(j\) 表示转移的组的编号,\(k\) 表示转移后的 DP 值。因为 \(size_{j, k}\) 单调不降,于是可以对每个组预处理,双指针维护最小的 \(k\)。这也侧面印证了每个颜色连续成段的结论。

时间复杂度 \(O(nm+2^mm)\)。构造方案则直接在转移的时候记录转移前驱即可。

#include <bits/stdc++.h>
#define fi first
#define se second
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
#define lc(x) (tr[x].ls)
#define rc(x) (tr[x].rs)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi = pair<int, int>;
const int N = 200005, M = 22, V = 1100005, inf = 0x3f3f3f3f;
int n, m, a[N], b[N], mnsz[M][N], orip[N], mnto[M][N], dp[V];
pi frm[V], oris[N];
bool cmp(int x, int y)
{
    return (x > y);
}
bool cmp2(pi x, pi y)
{
    return (x > y);
}
void upd(int id, int x, pi pre)
{
    if(x < dp[id])
    {
        dp[id] = x;
        frm[id] = pre;
    }
}
vector<int> choose[M];
int main()
{
    // freopen("CF1886E.in", "r", stdin);
    // freopen("CF1886E.out", "w", stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> n >> m;
    for(int i = 1; i <= n; i++)
    {
        cin >> a[i];
        oris[i] = {a[i], i};
    }
    for(int i = 1; i <= m; i++) cin >> b[i];
    sort(a + 1, a + n + 1, cmp);
    sort(oris + 1, oris + n + 1, cmp2);
    for(int i = 1; i <= n; i++) orip[i] = oris[i].se;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            mnsz[j][i] = ((b[j] + a[i] - 1) / a[i]);
    for(int i = 1; i <= m; i++)
    {
        int p = 1;
        for(int j = 0; j <= n; j++)
        {
            while(p <= n && (p <= j || j > p - mnsz[i][p])) p++;
            mnto[i][j] = p;
        }
    }
    memset(dp, 0x3f, sizeof(dp));
    dp[0] = 0;
    for(int i = 0; i < (1 << m) - 1; i++)
    {
        if(dp[i] >= n) continue;
        for(int j = 1; j <= m; j++)
        {
            if((i >> (j - 1)) & 1) continue;
            int v = (i | (1 << (j - 1)));
            upd(v, mnto[j][dp[i]], make_pair(dp[i], j));
        }
    }
    if(dp[(1 << m) - 1] > n)
    {
        cout << "NO";
        return 0;
    }
    cout << "YES\n";
    int now = (1 << m) - 1, ncnt = 0;
    while(now)
    {
        pi fv = frm[now];
        int presz = fv.fi, prex = fv.se;
        for(int i = presz + 1; i <= dp[now]; i++)
            choose[prex].push_back(orip[i]);
        now ^= (1 << (prex - 1));
    }
    for(int i = 1; i <= m; i++)
    {
        cout << choose[i].size() << " ";
        for(auto itm : choose[i]) cout << itm << " ";
        cout << "\n";
    }
    return 0;
}
posted @ 2025-12-04 21:23  KS_Fszha  阅读(11)  评论(0)    收藏  举报