Placing Lampposts
傳送門:https://vjudge.net/problem/UVA-10859
題目大意:給你一片森林,要求你在一些節點上放上燈,一個點放燈能照亮與之相連的所有的邊。問你最小化防止的燈數,在燈數相同的條件下,最大化兩個點都有燈的邊數。
題解:
首先有一個套路,也是做了此題才知道的,很神奇啊。最小化燈的數量,我們設燈數為V1,把“最大化兩個點都有燈的邊數”轉化為“最下化只有一個點有燈的邊數”,設為V2,那么我們設Val=Eps*V1+V2。這樣只要DP一個值就可以了。Eps設成一個足夠大的值,保證Eps>sum{V2}。此題姑且設為2000。
然后我們就可以DP了。樹上求解最優解,此題為森林,轉化為每棵樹的答案相加就可以了。那么怎么DP呢?
設狀態DP[i]代表i節點與它的子樹以及連向父親的那一條邊的最小的Val。每一個節點有放燈與不放燈兩種狀態,但是我們發現,父親放不放燈會影響兒子放不放燈,那么我們再加上一維的狀態:dp[i][0/1]代表代表i節點與它的子樹以及連向父親的那一條邊的最小的Val,j=1為父親放燈,j=0代表父親不放燈。
考慮兩種方案:
1. i放燈:i放燈的話,對於其他的沒有什么要求,所以dp[i][j]+=dp[son][1],dp[i][j]+=Eps。如果當前j==0,並且不是根節點,那么dp[i][j]++,因為到父親的那一條邊只有1個燈。
2. i不放燈:i不放燈,轉移就有限制條件了,必須父親放燈,或者i為根節點,dp[i][1]+=dp[son][0],如果i不是根節點,那么還要++,同樣的因為到父親的那一條邊只有1個燈。
然后一邊dfs一邊DP就可以了。注意狀態轉移是錯綜復雜的,並不是單一的0->1或0->0,具體順序見代碼。
條件1可以更新j=1和0的情況;條件2只能更新j=1的情況,但是在根節點也可以更新j=0的情況。
1 #include<queue> 2 #include<cstdio> 3 #include<vector> 4 #include<cstring> 5 #include<iostream> 6 #include<algorithm> 7 #define RG register 8 #define LL long long 9 #define fre(a) freopen(a".in","r",stdin);freopen(a".out","w",stdout); 10 using namespace std; 11 const int MAXN=5000,Eps=2000; 12 int n,num,m,Case,ans; 13 int dp[MAXN][2]; 14 int head[MAXN],to[MAXN],Next[MAXN]; 15 bool vis[MAXN]; 16 void dfs(int u,int fa) 17 { 18 vis[u]=1; 19 int sum1=0,sum2=Eps; 20 for(int i=head[u];i;i=Next[i]) 21 { 22 int v=to[i]; 23 if(v==fa)continue; 24 dfs(v,u); 25 sum1+=dp[v][0];//不放燈 26 sum2+=dp[v][1];//放燈 27 } 28 if(fa!=0)sum1++; 29 dp[u][1]=sum1; 30 dp[u][1]=min(dp[u][1],sum2);//與放燈的再比較一下。 31 dp[u][0]=sum2; 32 if(fa!=0) dp[u][0]++; 33 if(fa==0) 34 dp[u][0]=min(dp[u][0],sum1); 35 } 36 void add(int f,int t) 37 { 38 Next[++num]=head[f]; 39 to[num]=t; 40 head[f]=num; 41 } 42 int main() 43 { 44 scanf("%d",&Case); 45 while(Case--) 46 { 47 scanf("%d%d",&n,&m); 48 num=0; 49 memset(head,0,sizeof head); 50 memset(vis,0,sizeof vis); 51 memset(dp,0,sizeof dp); 52 for(int i=1,a,b;i<=m;i++) 53 { 54 scanf("%d%d",&a,&b); 55 a++,b++; 56 add(a,b); add(b,a); 57 } 58 ans=0; 59 for(int i=1;i<=n;i++) 60 if(!vis[i]) 61 { 62 dfs(i,0); 63 ans+=min(dp[i][0],dp[i][1]); 64 } 65 printf("%d %d %d\n",ans/Eps,m-ans%Eps,ans%Eps); 66 } 67 return 0; 68 }
