D. Cut and Stick
(賽后補題)借本題學習莫隊算法以及區間眾數的求法
題意:對于整型數組,每次詢問[L,R][L,R][L,R]區間問最少分為多少個子序列,使得每個子序列的眾數xxx的個數cntxcnt_xcntx?不大于 ?len2?\left \lceil \frac{len}{2} \right \rceil?2len??,lenlenlen表示子序列的長度
思路:對于每次詢問只需知道詢問區間內的眾數xxx的個數即可,最優解即為將其余的非眾數盡可能與更多的xxx組合為一個子序列,而剩下的xxx每個自為一個子序列
給一個不嚴格的證明,圖中有aaa個眾數與bbb個非眾數,法①將全部的非眾數放在同一個子序列中,此時該子序列最多匹配b+1b+1b+1個眾數,而剩余的眾數則需單獨出現;法②將b個非眾數分為兩部分(c+d=bc+d=bc+d=b),兩部分分別匹配c+1c+1c+1和d+1d+1d+1個眾數(c+1+d+1=c+d+2=b+2c+1+d+1 = c+d+2 = b+2c+1+d+1=c+d+2=b+2)此時比法①多匹配了一個眾數,然而由于被分為兩部分,自然地也多產生了一個子序列,因此兩者是等效的,可見只要將盡可能多的眾數匹配給非眾數,無論非眾數分為多少塊其效果是等效的
因此問題便轉化為求區間眾數的個數問題。
數組長度:nnn,詢問次數:mmm
若采用暴力求解,每次詢問計算眾數的個數的時間復雜度為O(n)O(n)O(n),總時間復雜度為O(m?n)O(m*n)O(m?n),會超時
采用莫隊算法優化,可將每次詢問的平均時間復雜度降為O(n)O(\sqrt{n})O(n?)
莫隊步驟:
- 將數組分為大小為n\sqrt{n}n?的塊
- 將詢問順序按下面的規則排序
首先按照LLL所在的塊號排序,塊號越小越靠前(升序)
若LLL所在的塊號相同,則按RRR所在的塊號升序 - 接著定義兩個函數add()add()add()和del()del()del()分別用于添加和刪除元素,用于在上一個詢問的區間基礎上增刪元素得到當前詢問的區間,每次調用增刪函數的關鍵是動態地更新眾數的狀態
維護眾數狀態:
采用mapmapmap映射存儲每個數出現的次數,采用數組aaa記錄有多少個數出現了iii次,當前的眾數的個數即為數組a[i]>0a[i]>0a[i]>0的最大的iii,每次增刪實時更新兩者的數據即可
#include<bits/stdc++.h>
#define debug1(x) cout<<#x<<":"<<x<<endl
#define fastio() ios_base::sync_with_stdio(0);cin.tie(0)
typedef long long ll;
typedef unsigned long long ull;
const int N = (int)3e5+100;
const int RN = sqrt(N)+10;
const int MOD = 10000;
using namespace std;
int a[N];
map<int,int> mp[RN];
int n,m,block;
int cnt[N],sum[N],ans[N],max_cnt = 0;
struct query
{int id;int l;int r;
}q[N];bool cmp(query a, query b)
{int al = a.l / block, bl = b.l / block;int ar = a.r / block, br = b.r / block;if(al != bl) return al < bl;return ar < br;
}void add(int e)
{sum[cnt[e]]--;cnt[e]++;sum[cnt[e]]++;max_cnt = max(max_cnt, cnt[e]);
}void del(int e)
{sum[cnt[e]]--;if(cnt[e] == max_cnt && sum[cnt[e]] == 0) max_cnt--;cnt[e]--;sum[cnt[e]]++;
}int main()
{cin>>n>>m;block = sqrt(n);for(int i = 1; i <= n; i++) scanf("%d",&a[i]);int l, r;for(int i = 0; i < m; i++){scanf("%d%d",&l,&r);q[i].id = i;q[i].l = l;q[i].r = r;}sort(q, q + m, cmp);int cl, cr, width;cl = cr = 0; add(a[0]);for(int i = 0; i < m; i++){l = q[i].l, r = q[i].r;while(cr < r) add(a[++cr]);while(cl > l) add(a[--cl]);while(cr > r) del(a[cr--]);while(cl < l) del(a[cl++]);width = r - l + 1;ans[q[i].id] = max(2 * max_cnt - width, 1);}for(int i = 0; i < m; i++) printf("%d\n", ans[i]);return 0;
}