JOI 2015 FInal 舞會
JOI 2015 FInal 舞會
題目連結:#2727. 「JOI 2015 Final」舞會 - 題目 - LibreOJ (loj.ac)
很明顯,答案具有單調性,我們考慮二分答案。那麼問題就變成了怎麼寫出 check
函式。
我們發現,根據題意,每三個點最後都會變成一個點,那我們不斷進行縮點,畫個圖看看?
我們發現,這個數列變成了一棵樹,這棵樹的根就是最後留下來的點,我們可以利用這棵樹來檢驗。注意到,這棵樹中的每個非葉子節點都有且僅有三個兒子,且他的值是他三個兒子的中位數。對於中位數的題目我們又有一個很套路的做法,二分中位數,將大於等於中位數的數變成1,小於中位數的數變成0。
至於建樹,我們模擬過程就好了,程式碼像這樣:
void build() { queue <int> q; for(int i=1;i<=n;++i) q.push(i); int tot=n; while(q.size()>1) { int oo=++tot; if(q.size()==3) root=oo; for(int i=1;i<=3;++i) add(oo,q.front()),q.pop(); q.push(oo); } }
然後我們研究樹上節點的值是怎麼來的。注意到,每個葉子節點的值與他這個位置的貴族的熟練度有關,兒每個非葉子節點的值就是它三個兒子的中位數。也就是說當且僅當這個節點的三個兒子中至少有兩個1,這個節點的值才為1。而葉子節點的值是可以靠人去分配的,同時葉子結點的值的個數也是一定的,那麼樹形dp的做法就呼之欲出了。我們遍歷一遍每個貴族的熟練度,求出我們手中擁有1的個數,再通過樹形dp求出要讓根節點的值為1最小需要1值的個數,再講這個數與我們手上擁有1值的個數相比較就可以完成判斷。
我們設 dp[i]
為使 \(i\) 這個點的值為1所需要再新增的1值的個數。我們又要讓dp值最小,那麼轉移式為:
因為我們只需要兩個1就可以了,付出代價最大的那個節點就讓他為0。然後我們看看初始值怎麼賦。如果這個點不是一個貴族一開始的固定的位置,那麼這個點的 \(dp\) 值就為1。如果是的話,再考慮貴族的值是不是1,如果是1,那麼這個點的 \(dp\) 值為0。否則為 \(inf\) (注:這裡的 \(inf\) 開小一點,否則容易溢位導致負數的出現)
初始值我們可以這麼賦:
memset(dp,0,sizeof dp);
for(int i=1;i<=n;++i) dp[i]=1;
for(int i=1;i<=m;++i)
{
if(d[i]>=x) dp[p[i]]=0;
else dp[p[i]]=1e7;
}
全部程式碼如下:
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e5+5;
int n,m,d[MAXN],p[MAXN],v[MAXN],dp[MAXN<<2],root;
int head[MAXN<<2],all;
struct E
{
int to,pre;
}e[MAXN<<2];
void add(int u,int v)
{
e[++all]=E{v,head[u]};
head[u]=all;
}
void build()
{
queue <int> q;
for(int i=1;i<=n;++i)
q.push(i);
int tot=n;
while(q.size()>1)
{
int oo=++tot;
if(q.size()==3) root=oo;
for(int i=1;i<=3;++i)
add(oo,q.front()),q.pop();
q.push(oo);
}
}
void dfs(int k)
{
if(k<=n) return ;
int Max=0;
for(int i=head[k];i;i=e[i].pre)
{
int to=e[i].to;
dfs(to);
dp[k]+=dp[to];
Max=max(Max,dp[to]);
}
dp[k]-=Max;
}
bool check(int x)
{
memset(dp,0,sizeof dp);
for(int i=1;i<=n;++i) dp[i]=1;
for(int i=1;i<=m;++i)
{
if(d[i]>=x) dp[p[i]]=0;
else dp[p[i]]=1e7;
}
int cnt=0;
for(int i=m+1;i<=n;++i) if(d[i]>=x) ++cnt;
dfs(root);
return dp[root]<=cnt;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=m;++i)
scanf("%d %d",&d[i],&p[i]);
for(int i=m+1;i<=n;++i)
scanf("%d",&d[i]);
for(int i=1;i<=n;++i)
v[i]=d[i];
sort(v+1,v+1+n);
int Siz=unique(v+1,v+1+n)-v-1;
build();
int l=1,r=Siz,ans=1;
while(l<=r)
{
int mid=l+r>>1;
if(check(v[mid]))
{
ans=mid;
l=mid+1;
}
else r=mid-1;
}
printf("%d\n",v[ans]);
return 0;
}
像這種有點像模擬,需要不斷進行操作的題目。對於這種陌生的模型,我們可以將模型轉化為我們熟悉的某種圖進行解題。一般來說,一種操作就相當於在圖上進行一次移動。這種轉化模型的圖還是比較考驗想法的。這就是我被初見殺的理由0.0 還是得靠做題積累經驗。