1. 程式人生 > 其它 >[Contest on 2022.5.2] 端午節能放幾天啊?

[Contest on 2022.5.2] 端午節能放幾天啊?

\(\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 $