[UVA10859]Placing Lampposts
https://zybuluo.com/ysner/note/1248929
题面
给定一个\(n\)个点\(m\)条边的无向无环图,在尽量少的节点上放灯,使得所有边都与灯相邻(被灯照亮)。
在灯的总数最小的前提下,被两盏灯同时照亮的边数应该尽可能大。
- \(n,m\leq1000\)
题面
有一种套路,如果要同时最小化或最大化两个量\(a,b\),则等价于最小化或最大化\(aM+b\)。
并且,\(M\)必须大到足以区分\(a,b\)。一般来说,\(M>max\{abs(a-b)\}\)。
所以本题可以设\(M=3000\)。
但是,题目要求是最小化\(a\),最大化\(b\)???
可以转化一下,把\(b\)表示为“只被一盏灯照亮的边数”(因为\(b'=m-b\))。
于是设\(f[i][0/1]\)分别表示以\(i\)为根的子树内,不放灯和放灯对答案(\(aM+b\))的贡献。
转移时不统计被两盏灯同时照亮的边的贡献。
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define re register
#define il inline
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int mod=1e9+7,N=2000,M=3000;
struct Edge{int to,nxt;}e[N<<1];
int n,m,h[N],cnt,dp[N][2],vis[N];
il void add(re int u,re int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
ll ans=0;
il ll gi()
{
re ll x=0,t=1;
re char ch=getchar();
while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
if(ch=='-') t=-1,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
return x*t;
}
il void dfs(re int u,re int fa)
{
vis[u]=1;dp[u][0]=0;dp[u][1]=M;
for(re int i=h[u];i+1;i=e[i].nxt)
{
re int v=e[i].to;
if(v==fa||vis[v]) continue;
dfs(v,u);
dp[u][0]+=dp[v][1]+1;
dp[u][1]+=min(dp[v][0]+1,dp[v][1]);
}
}
int main()
{
re int T=gi();
while(T--)
{
memset(h,-1,sizeof(h));cnt=0;
n=gi();m=gi();ans=0;
memset(vis,0,sizeof(vis));
fp(i,1,m)
{
re int u=gi()+1,v=gi()+1;
add(u,v);add(v,u);
}
fp(i,1,n)
if(!vis[i]) dfs(i,0),ans+=min(dp[i][0],dp[i][1]);
printf("%lld %lld %lld\n",ans/M,m-ans%M,ans%M);
}
return 0;
}