【TOI2019 二模 pD】離不開的新手村

題目敘述

給定一張 N  M 邊的有向圖,每個點有他的難度滿足「難度介於 1N 之間」且「任兩個點難度相異」。

一個數字 i 滿足「從任意的難度 i 的節點出發時,怎麼走都不會走到難度 >i 的節點」時,數字 i 就是『好的』,請你計算這張圖『好的』數字種類(介於 1N 之間)。

接下來會有 Q 次修改,每次修改會交換某兩個節點上的難度,請在每次修改後輸出新的『好的』數字種類。

子任務1(5分): 1N,M100,1Q100 
子任務2(16分):圖是一條有向路徑。
子任務3(13分): M=N1 ,每個點皆存在走到節點 1 的有向路徑。
子任務4(10分): 1Q1000,|ab|10000 
子任務5(25分):每個點的入度不超過 10 
子任務6(31分): 1N,M500000,1Q50000 

Time limit: 15 second

範測測資

Input:
5 5
1 2 3 4 5
2 4
3 1
2 1
4 3
5 3
3
2 4
2 3
5 3 
Output:
3
4
5
4

作法

在解題之前,我們需要通靈一些東西(?)。
如果我們考慮把點依照難度由小到大一個一個加進圖裡面,那等價我們要計算有幾個時刻,使得不存在一條邊指到空(未加入)的節點。

可以發現,對於每條邊難度 a 指到難度 b ,若 a 的難度比 b 的難度大,那就不會出事;可是若 a 的難度比 b 的難度小,表示在時刻 [a,b) 這個區間內,這條邊會出現在圖裡,卻指到空的節點。

我們發現每一條邊都有可能打到一條時刻的區間,於是我們可以把原題轉換成線段覆蓋問題,等價於求有多少點介於 1N 之間且沒有被任何一條邊覆蓋到。

很顯然的,交換兩個難度等價於兩次單點修改,而一個點的難度改變了就只會改變自己相鄰所有邊所打到的區間,所以我們需要一個支援「區間加值」、「查詢有多少點是 0 」的資料結構--這點操作完全可以拿我們在寫矩形覆蓋面積使用到的線段樹來支援。

所以我們目前至少獲得了一個 O(QMlogN) 的作法。

此時我們必須做一點觀察,看著所有進到同一個點(難度 h )的邊所對應到的區間都是形如 [x,h) 的形式,也就是右界都是固定的,由於我們只在意每個時刻是否「沒被蓋到過」,蓋到一次以上後蓋幾次都不在意了,所以我們可以只找到 [x,h)  x 最小的那個線段去覆蓋就可以了。這裡我們可以對每個點都開一個multiset去維護指到自己的人的最小值。

這時可以發現,覆蓋的線段總數會是 O(N) 的,修改一個點時除了處理自己蓋的這條邊外,還要跑過所有自己指到的點去看看會不會對其造成影響,這時很慘的我們的複雜度依舊是 O(Q(Max)logN) ,不過至少可以拿到subtask 2,3的分數。

若反過來思考的話,也可以獲得對稱的複雜度 O(Q(Max)logN) ,拿到subtask 5的分數,但還是拿不到滿分。

試著再觀察一下,雖然我們甚至可以把兩種做法合併起來,對於每一種點都維護出 [min,max) 的這種區間,但瓶頸完全在於修改一個點會需要更動到太多人的multiset--那如果有multiset的點數不要太多呢?

這時候大膽的想法就出現了(?),考慮用 degree 去分割:

以下把 N,M 之量級視為一樣。
 degreeK 的是大點, <K 的是小點

首先把每個大點維護出
 [min(,),max(,)) 
用兩個multiset搞掉
這樣可以cover到所有大→大、大→小、小→大的邊

把所有小→小的邊 ab 都維護出 [a,b) 這個區間

這樣在修改大點的時候
大點除了改自己的區間 ( logN )
還要主動修改所有其他相鄰的大點的區間和multiset ( NKlogN )

修改小點的時候
主動修改所有其他相鄰的大點的區間和multiset
暴力修改所有連到自己的小->小的邊維護出的區間
(兩者加起來不超過 KlogN )

 K  N 總共的複雜度即為 QNlogN ,而根據題目那時間限制感覺就可行XD。

實際上修改大點的常數可能會略大,所以我後來把 K 取到3000才過。

附上code:

#pragma GCC optimize("O3")
#include <bits/stdc++.h>
#define jizz ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
#define pb push_back
#define MP make_pair
#define F first
#define S second
#define ET cout << "\n"
#define MEM(i,j) memset(i,j,sizeof i)
#define ALL(v) v.begin(),v.end()
#define DB(a,s,e) {for(int i=s;i<e;++i) cerr << a[i] << " ";ET;}
using namespace std;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
vector<int> G[500005],nG[500005],rG[500005],nrG[500005];
multiset<int> mso[500005],msi[500005];
set<pii> st;
const int root=3000;
int seg[2000005],lazy[2000005],deg[500005],diff[500005],rdiff[500005],n;
void modify(int L,int R,int l,int r,int rt,int val)
{
if(L<=l&&R>=r)
return lazy[rt]+=val,void();
int m=l+r>>1;
if(L<=m) modify(L,R,l,m,rt<<1,val);
if(R>m) modify(L,R,m+1,r,rt<<1|1,val);
seg[rt]=0;
if(lazy[rt<<1]) seg[rt]+=m-l+1;
else seg[rt]+=seg[rt<<1];
if(lazy[rt<<1|1]) seg[rt]+=r-m;
else seg[rt]+=seg[rt<<1|1];
}
int gv()
{
if(lazy[1]) return 0;
return n-seg[1];
}
void range(int a,int b,int v)
{
if(a>=b) return;
modify(a,b-1,1,n,1,v);
}
void mdfy(int a,int b)
{
if(deg[a]>=root)
{
range(*msi[a].begin(),*mso[a].rbegin(),-1);
msi[a].erase(msi[a].find(diff[a])),msi[a].insert(b);
mso[a].erase(mso[a].find(diff[a])),mso[a].insert(b);
range(*msi[a].begin(),*mso[a].rbegin(),1);
for(int j:nG[a])
{
range(*msi[j].begin(),*mso[j].rbegin(),-1);
msi[j].erase(msi[j].find(diff[a])),msi[j].insert(b);
range(*msi[j].begin(),*mso[j].rbegin(),1);
}
for(int j:nrG[a])
{
range(*msi[j].begin(),*mso[j].rbegin(),-1);
mso[j].erase(mso[j].find(diff[a])),mso[j].insert(b);
range(*msi[j].begin(),*mso[j].rbegin(),1);
}
}
else
{
for(int j:G[a])
if(deg[j]>=root)
{
range(*msi[j].begin(),*mso[j].rbegin(),-1);
msi[j].erase(msi[j].find(diff[a])),msi[j].insert(b);
range(*msi[j].begin(),*mso[j].rbegin(),1);
}
else
range(diff[a],diff[j],-1),range(b,diff[j],1);
for(int j:rG[a])
if(deg[j]>=root)
{
range(*msi[j].begin(),*mso[j].rbegin(),-1);
mso[j].erase(mso[j].find(diff[a])),mso[j].insert(b);
range(*msi[j].begin(),*mso[j].rbegin(),1);
}
else
range(diff[j],diff[a],-1),range(diff[j],b,1);
}
diff[a]=b;
}
int main()
{jizz
int m,q,a,b;
cin >> n >> m;
for(int i=1;i<=n;++i)
cin >> diff[i],rdiff[diff[i]]=i;
while(m--)
{
cin >> a >> b;
if(a!=b)
st.insert(MP(a,b));
}
for(auto i:st)
G[i.F].pb(i.S),rG[i.S].pb(i.F);
for(int i=1;i<=n;++i)
deg[i]=G[i].size()+rG[i].size();
for(int i=1;i<=n;++i)
if(deg[i]>=root)
{
mso[i].insert(diff[i]),msi[i].insert(diff[i]);
for(int j:G[i])
{
mso[i].insert(diff[j]);
if(deg[j]>=root)
nG[i].pb(j);
}
for(int j:rG[i])
{
msi[i].insert(diff[j]);
if(deg[j]>=root)
nrG[i].pb(j);
}
range(*msi[i].begin(),*mso[i].rbegin(),1);
}
else
{
for(int j:G[i])
if(deg[j]<root&&i<j)
range(diff[i],diff[j],1);
for(int j:rG[i])
if(deg[j]<root&&i<j)
range(diff[j],diff[i],1);
}
cout << gv() << "\n";
for(cin >> q;q--;)
cin >> a >> b,mdfy(rdiff[a],b),mdfy(rdiff[b],a),swap(rdiff[a],rdiff[b]),cout << gv() << "\n";
}
view raw Stay.cpp hosted with ❤ by GitHub

留言

這個網誌中的熱門文章

【心得文】高中資訊競賽,回顧心得文

【World Finals】2023 ICPC World Finals

【World Finals 團練紀錄】2018 ACM-ICPC World Finals