1. 程式人生 > 其它 >[2021.10.4NOIP模擬] 切題

[2021.10.4NOIP模擬] 切題

你來我辦公室一趟。——LLL

前言

好題啊,這很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了。