\(\cal T_1\)

\(\mathbb{D}\rm escription\)

对于一个长度为 \(m\) 的非负整数序列 \(b\),如果能通过下述两个操作使得序列变成全零,则称这个序列是好的:

  1. 选择 \(1\le i\le m\)\(b_i\)\(2\)
  2. 选择 \(1\le i<m\)\(b_{i+1}\) 以及 \(b_i\)\(1\).

现在给出一个长度为 \(n\) 的序列 \(a\)\(q\) 次询问,每次询问一个区间中有多少子区间是好的。

\(1\le n,q\le 5\cdot 10^5,0\le a_i\le 10^9\).

\(\mathbb{S}\rm olution\)

首先容易观察得到:一个子区间是好的的标志是,在被 \(0\) 划分出的每一段内,所有数之和为偶。一个证明的思路是从前往后两个两个地消,如果同奇偶就直接消完,否则如果两个数都 不为零,总可以让前面那个数变成零,后面那个数为非零数……最后由于所有数之和为偶,所以一定能消完。

那怎么统计查询区间 \([l,r]\) 之中好的子区间呢?这种子区间问题已经有一个固定的思考方式就是扫描线 + 线段树了,这里向左移动左端点 \(i\),先将左端点为 \(i\) 的好区间的右端点在线段树上加一,对于查询 \([i,r]\) 就查询线段树上的 \([i,r]\) 即可。不过直接处理加一操作并不是很好搞,可以分前缀和奇偶性(令其为 \(s_i\))为 \(0/1\) 两类来讨论:加入 \(i\) 时,就在 \(s_j\)\(s_{i-1}\) 的线段树上加一,加一区间为 \([i,\text{R}_{s_{i-1}}]\),其中 \(\text{R}_{0/1}\) 为前缀和奇偶性为 \(0/1\) 能拓展的最右点。比如说 \(a_i=0\),那么假设走到 \(s_{i'-1}\)\(s_{i-1}\) 不同的左端点 \(i'\),此时 \(i'\)\(i-1\) 之后的右端点都无法配对了,因为 \([i',i)\) 这个区间不合法。此时我们将 \(\text{R}_{s_{i'-1}}\) 置为 \(i-1\).

如果想要更快的话,可以将线段树换成树状数组(这里以前缀和奇偶性为 \(0\) 的数据结构为例)。树状数组区间加 + 区间求和是经典的,此时我们只是将求和的柿子换成了 \(\sum_{i=1}^n a_i\cdot r_i\),其中 \(r_i\) 表示 \(s_i\) 是否等于零。令 \(\text{suf}_i=\sum_{j=i}^n r_j,c_i=a_i-a_{i-1}\),我们可以稍微变化一下

\[\begin{align}\sum_{i=1}^m a_i\cdot r_i&=\sum_{i=1}^m r_i\cdot \sum_{j=1}^i c_j\\&=\sum_{i=1}^m c_i\cdot \text{suf}_i-\text{suf}_{m+1}\cdot \sum_{i=1}^m c_i\end{align} \]

然后就可以做啦!

$\mathbb{C}\rm ode $

# include <cstdio>
# include <cctype>
# define print(x,y) write(x),putchar(y)

template <class T>
inline T read(const T sample) {
	T x=0; char s; bool f=0;
	while(!isdigit(s=getchar())) f|=(s=='-');
	for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
	return f? -x: x;
}
template <class T>
inline void write(T x) {
	static int writ[50], w_tp=0;
	if(x<0) putchar('-'), x=-x;
	do writ[++w_tp]=x-x/10*10, x/=10; while(x);
	while(putchar(writ[w_tp--]^48), w_tp);
}

# include <vector>
using namespace std;
typedef long long ll;
typedef pair <int,int> par;

const int maxn = 5e5+5;

ll ans[maxn];
vector <par> Q[maxn];
int n,q,a[maxn],b[maxn];

struct FwTree {

int c1[maxn];
ll c2[maxn],suf[maxn];

inline int lowbit(int x) { return x&-x; }
inline void Add(int x,const int& k) { 
	const int coe = suf[x]*k;
	for(; x<=n; x+=lowbit(x)) 
		c1[x]+=k, c2[x]+=coe;
}
inline void add(int l,int r) { Add(l,1), Add(r+1,-1); }
inline ll ask(int x,ll ret1=0,ll ret2=0) {
	const int coe = suf[x+1];
	for(; x; x-=lowbit(x)) 
		ret1+=c1[x], ret2+=c2[x];
	return ret2-ret1*coe;
}

} T[2];

int main() {
	freopen("a.in","r",stdin);
	freopen("a.out","w",stdout);
	n=read(9), q=read(9);
	for(int i=1;i<=n;++i)
		b[i] = b[i-1]^((a[i]=read(9))&1), ++ T[b[i]].suf[i];
	for(int i=n;i;--i)
		T[0].suf[i] += T[0].suf[i+1], T[1].suf[i] += T[1].suf[i+1];
	for(int i=1;i<=q;++i) {
		int l=read(9), r=read(9);
		Q[l].emplace_back(make_pair(r,i));
	}
	int p[2]={n,n};
	for(int i=n;i;--i) {
		if(!a[i]) p[b[i-1]^1] = i-1;
		T[b[i-1]].add(i,p[b[i-1]]);
		for(const auto& t:Q[i])
			ans[t.second] = T[0].ask(t.first)+T[1].ask(t.first);
	}
	for(int i=1;i<=q;++i) print(ans[i],'\n');
	return 0;
}

\(\cal T_2\)

\(\mathbb{D}\rm escription\)

给出一个长度为 \(n\) 的序列 \(A\),有 \(m\) 个操作 \((l_i,r_i,x_i)\),表示把区间 \([l_i,r_i]\)\(x\)\(\min\)\(q\) 个询问 \(p_i\leftarrow (a,b,c,d)\),表示对序列执行第 \(a\)\(b\) 个操作后区间 \([c,d]\) 的和。

保证 \(a,b\) 随机生成

\(1\le n,m\le 15000,1\le q\le 10^5,1\le a_i\le 10^9\).

\(\mathbb{S}\rm olution\)

首先可以想到一个暴力:对于每个询问,将询问内的操作按值从小到大排序,这样每个点只会被操作一次,所以可以用并查集来优化。时间复杂度 \(\mathcal O(qn\log n)\).

一个结论是,当 数据随机 时,将询问按 \(a\) 排序后,最长上升子序列长度期望为 \(\sqrt q\)。把这个结论拓展到偏序集上(对于此题,定义元素为 \((a_i,b_i)\)),实际上就是最大全序集(也等价于最少反链覆盖数)期望为 \(\sqrt q\)。可以发现,每个反链覆盖都是一堆嵌套的询问区间,如果从最内层开始向外扩展就不需要撤销操作,可以直接用吉司机线段树求解这个问题。但是如何划分序列?我当时对题解一知半解就开始写写写,后来才发现没有真正领会到这个性质,导致写了一个复杂度非常假的做法。

这里给出划分序列的方法:按 \(a\) 排序后,从后往前遍历序列,每找到一个未被划分的下标 \(i\),就令 \(l=a_i,r=b_i\),再往前遍历,能扩展 \(l,r\) 就进行扩展,直到无法扩展为止。时间复杂度 \(\mathcal O(\sqrt q\cdot n\log n)\).

$\mathbb{C}\rm ode $

伞兵锅都在代码里了。

# pragma GCC optimize(2)
# pragma GCC optimize(3)
# pragma GCC optimize("Ofast")
# include <cstdio>
# include <cctype>
# define print(x,y) write(x), putchar(y)

template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while(!isdigit(s=getchar())) f|=(s=='-');
    for(; isdigit(s); s=getchar()) x=(x<<1)+(x<<3)+(s^48);
    return f? -x: x;
}
template <class T>
inline void write(T x) {
    static int writ[50], w_tp=0;
    if(x<0) putchar('-'), x=-x;
    do writ[++w_tp] = x-x/10*10, x/=10; while(x);
    while(putchar(writ[w_tp--]^48), w_tp);
}

# include <tuple>
# include <iostream>
# include <algorithm>
using namespace std;
typedef long long ll;

const int maxn = 15005;
const int maxq = 100005;

bool vis[maxq];
ll ans[maxq];
tuple <int,int,int> opt[maxn];
int n,m,q,a[maxn];
struct Node { int l1,r1,l2,r2,id; } s[maxq];

namespace SgT {

struct node { int mx,md,cnt; ll sm; } t[maxn<<2];

void pushUp(int o) {
    t[o].sm = t[o<<1].sm+t[o<<1|1].sm;
    t[o].mx = max(t[o<<1].mx,t[o<<1|1].mx);
    t[o].md = max(t[o<<1].md,t[o<<1|1].md);
    if(t[o<<1].mx^t[o<<1|1].mx)
        t[o].md = max(t[o].md,min(t[o<<1].mx,t[o<<1|1].mx));
    t[o].cnt=0; // important!!!
    if(t[o].mx==t[o<<1].mx) t[o].cnt += t[o<<1].cnt;
    if(t[o].mx==t[o<<1|1].mx) t[o].cnt += t[o<<1|1].cnt;
}

void build(int o,int l,int r) {
    t[o].md=-1;
    if(l==r) return t[o].mx=t[o].sm=a[l], t[o].cnt=1, void();
    int mid = l+r>>1; build(o<<1,l,mid);
    build(o<<1|1,mid+1,r); pushUp(o);
}

void update(int o,int k) {
    if(k>=t[o].mx) return;
    t[o].sm -= 1ll*(t[o].mx-k)*t[o].cnt;
    t[o].mx=k;
}

void pushDown(int o) { update(o<<1,t[o].mx), update(o<<1|1,t[o].mx); }

void modify(int o,int l,int r,int L,int R,int x) {
    if(x>=t[o].mx) return;
    if(l>=L && r<=R && x>t[o].md) return update(o,x), void();
    int mid = l+r>>1; pushDown(o);
    if(L<=mid) modify(o<<1,l,mid,L,R,x);
    if(R>mid) modify(o<<1|1,mid+1,r,L,R,x);
    pushUp(o);
}

ll query(int o,int l,int r,int L,int R) {
    if(l>=L && r<=R) return t[o].sm;
    int mid = l+r>>1; ll ret=0; pushDown(o);
    if(L<=mid) ret = query(o<<1,l,mid,L,R);
    if(R>mid) ret = ret+query(o<<1|1,mid+1,r,L,R);
    return ret;
}

}

void modify(int x) { SgT::modify(1,1,n,get<0>(opt[x]),get<1>(opt[x]),get<2>(opt[x])); }

signed main() {
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);
    n=read(9), m=read(9), q=read(9);
    for(int i=1;i<=n;++i) a[i]=read(9);
    for(int i=1;i<=m;++i) {
        int l=read(9), r=read(9), x=read(9);
        opt[i] = make_tuple(l,r,x);
    }
    for(int i=1;i<=q;++i)
        s[i].id = i,
        s[i].l1=read(9), s[i].r1=read(9),
        s[i].l2=read(9), s[i].r2=read(9);
    sort(s+1,s+q+1,[](const Node& x,const Node& y) {
    	return x.l1<y.l1;
    });
    Node t;
    for(int i=q;i;--i) if(!vis[i]) {
        SgT::build(1,1,n);
        int l, r; t = s[i]; vis[i] = true;
        for(l=r=t.l1; r<=t.r1; ++r) modify(r);
        r = t.r1; // 我是伞兵啊!现在的 r 已经是 t.r1+1 了!!!
        ans[t.id] = SgT::query(1,1,n,t.l2,t.r2);
        for(int j=i-1; j; --j) if(!vis[j]) {
            t = s[j];
            if(t.l1<=l && t.r1>=r) {
                vis[j] = true;
                while(r<t.r1) modify(++r);
                while(l>t.l1) modify(--l);
                ans[t.id] = SgT::query(1,1,n,t.l2,t.r2);
            }
        }
    }
    for(int i=1;i<=q;++i) print(ans[i],'\n');
    return 0;
}

\(\cal T_3\)

\(\mathbb{D}\rm escription\)

\(\mathbb{S}\rm olution\)

数学题,不会。

$\mathbb{C}\rm ode $


posted on 2022-05-08 17:52  Oxide  阅读(26)  评论(0编辑  收藏  举报