3.15做题记录

CF626F Group Projects

神仙dp思路
我们不记录总共的组数,而是规定每段的最小和最大,然后类似括号匹配那样去dp

首先将数组排序,从小到大排序
f [ i ] [ j ] [ k ] [ t ] f[i][j][k][t] f[i][j][k][t]表示前 i 个数,还剩 j 个左括号没有匹配(也就是有 j 段规定了最小值,没规定最大值),目前的不和谐度之和为 k,左右端点填了 t 个的方案数
考虑当前插入第 i+1 个数,有以下几种情况
插入到一段的中间:不作为最小/最大值,不产生贡献
作为一个新的左端点:左括号多一个,不和谐度-a[i+1]
作为一个右端点:左括号少一个,不和谐度+a[i+1]
这里没有详细写端点的情况,仔细讨论即可

但是这样由于不和谐度有加有减,所以 k 那一维需记到 ∑ a [ i ] \sum a[i] a[i]
但是由于不和谐度有限制,显然有很多浪费

考虑如何能让贡献一直递增,我们把贡献拆开
a [ j ] − a [ i ] = ∑ i ≤ t < j a [ t + 1 ] − a [ t ] a[j]-a[i]=\sum_{i \le t <j }a[t+1]-a[t] a[j]a[i]=it<ja[t+1]a[t]
这样我们每次计算相邻两项差的贡献次数为尚未匹配的左端点的个数,因为这些段的左右端点一定是一个小于i+1,一个大于i+1,肯定会有这个差的贡献。
至此得到了一个 k 维递增的转移,这样超过上限的就不用计算了
具体转移见代码

代码

loj2743. 「JOI Open 2016」摩天大楼

状态设计基本同上,先给数组从小到大排序,设 f [ i ] [ j ] [ k ] [ t ] f[i][j][k][t] f[i][j][k][t]表示前 i 个数,有 j 个连通部分,目前的参差值为 k,左右端点填了 t 个的方案数

还是像上一道题一样考虑两个相邻的数差的贡献
考虑当前插入第 i+1 个数,有以下几种情况:
插入到一段左侧/右侧:段数不变,参差值+=差 * 端点数
自己单独开一段:段数+1,参差值+=差 * 端点数

具体细节见代码


代码

ybtoj771. 「分块算法」历史序列

分块后给每一段内部从小到大排序,记录pos[i]表示排序后 i 位置对应值的初始编号是多少

对于区间加法,散点下传标记加完后,利用归并维护有序,整块记录到标记上
对于区间取max,散点下传标记直接修改,整块我们记录上这个操作
重点再如何下传标记,对于一个块的标记一定是加,max,加,max这样的形式(相邻的两个加法可以合并,相邻的两个max也可以),记录 s i = d m a x − d a d d s_i=d_{max}-d_{add} si=dmaxdadd,如果 s i > s j s_i>s_j si>sj 且 j>i ,那么 j 操作就没有必要,所以维护 s i s_i si的单调递增序列,用一个vector即可存储

又由于块内部有序,我们可以双指针来更新块内部的值和修改次数,详见代码

这道题的坑点在于加法可能加0,不会对修改次数产生贡献,卡了好久,写的对拍由于暴力也是错的,拍了个寂寞。。。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
const int maxn = 2e5 + 5;
int n, sz, bl[maxn], br[maxn], id[maxn];
ll a[maxn];
int pos[maxn];
struct point {
    ll v;
    int id;
} p[maxn];
bool cmp(point x, point y) { return x.v < y.v; }
int m;
ll delta[maxn];
int cnt[maxn], ans[maxn];
int q1[maxn], q2[maxn];
vector<ll> G[1000];
void pushdown(int no) {
    int l = bl[no], r = br[no];
    int siz = G[no].size();
    if (!siz) {
        for (int i = l; i <= r; i++) ans[i] += cnt[no], a[i] += delta[no];
        cnt[no] = delta[no] = 0;
        return;
    }
    int j = 0, gs = cnt[no] + siz;
    ll final = G[no][siz - 1];
    for (int i = l; i <= r; i++) {
        int k = pos[i];
        ll d = G[no][k];
        while (j < siz && G[no][j] <= a[k]) j++, gs--;
        ans[k] += gs;
        if (j != siz)
            a[k] = final;
    }
    G[no].clear();
    cnt[no] = 0;
    for (int i = l; i <= r; i++) a[i] += delta[no];
    delta[no] = 0;
}
void add(int l, int r, ll z) {
    int no = id[l];
    int cnt1 = 0, cnt2 = 0;
    pushdown(no);
    for (int i = bl[no]; i <= br[no]; i++) {
        int k = pos[i];
        if (l <= k && k <= r)
            q1[++cnt1] = k;
        else
            q2[++cnt2] = k;
    }
    int now1 = 1, now2 = 1;
    for (int i = bl[no]; i <= br[no]; i++) {
        if (now2 > cnt2 || (now1 <= cnt1 && a[q1[now1]] + z <= a[q2[now2]])) {
            int k = q1[now1++];
            ans[k]++;
            a[k] += z;
            pos[i] = k;
        } else
            pos[i] = q2[now2++];
    }
}
void modify(int l, int r, ll z) {
    int no = id[l];
    int cnt1 = 0, cnt2 = 0;
    pushdown(no);
    for (int i = bl[no]; i <= br[no]; i++) {
        int k = pos[i];
        if (l <= k && k <= r && a[k] < z)
            q1[++cnt1] = k;
        else
            q2[++cnt2] = k;
    }
    int now1 = 1, now2 = 1;
    for (int i = bl[no]; i <= br[no]; i++) {
        if (now2 > cnt2 || (now1 <= cnt1 && z <= a[q2[now2]])) {
            int k = q1[now1++];
            ans[k]++;
            a[k] = z;
            pos[i] = k;
        } else
            pos[i] = q2[now2++];
    }
}
signed main() {
    freopen("seq.in", "r", stdin);
    freopen("seq.out", "w", stdout);
    scanf("%lld", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%lld", &a[i]);
        p[i].v = a[i];
        p[i].id = i;
    }
    sz = sqrt(n);
    for (int i = 1; (i - 1) * sz < n; i++) {
        bl[i] = br[i - 1] + 1;
        br[i] = min(i * sz, n);
        for (int j = bl[i]; j <= br[i]; j++) id[j] = i;
        sort(p + bl[i], p + br[i] + 1, cmp);
        for (int j = bl[i]; j <= br[i]; j++) pos[j] = p[j].id;
    }
    char op[3];
    int x, y, z;
    scanf("%lld", &m);
    for (int i = 1; i <= m; i++) {
        scanf("%s", op + 1);
        if (op[1] == 'A') {
            scanf("%lld%lld%lld", &x, &y, &z);
            if (!z)
                continue;
            int nx = id[x], ny = id[y];
            if (nx == ny) {
                add(x, y, z);
                continue;
            }
            add(x, br[nx], z);
            add(bl[ny], y, z);
            for (int j = id[x] + 1; j < id[y]; j++) delta[j] += z, cnt[j]++;
        }
        if (op[1] == 'M') {
            scanf("%lld%lld%lld", &x, &y, &z);
            int nx = id[x], ny = id[y];
            if (nx == ny) {
                modify(x, y, z);
                continue;
            }
            modify(x, br[nx], z);
            modify(bl[ny], y, z);
            for (int j = id[x] + 1; j < id[y]; j++) {
                if (G[j].empty() || G[j].back() < z - delta[j])
                    G[j].push_back(z - delta[j]);
            }
        }
        if (op[1] == 'Q') {
            scanf("%lld", &x);
            int no = id[x];
            pushdown(no);
            printf("%lld %lld\n", a[x], ans[x]);
        }
    }
    return 0;
}


P4577 [FJOI2018]领导集团问题

树上LIS问题,每个点 u 记录一个set, f u , i f_{u,i} fu,i表示u这个点的子树内,选大小为 i 的连通块最小值的最大为多少

这样就可以做树上启发式合并,合并儿子节点的set,表示u这个点不选。
二分set中第一个小于val[u]的位置,可以把这个位置改为val[u]

代码

P6845 [CEOI2019] Dynamic Diameter

看起来可以ddp

直径=任意两点间距离的max
d i s t a n c e ( u , v ) = d i s ( u ) + d i s ( v ) − 2 ∗ d i s ( l c a ) distance(u,v)=dis(u)+dis(v)-2*dis(lca) distance(u,v)=dis(u)+dis(v)2dis(lca)
考虑用欧拉序求 lca 的方式,lca为欧拉序中u,v之间的深度最小的点,也就是u,v之间dis最小的值,那么问题就是区间内满足Euler[u] < lca < Euler[v] 的 dis(u)+dis(v)-2*dis(lca)最大值,由于树的形态固定,意味着欧拉序固定,可以使用线段树

我们记录区间now的以下内容:
mxx,mnx:区间内最大和最小的dis
lmx,rmx:区间内最大的dis(u)-2dis(x) (u<x),dis(u)-2dis(x) (u>x)
mx:区间内最大的dis(u)-2*dis(x)+dis(v) (u<x<v)

对于修改一个边权,只会印象到深度较深那个点的子树内的值,相当于区间修改

然后处理好pushup的部分就好了


代码

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值