ABC408 位运算拆位+并查集 线段树优化dp+扫描线

E

位运算 拆位 并查集
一张连通图,找到一条连接1,n1,n1,n的简单路径,是的路径边权或起来最小

位运算答案最小,考虑拆位。并且可以从高位到低位贪心,贪心的看能否让每一位为0,这是对的是因为让高位为0,对于让值更小肯定是更优的。

对于第iii位,我们尝试把所有这一位位0的便都连起来,看1,n1,n1,n是否连通,如果联通说明答案的这一位可以为0。否则答案的这一位必须为1,答案加上2i2^i2i

如果我们让第iii位位0了,那么后面在对更低位贪心的时候,就必须保持这一位为0,也就是所有这一位为1的边,后面都不能选了,用一个标志位记录一下每条边还能不能选。

int f[N];
int find(int x)
{
    if (f[x] == x)
        return x;
    return f[x] = find(f[x]);
}
void solve()
{
    int n, m;
    cin >> n >> m;

    vvi e;
    rep(i, 1, m)
    {
        int u, v, w;
        cin >> u >> v >> w;
        e.push_back({u, v, w, 0});
    }

    int ans = 0;
    for (int i = 30; i >= 0; i--)
    {
        rep(j, 1, n)
        {
            f[j] = j;
        }
        for (auto &t : e)
        {
            int u = t[0], v = t[1], w = t[2], used = t[3];
            if (w >> i & 1)
                continue;
            if (used)
                continue;
            int fu = find(u), fv = find(v);
            if (fu != fv)
            {
                f[fu] = fv;
            }
        }

        if (find(1) != find(n))
        {
            ans += 1ll << i;
        }
        else
        {
            for (auto &t : e)
            {
                if (t[2] >> i & 1)
                {
                    t[3] = 1;
                }
            }
        }
    }
    cout << ans;
}

F

线段树优化dp 扫描线

一个排列,hih_ihi能转移到,下标距离他不超过RRR,值比他至少小DDD的另一个位置。从任意位置出发,问最多转移几次?

dpdpdp转移是显然的,考虑一个iii能从哪些jjj转移,显然就是在j∈[i−R,i+R]j∈[i-R,i+R]j[iR,i+R],且hj>=hi+Dh_j>=h_i+Dhj>=hi+D的所有位置中,能转移过来,在他们中选个dpdpdp值最大的。

这里有两重约束,下标这个维度可以用线段树,我们让线段树上位置iii保存dpidp_idpi即可。另外的这个用数据结构不太好办。考虑用扫描线的思想,我们根据hih_ihi从大到小枚举,进行转移,枚举到iii时,才把dpi+Ddp_{i+D}dpi+D加入线段树。这样线段树内的值,就是满足hj>=hi+Dh_j>=h_i+Dhj>=hi+D的所有dpdpdp值,直接查即可。

这其实是一个类似扫描线的思想。

注意答案是移动次数,不是点数,可能需要减一。并且最优值所在终点不一定是111,最后对所有dpdpdpmin⁡\minmin

struct Tree
{
#define ls u << 1
#define rs u << 1 | 1
    struct Node
    {
        int l, r;
        ll mx, add;
    } tr[N << 2];

    void pushup(int u)
    {
        tr[u].mx = max(tr[ls].mx, tr[rs].mx);
    }

    void pushdown(int u)
    {
        if (tr[u].add)
        {
            tr[ls].mx += tr[u].add * (tr[ls].r - tr[ls].l + 1);
            tr[rs].mx += tr[u].add * (tr[rs].r - tr[rs].l + 1);
            tr[ls].add += tr[u].add;
            tr[rs].add += tr[u].add;
            tr[u].add = 0;
        }
    }

    void build(int u, int l, int r)
    {
        tr[u] = {l, r, 0, 0};
        if (l == r)
        {
            tr[u].mx = 0;
            return;
        }
        int mid = (l + r) >> 1;
        build(ls, l, mid);
        build(rs, mid + 1, r);
        pushup(u);
    }

    void modify(int u, int l, int r, int val)
    {
        if (tr[u].l >= l && tr[u].r <= r)
        {
            tr[u].mx += val * (tr[u].r - tr[u].l + 1);
            tr[u].add += val;
            return;
        }
        else
        {
            int mid = (tr[u].l + tr[u].r) >> 1;
            pushdown(u);
            if (mid >= l)
                modify(ls, l, r, val);
            if (r > mid)
                modify(rs, l, r, val);
            pushup(u);
        }
    }

    ll query(int u, int l, int r)
    {
        if (l <= tr[u].l && tr[u].r <= r)
            return tr[u].mx;
        pushdown(u);
        int mid = (tr[u].l + tr[u].r) >> 1;
        if (r <= mid)
            return query(ls, l, r);
        if (l > mid)
            return query(rs, l, r);
        return max(query(ls, l, r), query(rs, l, r));
    }
} t;

void solve()
{
    int n, D, R;
    cin >> n >> D >> R;

    vi h(n + 1), pos(n + 1);
    rep(i, 1, n)
    {
        cin >> h[i];
        pos[h[i]] = i;
    }

    t.build(1, 1, n);
    vi dp(n + 1);
    for (int i = n; i >= 1; i--)
    {
        if (i + D <= n)
        {
            t.modify(1, pos[i + D], pos[i + D], dp[i + D]);
        }

        int mx = t.query(1, max(1ll, pos[i] - R), min(n, pos[i] + R));
        dp[i] = mx + 1;
    }
    int ans = 0;
    rep(i, 1, n)
    {
        ans = max(ans, dp[i]);
    }
    cout << ans - 1;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值