[BZOJ2138]stone(Hall定理,线段树)

Description

话说Nan在海边等人,预计还要等上M分钟。为了打发时间,他玩起了石子。Nan搬来了N堆石子,编号为1到N,每堆
包含Ai颗石子。每1分钟,Nan会在编号在\([L_i,R_i]\)之间的石堆中挑出任意Ki颗扔向大海(好疼的玩法),如果\([L_i,R_i]\)剩下石子不够\(K_i\)颗,则取尽量地多。为了保留扔石子的新鲜感,Nan保证任意两个区间\([L_i,R_i]\)\([L_j,R_j]\),不会存在\(L_i<=L_j\& R_j<=R_i\)的情况,即任意两段区间不存在包含关系。可是,如果选择不当,可能无法扔出最多的石子,这时Nan就会不高兴了。所以他希望制定一个计划,他告诉你他m分钟打算扔的区间\([L_i,R_i]\)以及\(K_i\)。现在他想你告诉他,在满足前i-1分钟都取到你回答的颗数的情况下,第i分钟最多能取多少个石子。
\(n\le 40000\)

Solution

Hall定理:

设二分图中\(\text{G=< V1,V2,E >}\)\(\text{|V1|=m<=|V2|=n}\) ,\(\text{G}\)中存在从 \(\text{V1}\)\(\text{V2}\)的完全匹配当且仅当\(\text{V1}\)中任意\(\text{k(k=1,2,...,m)}\)个顶点至少与\(\text{V2}\)\(\text{k}\)个顶点是相邻的。

建立二分图匹配模型,左部节点为石头(每个点拆为A[i]个点),右部节点为需求(每个点拆为K[i]个点),从小到大依次加入右部节点,然后询问在之前需求点匹配数不变的情况下该点最多能匹配的点数。

不难发现最后是一部分完美匹配+一部分不完美匹配,根据hall定理,一些点具有完美匹配要求任取左部区间,该区间石头数大于等于包含在区间内的需求数。

那么设 \(a[i]\) 表示右端点 \(\le i\) 的区间需求量, \(b[i]\) 为左端点 \(\le i\) 的区间的需求量。

那么区间 \([l,r]\)

石子数 \(=s[r] - s[l - 1]\)
需求数 \(=a[r] - b[l - 1]\)

那么只要满足 \(\forall 0 \le i < j \le n\)

\[ (s[j] - s[i]) - (a[j] - b[i]) \geq 0 \]

\(f[i] = s[i] - a[i], g[i] = s[i] - b[i]\)

考虑区间 \([l,r]\) 满足 \(k\) 的需求量造成的影响。

\[ f[r...n] -= k\\ g[l...n] -= k \]
接下来分类讨论,列出不等式:

\[ i\in [0, l - 1], j\in [r + 1, n]\\ f[j] - k - g[i] \geq 0\\ k\le min(f[j]) - max(g[i])\\ k\le min(f[r+1...n]) - max(g[1...l-1]) \]

\(k\)\(min(f[r+1...n]) - max(g[0...l-1])\) 时最优。

用线段树维护即可。

Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <fstream>

typedef long long LL;
typedef unsigned long long uLL;

#define SZ(x) ((int)x.size())
#define ALL(x) (x).begin(), (x).end()
#define MP(x, y) std::make_pair(x, y)
#define DE(x) cerr << x << endl;
#define debug(...) fprintf(stderr, __VA_ARGS__)
#define GO cerr << "GO" << endl;
#define rep(i, a, b) for (register int (i) = (a); (i) <= (b); ++(i))

using namespace std;

inline void proc_status()
{
    ifstream t("/proc/self/status");
    cerr << string(istreambuf_iterator<char>(t), istreambuf_iterator<char>()) << endl;
}
inline int read() 
{
    register int x = 0; register int f = 1; register char c;
    while (!isdigit(c = getchar())) if (c == '-') f = -1;
    while (x = (x << 1) + (x << 3) + (c xor 48), isdigit(c = getchar()));
    return x * f;
}
template<class T> inline void write(T x) 
{
    static char stk[30]; static int top = 0;
    if (x < 0) { x = -x, putchar('-'); }
    while (stk[++top] = x % 10 xor 48, x /= 10, x);
    while (putchar(stk[top--]), top);
}
template<typename T> inline bool chkmin(T &a, T b) { return a > b ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return a < b ? a = b, 1 : 0; }

const int maxN = 4e4;

int n, m;
int s[maxN + 2], K[maxN + 2];

#define ls (x << 1)
#define rs (x << 1 | 1)
#define Rson rs, mid + 1, r
#define Lson ls, l, mid
int f[maxN << 2], g[maxN << 2], tagG[maxN << 2], tagF[maxN << 2];

void pushup(int x)
{
    f[x] = min(f[ls], f[rs]);
    g[x] = max(g[ls], g[rs]);
}

void build(int x, int l, int r)
{
    if (l == r) { f[x] = g[x] = s[l]; return; }
    int mid = l + r >> 1;
    build(Lson); build(Rson);
    pushup(x);
}

void pushG(int x, int k)
{
    tagG[x] += k;
    g[x] += k;
}

void pushF(int x, int k)
{
    tagF[x] += k;
    f[x] += k;
}

void pushdown(int x)
{
    if (tagG[x] != 0)
    {
        pushG(ls, tagG[x]);
        pushG(rs, tagG[x]);
        tagG[x] = 0;
    }
    if (tagF[x] != 0)
    {
        pushF(ls, tagF[x]);
        pushF(rs, tagF[x]);
        tagF[x] = 0;
    }
}

void addF(int x, int l, int r, int L, int R, int k)
{
    if (L <= l and r <= R) { return pushF(x, k); }
    int mid = l + r >> 1;
    pushdown(x);
    if (L <= mid) addF(Lson, L, R, k);
    if (R > mid) addF(Rson, L, R, k);
    pushup(x);
}

void addG(int x, int l, int r, int L, int R, int k)
{
    if (L <= l and r <= R) { return pushG(x, k); }
    int mid = l + r >> 1;
    pushdown(x);
    if (L <= mid) addG(Lson, L, R, k);
    if (R > mid) addG(Rson, L, R, k);
    pushup(x);
}

int queryG(int x, int l, int r, int L, int R)
{
    if (L <= l and r <= R) { return g[x]; }
    int mid = l + r >> 1;
    pushdown(x);
    int ans = -0x3f3f3f3f;
    if (L <= mid) chkmax(ans, queryG(Lson, L, R));
    if (mid < R) chkmax(ans, queryG(Rson, L, R));
    return ans;
}

int queryF(int x, int l, int r, int L, int R)
{
    if (L <= l and r <= R) { return f[x]; }
    int mid = l + r >> 1;
    pushdown(x);
    int ans = 0x3f3f3f3f;
    if (L <= mid) chkmin(ans, queryF(Lson, L, R));
    if (mid < R) chkmin(ans, queryF(Rson, L, R));
    return ans;
}

int main() 
{
#ifndef ONLINE_JUDGE
    freopen("stone.in", "r", stdin);
    freopen("stone.out", "w", stdout);
#endif
    int x, y, z, P;
    scanf("%d", &n);
    scanf("%d%d%d%d", &x, &y, &z, &P);
    for (int i = 1; i <= n; ++i)
        s[i] = s[i - 1] + ((i - x) * (i - x) + (i - y) * (i - y) + (i - z) * (i - z)) % P;
    scanf("%d", &m);
    scanf("%d%d%d%d%d%d", &K[1], &K[2], &x, &y, &z, &P);
    for (int i = 3; i <= m; ++i)
        K[i] = (1ll * x * K[i - 1] + 1ll * y * K[i - 2] + z) % P;
    build(1, 0, n);
    for (int i = 1; i <= m; ++i)
    {
        int L, R, k;
        scanf("%d%d", &L, &R);
        printf("%d\n", k = min(queryF(1, 0, n, R, n) - queryG(1, 0, n, 0, L - 1), K[i]));
        addF(1, 0, n, R, n, -k);
        addG(1, 0, n, L, n, -k);
    }
    return 0;
}

转载于:https://www.cnblogs.com/cnyali-Tea/p/11439760.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述 有一个 $n$ 个点的棋盘,每个点上有一个数字 $a_i$,你需要从 $(1,1)$ 走到 $(n,n)$,每次只能往右或往下走,每个格子只能经过一次,路径上的数字和为 $S$。定义一个点 $(x,y)$ 的权值为 $a_x+a_y$,求所有满足条件的路径中,所有点的权值和的最小值。 输入格式 第一行一个整数 $n$。 接下来 $n$ 行,每行 $n$ 个整数,表示棋盘上每个点的数字。 输出格式 输出一个整数,表示所有满足条件的路径中,所有点的权值和的最小值。 数据范围 $1\leq n\leq 300$ 输入样例 3 1 2 3 4 5 6 7 8 9 输出样例 25 算法1 (树形dp) $O(n^3)$ 我们可以先将所有点的权值求出来,然后将其看作是一个有权值的图,问题就转化为了在这个图中求从 $(1,1)$ 到 $(n,n)$ 的所有路径中,所有点的权值和的最小值。 我们可以使用树形dp来解决这个问题,具体来说,我们可以将这个图看作是一棵树,每个点的父节点是它的前驱或者后继,然后我们从根节点开始,依次向下遍历,对于每个节点,我们可以考虑它的两个儿子,如果它的两个儿子都被遍历过了,那么我们就可以计算出从它的左儿子到它的右儿子的路径中,所有点的权值和的最小值,然后再将这个值加上当前节点的权值,就可以得到从根节点到当前节点的路径中,所有点的权值和的最小值。 时间复杂度 树形dp的时间复杂度是 $O(n^3)$。 C++ 代码 算法2 (动态规划) $O(n^3)$ 我们可以使用动态规划来解决这个问题,具体来说,我们可以定义 $f(i,j,s)$ 表示从 $(1,1)$ 到 $(i,j)$ 的所有路径中,所有点的权值和为 $s$ 的最小值,那么我们就可以得到如下的状态转移方程: $$ f(i,j,s)=\min\{f(i-1,j,s-a_{i,j}),f(i,j-1,s-a_{i,j})\} $$ 其中 $a_{i,j}$ 表示点 $(i,j)$ 的权值。 时间复杂度 动态规划的时间复杂度是 $O(n^3)$。 C++ 代码

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值