[2021.10.4NOIP模擬] 切題
前言
好題啊,這很NOIP。
題目
模擬賽的題,我就不放出(搬)題人的fAKe題面了,可以去 CF 看原題。
題目大意:
班主任 \(\tt LLL\) 對假期裡同學們的表現不是很滿意,於是她決定來 \(N\) 次突擊檢查,每次她會挑選 \(a_i\) 個同學(顯然是不同的)到她的辦公室,顯然同學們的忍耐程度是有極限的,對於 \(M\) 個同學來說,每個人有一個忍耐程度 \(b_i\),表示他/她最多能忍受被 \(\tt LLL\) 叫去辦公室的次數,一旦到達極限,他/她將逃離教室,同學們在沒有達到極限的時候會盡可能滿足 \(\tt LLL\) 的要求。
但隨著時間的推移,\(\tt LLL\) 的抽查和同學們的程度都會有所變化。
具體的,共有 \(Q\) 個時間點,每個時間點到達後會有以下四種變化情況:
- \(1\ i\) :\(a_i\) 加一。
- \(2\ i\) :\(a_i\) 減一。
- \(3\ j\) :\(b_j\) 加一。
- \(4\ j\) :\(b_j\) 減一。
對於每個時間點,你需要回答同學們是否能夠滿足 \(\tt LLL\) 的抽查要求。
\(1\le N,M,Q\le 250000;0\le a_i,b_i\le 250000.\)
資料保證任意時刻 \(a,b\) 非負。
講解
首先試圖用一個式子來表示合法時的情況:
對 \(a\) 從大到小排序後,當且僅當 \(\forall k\in[1,N],\sum_{i=1}^k a_i\le \sum_{i=1}^M\min(b_i,k)\)
具體證明可以去看 _Wallace_巨佬 的部落格。
我們對每個 \(k\) 維護 \(\sum_{i=1}^M\min(b_i,k)-\sum_{i=1}^ka_i\),但是直接做不好做,考慮轉換。
令 \(c_k=\sum_{i=1}^M[b_i\ge k]\),則上面的式子可以化為 \(\sum_{i=1}^k(c_i-a_i)\),是一個很舒服的式子,用線段樹很好維護。
- 對於 \(a\) 的改動,由於每次只改 \(1\),所以二分出最前面/後面那個點直接改就好,單調不降性質依然保持。
- 對於 \(b\) 的改動,顯然只會對 \(1\) 個 \(c\) 有影響,可以直接改。
時間複雜度 \(O(n\log_2n)\)
程式碼
普通線段樹
//12252024832524
#include <cstdio>
#include <cstring>
#include <algorithm>
#define TT template<typename T>
using namespace std;
typedef long long LL;
const int MAXN = 250005;
const LL INF = 1ll << 60;
int n,m;
int a[MAXN],b[MAXN],nowa[MAXN];
LL cha[MAXN];
LL Read()
{
LL x = 0,f = 1; char c = getchar();
while(c > '9' || c < '0'){if(c == '-') f = -1;c = getchar();}
while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
return x * f;
}
TT void Put1(T x)
{
if(x > 9) Put1(x/10);
putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
if(x < 0) putchar('-'),x = -x;
Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Min(T x){return x < 0 ? -x : x;}
#define lc (x<<1)
#define rc (x<<1|1)
LL MIN[MAXN << 2],lz[MAXN << 2];
void calc(int x,int val){MIN[x] += val; lz[x] += val;}
void up(int x){MIN[x] = Min(MIN[lc],MIN[rc]);}
void down(int x){if(!lz[x]) return; calc(lc,lz[x]); calc(rc,lz[x]); lz[x] = 0;}
void Add(int x,int l,int r,int ql,int qr,int val)
{
if(ql > qr) return;
if(ql <= l && r <= qr)
{
calc(x,val);
return;
}
int mid = (l+r) >> 1;
down(x);
if(ql <= mid) Add(lc,l,mid,ql,qr,val);
if(mid+1 <= qr) Add(rc,mid+1,r,ql,qr,val);
up(x);
}
int main()
{
// freopen("problem.in","r",stdin);
// freopen("problem.out","w",stdout);
n = Read(); m = Read();
for(int i = 1;i <= n;++ i) nowa[i] = a[i] = Read();
for(int i = 1;i <= m;++ i) b[i] = Read(),--cha[b[i]+1],++cha[1];
sort(nowa+1,nowa+n+1,[](int x,int y){return x > y;});
for(int i = 1;i <= n;++ i) Add(1,1,n,i,n,-nowa[i]);
for(int i = 1;i <= n;++ i) cha[i] += cha[i-1],Add(1,1,n,i,n,cha[i]);
for(int Q = Read(); Q ;-- Q)
{
int opt = Read(),pos = Read();
if(opt == 1)
{
int v = a[pos]; ++a[pos];
int l = 1,r = n,ret = 1;
while(l <= r)
{
int mid = (l+r) >> 1;
if(nowa[mid] <= v) r = mid-1,ret = mid;
else l = mid+1;
}
Add(1,1,n,ret,n,-1);
++nowa[ret];
}
else if(opt == 2)
{
int v = a[pos]; --a[pos];
int l = 1,r = n,ret = 1;
while(l <= r)
{
int mid = (l+r) >> 1;
if(nowa[mid] >= v) l = mid+1,ret = mid;
else r = mid-1;
}
Add(1,1,n,ret,n,1);
--nowa[ret];
}
else if(opt == 3)
{
++b[pos];
Add(1,1,n,b[pos],n,1);
}
else
{
Add(1,1,n,b[pos],n,-1);
--b[pos];
}
if(MIN[1] >= 0) Put(1,'\n');
else Put(0,'\n');
}
return 0;
}
後記
這題只用了簡單的線段樹,卻涉及到了網路流的知識,可以說是非常NOIP了。