[Contest on 2022.5.2] 端午節能放幾天啊?
\(\cal T_1\) 錒
\(\mathbb{D}\rm escription\)
對於一個長度為 \(m\) 的非負整數序列 \(b\),如果能通過下述兩個操作使得序列變成全零,則稱這個序列是好的:
- 選擇 \(1\le i\le m\) 把 \(b_i\) 減 \(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\)
然後就可以做啦!
$\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 $