1. 程式人生 > >[Usaco2010 Dec]Exercise 奶牛健美操

[Usaco2010 Dec]Exercise 奶牛健美操

得到 二分 tdi || 聯通塊 algorithm sed cli -a

[Usaco2010 Dec]Exercise 奶牛健美操

題目

Farmer John為了保持奶牛們的健康,讓可憐的奶牛們不停在牧場之間 的小路上奔跑。這些奶牛的路徑集合可以被表示成一個點集和一些連接 兩個頂點的雙向路,使得每對點之間恰好有一條簡單路徑。簡單的說來, 這些點的布局就是一棵樹,且每條邊等長,都為1。 對於給定的一個奶牛路徑集合,精明的奶牛們會計算出任意點對路徑的最大值, 我們稱之為這個路徑集合的直徑。如果直徑太大,奶牛們就會拒絕鍛煉。 Farmer John把每個點標記為1..V (2 <= V <= 100,000)。為了獲得更加短 的直徑,他可以選擇封鎖一些已經存在的道路,這樣就可以得到更多的路徑集合, 從而減小一些路徑集合的直徑。 我們從一棵樹開始,FJ可以選擇封鎖S (1 <= S <= V-1)條雙向路,從而獲得 S+1個路徑集合。你要做的是計算出最佳的封鎖方案,使得他得到的所有路徑集合 直徑的最大值盡可能小。 Farmer John告訴你所有V-1條雙向道路,每條表述為:頂點A_i (1 <= A_i <= V) 和 B_i (1 <= B_i <= V; A_i!= B_i)連接。 我們來看看如下的例子:線性的路徑集合(7個頂點的樹) 1---2---3---4---5---6---7 如果FJ可以封鎖兩條道路,他可能的選擇如下: 1---2 | 3---4 | 5---6---7 這樣最長的直徑是2,即是最優答案(當然不是唯一的)。

INPUT

第1行: 兩個空格分隔的整數V和S * 第2...V行: 兩個空格分隔的整數A_i和B_i

OUTPUT

第1行:一個整數,表示FJ可以獲得的最大的直徑。

SAMPLE

INPUT

7 2

6 7

3 4

6 5

1 2

3 2

4 5

OUTPUT

2

解題報告

二分答案$+$貪心驗證,竟然放在$DP$專題裏= =(可能是我造化不夠)

首先我們可以看到題面中閃閃發光的一句話

最大值盡可能小

這東西一眼看上去就知道,二分差不多就是可行的了

我們可以二分該最小值,然後驗證其是否合法

我們設$f[i]$表示以第$i$個節點為根的子樹中,合法的最長直鏈的長度

合法:

即保證最長鏈長度不可大於二分的答案

直鏈:

指鏈兩端點路徑不跨過根節點的鏈

然後我們就可以用$f[i]$計算要砍去多少條邊,從而判斷當前二分出的答案的合法性

那麽問題來了,如何計算$f[i]$

顯然直接求$max(f[son])$是不可行的,因為這不保證合法,但我們想,當我們選出兩條兒子所在的直鏈,發現當他們接在一起時長度過大,需要從中砍斷的時候,會有這樣的事情:

  1. 砍較長鏈鏈頂的邊最優
  2. 砍完該邊後,該鏈不再對父節點造成影響

第二點顯然,砍完之後該鏈與父節點不再屬於同一聯通塊內,故不會再影響

第一點也很顯然(廢話),如果我們砍較短鏈,或者不是鏈頂的邊,那麽最長直鏈可能還會與其他直鏈相接再次產生不合法鏈,需要多砍一次,所以砍較長鏈鏈頂的邊是最優的

那麽我們就可以處理了,將當前節點的所有$f[son]$從大到小排序,依次枚舉,判斷相鄰的兩條直鏈長度相接是否合法,假如合法,將$f[i]$賦值,否則繼續枚舉,並且記錄砍掉的邊的數目$++$

註意處理某些奇怪的邊界問題

技術分享
 1 #include<algorithm>
 2 #include<iostream>
 3 #include<cstring>
 4 #include<cstdio>
 5 using namespace std;
 6 inline int read(){
 7     int sum(0);
 8     char ch(getchar());
 9     for(;ch<0||ch>9;ch=getchar());
10     for(;ch>=0&&ch<=9;sum=sum*10+(ch^48),ch=getchar());
11     return sum;
12 }
13 struct edge{
14     int e;
15     edge *n;
16 }a[200005],*pre[100005];
17 int tot;
18 inline void insert(int s,int e){
19     a[++tot].e=e;
20     a[tot].n=pre[s];
21     pre[s]=&a[tot];
22 }
23 int n,m;
24 int f[100005],fa[100005];
25 int ans;
26 int mid,num;
27 int tmp[100005];
28 inline bool cmp(int x,int y){
29     return x>y;
30 }
31 inline void dfs(int u){
32     bool flag(false);
33     for(edge *i=pre[u];i;i=i->n){
34         int e(i->e);
35         if(e!=fa[u]){
36             flag=true;
37             fa[e]=u;
38             dfs(e);
39         }
40     }
41     if(!flag)
42         return;
43     tmp[0]=0;
44     f[u]=0;
45     for(edge *i=pre[u];i;i=i->n){
46         int e(i->e);
47         if(e!=fa[u])
48             tmp[++tmp[0]]=f[e]+1;
49     }
50     int size(tmp[0]);
51     sort(tmp+1,tmp+1+size,cmp);
52     tmp[size+1]=0;
53     for(int i=1;i<=size;++i){
54         if(tmp[i]+tmp[i+1]>mid)
55             ++num;
56         else{
57             f[u]=tmp[i];
58             break;
59         }
60     }
61 }
62 inline bool check(){
63     num=0;
64     memset(f,0,sizeof(f));
65     dfs(1);
66     if(num<=m)
67         return true;
68     return false;
69 }
70 inline void ef(int l,int r){
71     while(l<=r){
72         mid=(l+r)>>1;
73         if(check())
74             r=mid-1,ans=mid;
75         else
76             l=mid+1;
77     }
78 }
79 int main(){
80     memset(pre,NULL,sizeof(pre));
81     n=read(),m=read();
82     for(int i=1;i<n;++i){
83         int x(read()),y(read());
84         insert(x,y),insert(y,x);
85     }
86     ef(1,n);
87     printf("%d",ans);
88 }
View Code

[Usaco2010 Dec]Exercise 奶牛健美操