[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;
}

猜你喜欢

转载自www.cnblogs.com/yanshannan/p/9471926.html