1. 程式人生 > >洛谷 P3629 【[APIO2010]巡邏】

洛谷 P3629 【[APIO2010]巡邏】

遍歷 new 之間 ID bool blank 簡單 之前 找到

題目在這裏

這是一個紫題,當然很難。

我們往簡單的想,不建立新的道路時,從1號節點出發,把整棵樹上的每條邊遍歷至少一次,再回到1號節點,會恰好經過每條邊兩次,路線總長度為2(n-1),根據樹的深度優先遍歷思想,很容易證明這個結論,因為每條邊必然被遞歸一次,回溯一次。

建立1條新道路之後,因為新道路必須恰好經過一次(0次,2次都不可以),所以在沿著新道路(x,y)巡邏之後,要返回x,就必須沿著樹上從y到x的路徑巡邏一遍,最終形成一個環。與不建立新道路的情況相結合,相當於樹上x與y之間的路徑就只需經過一次了。

因此,當k=1時,我們找到樹的最長鏈,在兩個端點之間加一條新道路,就能讓總的巡邏距離最小。若樹的直徑為L,答案就是2(n-1)-L+1。

建立第2條新道路(u,v)之後,又會形成一個環。若兩條新道路形成的環不重疊,則樹上u,v之間的路徑只需經過一次,答案繼續減小。否則,在兩個環重疊的情況下,如果我們還按照剛才的方法把第2個環與建立1條新道路的情況相結合,兩個環重疊的部分就不會被巡邏到。最終的結果是兩個環重疊的部分由只需經過一次變回了需要經過兩次。

綜上所述,我們得到了如下算法:

1.在最初的樹上求直徑,設直徑為L1。然後,把直徑上的邊權取反(從1改為-1)。

2.在最長鏈邊權取反之後的樹上再次求直徑,設直徑為L2。

答案就是2(n-1)-(L1-1)-(L2-1)=2n-L1-L2。如果L2這條直徑包含L1取反的部分,就相當於兩個環重疊。減掉(L1-1)後,重疊的部分變成了只需經過一次,減掉(L2-1)後,相當於把重疊的部分加回來,變回需要經過兩次,與我們之前討論相符。時間復雜度為O(n)。

code:

// luogu-judger-enable-o2
#include <bits/stdc++.h>
using namespace std;

queue <int> q;
const int N=100010;
bool v[N];
int f[N];
int n,k,e=1,cnt,p;
int s[N<<1][2],o[N],d[N],fa[N],w[N<<1];

void add(int x,int y)
{
    s[++e][0]=y;s[e][1]=o[x];o[x]=e;w[e]=1;
}

int bfs(int xx) { int ans=xx; memset(v,0,sizeof(v)); fa[xx]=0;v[xx]=1;d[xx]=0;q.push(xx); while (!q.empty()) { int x=q.front(); for (int i=o[x];i;i=s[i][1]) { int y=s[i][0]; if (!v[y]) { fa[y]=x;d[y]=d[x]+1; if (d[y]>d[ans]) ans=y; v[y]=1;q.push(y); } } q.pop(); } return ans; } void mark(int x) { int i=o[x]; while (i) { int y=s[i][0]; if (y!=fa[x]) { if (v[x]&&v[y]) w[i]=w[i^1]=-1; mark(y); } i=s[i][1]; } } void tree_dp(int x) { int mx=0,i=o[x]; while (i) { int y=s[i][0]; if (y!=fa[x]) { tree_dp(y); p=max(p,mx+f[y]+w[i]); mx=max(mx,f[y]+w[i]); } i=s[i][1]; } p=max(mx,p);f[x]=mx; } int main() { int x,y; cin>>n>>k; for (int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y);add(y,x); } int ss,t; ss=bfs(1);t=bfs(ss); bfs(1); memset(v,0,sizeof(v)); if (d[ss]<d[t]) swap(ss,t); v[ss]=v[t]=1; while (d[ss]>d[t]) { ss=fa[ss];v[ss]=1;++cnt; } while (ss!=t) { ss=fa[ss];t=fa[t];v[ss]=v[t]=1;cnt+=2; } if (k==1) {cout<<(n-1)*2+1-cnt<<endl;return 0;} if (cnt==n-1) {cout<<n+1<<endl;return 0;} mark(1);tree_dp(1); cout<<(n-1)*2+2-cnt-p<<endl; return 0; }

洛谷 P3629 【[APIO2010]巡邏】