图片加载可能有点慢,请跳过题面先看题解,谢谢
这道题需要求两样东西:放的灯数 \(a\) 最少,并且被两盏灯照亮的边数 \(b\) 尽量大,一个大一个小贼不爽了,我们改一下
求:放的灯数 \(a\) 最少,并且被一盏灯照亮的边数 \(c\) 尽量小,是不是看着舒服多了
$
$
那这个怎么求呢?并不会
$
$
这儿上一个套路,我们求这样一个东西 \(x=k*a+c\) ,\(k\) 为一个较大的常数(这道题我取2333),下面给出刘汝佳的说法:
\(k\) 是一个比“ \(c\) 的最大理论值和 \(c\) 的最小理论值之差”还要大的数,这样,只要两个解的 \(a\) 不同,不管 \(c\) 相差多少,\(x\) 都会取决于 \(a\) 的大小,
只有当 \(a\) 相等时才取决于 \(c\) 。
$
$
那么我们设状态:\(f[i][0/1]\) 表示,\(i\) 的父亲是否放灯时(1放灯,0不放),以 \(i\) 为根的子树的最小 \(x\) 值。
那么有转移:
- \(i\) 不放灯,只有当 \(i\) 的父亲放灯或者 \(i\) 为根时才能有此决策,\(f[i][j]=\sum{(v为 i 的儿子)}f[v][0]\) 。如果 \(i\) 不为根,则 \(f[i][j]++\)(\(i\) 的父边只有一盏灯照亮);
- \(i\) 放灯,\((f[i][j]=\sum{(v 为 i 的儿子)}f[v][1])+k\)。如果 \(j=0\) 且 \(i\) 不是根,则 \(f[i][j]++\)(\(i\) 的父边只有一盏灯照亮);
$
$
//made by Hero_of_Someone
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define il inline
#define RG register
using namespace std;
il int gi(){ RG int x=0,q=1; RG char ch=getchar(); while( ( ch<'0' || ch>'9' ) && ch!='-' ) ch=getchar();
if( ch=='-' ) q=-1,ch=getchar(); while(ch>='0' && ch<='9') x=x*10+ch-48,ch=getchar(); return q*x; }
int T,n,m,f[1010][2];
int num,head[1010],nxt[2010],to[2010];
il void add(int u,int v){
nxt[++num]=head[u];to[num]=v;head[u]=num;
nxt[++num]=head[v];to[num]=u;head[v]=num;
}
il void init(){
n=gi(),m=gi(),num=0;
memset(head,0,sizeof(head));
for(int i=1;i<=m;i++){ RG int u=gi(),v=gi(); add(u,v); }
}
bool vis[1010][2];
il int dfs(int x,int k,int fa){
if(vis[x][k]) return f[x][k];
vis[x][k]=1;
RG int ret=2333+(!k&&fa>=0);
for(int i=head[x];i;i=nxt[i]){
RG int v=to[i];
if(v==fa) continue;
ret+=dfs(v,1,x);
}
if(k||fa<0){
RG int sum=(fa>=0);
for(int i=head[x];i;i=nxt[i]){
RG int v=to[i];
if(v==fa) continue;
sum+=dfs(v,0,x);
}
ret=min(ret,sum);
}
return f[x][k]=ret;
}
il void work(){
int ans=0;
memset(vis,0,sizeof(vis));
for(RG int i=0;i<n;i++) if(!vis[i][0]) ans+=dfs(i,0,-1);
printf("%d %d %d\n",ans/2333,m-ans%2333,ans%2333);
}
int main(){ T=gi(); while(T--){ init(); work(); } return 0; }