一個基於分塊之上的演算法
先來看例題:
對於每個詢問,可以拿上一次詢問完的[l,r]加上移動到與這一次詢問的[l,r]的差異值,就是答案,那麼現在的問題就是 : 所有詢問間的位移最小值
依照上面的想法可以先寫出一個code
int ans=0,cnt[MAXN]={0},a[]={..../*test*/};
void add(int val){
if(cnt[val]==0) ans++;
cnt[val]++;
}
void sub(int val){
if(cnt[val]==1) ans--;
cnt[val]--;
}
//main-----------
int l=0,r=-1; //初始化
for(int i=0;i<q;i++){
cin >> ql >> qr;
while(l<ql) sub(a[l++]);
while(l>ql) add(a[--l]);
while(r<qr) add(a[++r]);
while(r>qr) sub(a[r--]);
cout << ans << '\n';
}
但是這樣會來來回回很多次,所以回到我們之前的問題 : 如何讓位移量最小化
但要怎麼排呢?
假設我們的側資長這樣
L R :
5 8
1 10
6 9
5 9
1 5
3 10
1 9
1 1
6 9
2 3
(法1) 我們可以依照L由小到大,如果L一樣,就依照R
bool cmp(node a,node b){
if(a.l!=b.l) return a.l<b.l;
else return a.r<b.r;
}
L R :
1 1
1 5
1 9
1 10
2 3
3 10
5 8
5 9
6 9
6 9
L是很好沒錯,但R在L換數字時就會跳很大了,所以我們可以在適當的範圍內讓L跳,像是做完第一組詢問後可以跳(2,3)
(法2) 利用分塊概念,當L在不同塊時,用塊去排序,當L在同一塊時,用R去排序
int k=(int)sqrt(n);
bool cmp(node a,node b){
if(a.l/k!=b.l/k) return a.l/k>b.l/k;
else return a.r<b.r;
}
L R :
1 1
2 3
1 5
1 9
1 10
5 8
5 9
3 10
6 9
6 9
加速
一開始右界會從最左邊跑到最右邊,此時下一塊可由最右邊開始,從右跑到左,再下一塊就由最左邊開始,這樣比較有效率
bool cmp(Q a,Q b){
if(a.F/k==b.F/k){
if((a.F/k)%2) return a.S>b.S;
else return a.S<b.S;
}
return a.F/k<b.F/k;
}
L R :
1 1
2 3
1 5
1 9
1 10
3 10
5 9
5 8
6 9
6 9
#include <bits/stdc++.h>
#define MAXN 100005
#define pii pair<int,int>
using namespace std;
typedef struct{
int l,r,id;
}node;
int cnt[MAXN]={0},cntcnt[MAXN]={0},ma=0,k;
bool cmp(node a,node b){
return a.l/k==b.l/k?a.r<b.r:a.l/k<b.l/k;
}
void add(int pos){
cntcnt[cnt[pos]]--;
cnt[pos]++;
cntcnt[cnt[pos]]++;
if(cntcnt[cnt[pos]]==1) ma=max(ma,cnt[pos]);
}
void sub(int pos){
cntcnt[cnt[pos]]--;
cnt[pos]--;
cntcnt[cnt[pos]]++;
if(cntcnt[cnt[pos]+1]==0 && ma==cnt[pos]+1) ma=cnt[pos];
}
int main(){
cin.sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n,m;
cin >> n >> m;
k=(int)sqrt(n);
vector<int> v(n+1);
for(int i=1;i<=n;i++){
cin >> v[i];
}
vector<node> q(m);
for(int i=0;i<m;i++){
cin >> q[i].l >> q[i].r;
q[i].id=i;
}
sort(q.begin(),q.end(),cmp);
vector<pii> ans(m);
int l=1,r=0;
for(auto p:q){
while(l<p.l) sub(v[l++]);
while(l>p.l) add(v[--l]);
while(r<p.r) add(v[++r]);
while(r>p.r) sub(v[r--]);
ans[p.id].first=ma;
ans[p.id].second=cntcnt[ma];
}
for(auto i:ans) cout << i.first << ' ' << i.second << '\n';
return 0;
}
要分成n2/3塊,別問我問什麼,我也不知道
(2022/6/28)更 : 因為時間複雜度最小,至於怎麼證明有待研究
k=pow(n,2/3.0);
優先順序為左塊>右塊>t,t為第幾次修改
struct Q{
int l,r,t,idx;
bool operator<(Q b){
if(l/k==b.l/k){
if(r/k==b.r/k){
return t<b.t;
}else{
return r/k<b.r/k;
}
}else{
return l/k<b.l/k;
}
}
}q[MAXN];
跟前面一樣,就只是多了個t要判斷,在修改的時候,如果要修改的編號不在[l,r]那就直接修改,否則要先把此元素在[l,r]中刪掉,然後新增此修改值,最後在修改陣列的值,要注意的是不能直接覆蓋,而是交換,因為後面有可能會把此元素換回未修改前的值。
注意:在函式裡做交換要取本尊來交換
void modity(Q x,M &y){
if(x.l<=y.idx && y.idx<=x.r){
sub(a[y.idx]);
add(y.val);
}
swap(a[y.idx],y.val);
}
//新增要判斷的
while(t<q[i].t) modity(q[i],m[++t]);
while(t>q[i].t) modity(q[i],m[t--]);
#include <bits/stdc++.h>
#define int long long
using namespace std;
#define pii pair<int,int>
#define F first
#define S second
#define pb push_back
#define all(a) (a).begin(),(a).end()
#define mem(a) memset(a,0,sizeof(a))
#define binary_search
#define inf LLONG_MAX
#define mod 1000000007
const int MAXN=133335;
int N=1,n,x,k;
//-----------------------------------
int a[MAXN],mp[1000005],l,r,t,qid,mid,cnt;
struct Q{
int l,r,t,idx;
bool operator<(Q b){
if(l/k==b.l/k){
if(r/k==b.r/k){
return t<b.t;
}else{
return r/k<b.r/k;
}
}else{
return l/k<b.l/k;
}
}
}q[MAXN];
struct M{
int idx,val;
}m[MAXN];
void add(int val){
if(!mp[val]) cnt++;
mp[val]++;
}
void sub(int val){
mp[val]--;
if(!mp[val]) cnt--;
}
void modity(Q x,M &y){
if(x.l<=y.idx && y.idx<=x.r){
sub(a[y.idx]);
add(y.val);
}
swap(a[y.idx],y.val);
}
signed main(){
cin.sync_with_stdio(0),cin.tie(0);
int q_num;
char info;
cin >> n >> q_num;
k=pow(n,(double)2/(double)3);
for(int i=1;i<=n;i++) cin >> a[i];
for(int i=0;i<q_num;i++){
cin >> info;
int l,r,x,val;
if(info=='Q'){
cin >> l >> r;
q[qid]={l,r,mid,qid};
qid++;
}else{
cin >> x >> val;
m[++mid]={x,val};
}
}
sort(q,q+qid);
vector<int> ans(qid);
for(int i=0;i<qid;i++){
while(l<q[i].l) sub(a[l++]);
while(l>q[i].l) add(a[--l]);
while(r>q[i].r) sub(a[r--]);
while(r<q[i].r) add(a[++r]);
while(t<q[i].t) modity(q[i],m[++t]);
while(t>q[i].t) modity(q[i],m[t--]);
ans[q[i].idx]=cnt;
}
for(auto i:ans) cout << i << '\n';
}
莫對其念是當我們得知一個區間\([l,r]\)的值後,我們可以以這個區間來位移到我們要查詢的區間,這樣就不用每個都從頭算了。
在位移的值後,我們可以發現在新增元素的時候很好寫,只需要跟答案取最大值就好,但刪除元素的時候就沒那麼容易了,因此我們可以採用一個方法 : 回朔,先記錄下更新此元素前的最大值是多少,接著還刪除此元素後就考可以一併更新最大值了。
void add(int x){
stk.push({cur_ma,x});
cnt[x]++;
cur_ma=max(cur_ma,cnt[x]*x);
}
void roll_back(){
pii top=stk.top();stk.pop();
cur_ma=top.F;
cnt[top.S]--;
}
如果\([l,r]\)在同一個block的時候可以先做,直接暴力求解,不同block時,以下為更新的步驟 :
初始話時,L要在左界屬於的block+1的最左邊的格子,R要在左界屬於的block的最右邊,因最左界往左跑,要初始話在-1的地方,右界同理。
接著右界移動,左界移動,然後更新答案,最後再把左界移到初始位置,因為左界是照block順序排的,右界是直接照順序排的,因此左界要回朔,再移動到新的左界,右界只需要一直往右移動就好了,因為回朔具有單調性,因此左界每次都要回到最初的起點才不會左把右界混和。
for(auto [ql,qr,i]:q){
if(ql/k==qr/k) continue;
if(ql/k!=block){
while(!stk.empty()) roll_back();
block=ql/k;
L=(block+1)*k;
R=L-1;
}
while(R<qr) add(a[++R]);
while(ql<L) add(a[--L]);
ans[i]=curma;
while(L<(block+1)*k) roll_back(),L++;
}
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define MAXN 100005
#define pb push_back
#define all(a) (a).begin(),(a).end()
#define pii pair<int,int>
#define F first
#define S second
#define inf LLONG_MAX
int a[MAXN],ans[MAXN],ori[MAXN],cnt[MAXN],k,curma;
stack<pii> stk;
struct Q{
int l,r,i;
bool operator<(Q b){
return (l/k==b.l/k ? r<b.r : l/k<b.l/k);
}
};
void add(int x){
stk.push({curma,x});
cnt[x]++;
curma=max(curma,cnt[x]*ori[x]);
}
void roll_back(){
pii top=stk.top();stk.pop();
curma=top.F;
cnt[top.S]--;
}
signed main(){
int n,m;
cin >> n >> m;
k=sqrt(n);
vector<int> tmp;
for(int i=0;i<n;i++){
cin >> a[i];
tmp.pb(a[i]);
}
sort(all(tmp));
tmp.resize(unique(all(tmp))-tmp.begin());
for(int i=0;i<n;i++){
int id=lower_bound(all(tmp),a[i])-tmp.begin();
ori[id]=a[i];
a[i]=id;
}
vector<Q> q;
for(int i=0;i<m;i++){
int l,r;
cin >> l >> r;
l--,r--;
q.pb({l,r,i});
if(l/k==r/k){
for(int j=l;j<=r;j++) add(a[j]);
ans[i]=curma;
for(int j=l;j<=r;j++) roll_back();
}
}
sort(all(q));
int L=0,R=0,block=-1;
for(auto [ql,qr,i]:q){
if(ql/k==qr/k) continue;
if(ql/k!=block){
while(!stk.empty()) roll_back();
block=ql/k;
L=(block+1)*k;
R=L-1;
}
while(R<qr) add(a[++R]);
while(ql<L) add(a[--L]);
ans[i]=curma;
while(L<(block+1)*k) roll_back(),L++;
}
for(int i=0;i<m;i++) cout << ans[i] << '\n';
}