【vijos】1770 大内密探(树形dp+计数)

https://vijos.org/p/1770

不重不漏地设计状态才能正确的计数QAQ

虽然可能最优化是正确的,但是不能保证状态不相交就是作死。。。。

之前设的状态错了。。。

应该设

f[i][0]表示i点不取且至少有一个儿子取,且保证i点被覆盖

f[i][1]表示i点取儿子任意,且保证i点被覆盖

f[i][2]表示i点不取且i点的儿子也不取,保证i点不被覆盖!(即留给父亲覆盖)

f[i][2]表示i点不取且儿子也不取。并不是表示i点不取儿子任意!!!!!!!!!!要不然这样会出现交的情况!假设使用后者,那么就会产生和f[i][0]一样的状态!!!!!!

然后我们分别计数

g[i][0]表示f[i][0]的方案数,g[i][1]表示f[i][1]的方案数,g[i][2]表示f[i][2]的方案数,那么有初始化

g[i][0]=0 当i没有儿子时

而转移f[i][1]和f[i][2]很简单,即

f[i][1]=sigma{ min{f[j][0], f[j][1], f[j][2]} (j是i儿子) }

f[i][2]=sigma{ f[j][0] (j是i儿子) }

方案的话加法乘法原理上。。。

f[i][0]转移非常麻烦(因为还要顾及到g[i][0],要做到不重不漏!)

首先我们知道,至少要有一个儿子选中状态才能转移。

如果不小心,很容易得到

f[i][0]=min{f[j][1]+sigma{min{f[k][0], f[k][1]}} (j和k均为i儿子且j!=k) }

这样虽然可以用技巧实现O(n)转移,但是方案却不能够得到!

比如i有2和3这两个儿子,f[3][0]=5, f[3][1]=3, f[2][0]=6, f[2][1]=3;那么转移的时候,两次决策都是f[2][1]+f[3][1]或f[3][1]+f[2][1]!!!!!这样显然不能计数。。重合了。。

所以我之前就这样sb了。。

那么我们考虑如何去重?我们分析得到,我们枚举要取的儿子时,之前枚举过的全部给取f[k][0],没有取过的任意!!!!这样就不会重!

那么问题就好解决了,我们在枚举儿子时,维护一个前缀和f[k][0],k是已经枚举过的,然后再维护一个后缀和,表示sum{min{f[l][0], f[l][1]}},l是未枚举过的

计数的方法相同,那么问题就解决了orz

#include <cstdio>
#include <cstring>
#include <cmath>
#include <string>
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#include <map>
using namespace std;
typedef long long ll;
#define pii pair<int, int>
#define mkpii make_pair<int, int>
#define pdi pair<double, int>
#define mkpdi make_pair<double, int>
#define pli pair<ll, int>
#define mkpli make_pair<ll, int>
#define rep(i, n) for(int i=0; i<(n); ++i)
#define for1(i,a,n) for(int i=(a);i<=(n);++i)
#define for2(i,a,n) for(int i=(a);i<(n);++i)
#define for3(i,a,n) for(int i=(a);i>=(n);--i)
#define for4(i,a,n) for(int i=(a);i>(n);--i)
#define CC(i,a) memset(i,a,sizeof(i))
#define read(a) a=getint()
#define print(a) printf("%d", a)
#define dbg(x) cout << (#x) << " = " << (x) << endl
#define error(x) (!(x)?puts("error"):0)
#define printarr2(a, b, c) for1(_, 1, b) { rep(__, c) cout << a[_][__] << ‘\t‘; cout << endl; }
#define printarr1(a, b) for1(_, 1, b) cout << a[_] << ‘\t‘; cout << endl
inline const int getint() { int r=0, k=1; char c=getchar(); for(; c<‘0‘||c>‘9‘; c=getchar()) if(c==‘-‘) k=-1; for(; c>=‘0‘&&c<=‘9‘; c=getchar()) r=r*10+c-‘0‘; return k*r; }
inline const int max(const int &a, const int &b) { return a>b?a:b; }
inline const int min(const int &a, const int &b) { return a<b?a:b; }
#define rdm(x,i) for(int i=ihead[x]; i; i=e[i].next)

const int N=100005, oo=~0u>>1, MD=1000000007;
int ihead[N], cnt, n, f[N][3], l, r[N], st[N];
ll d[N][3], suml, sumr[N];
struct ED { int to, next; }e[N<<1];
ll mul(ll a, ll b) { return ((a%MD)*(b%MD))%MD;}
ll Plus(ll a, ll b) { return ((a%MD)+(b%MD))%MD;}
void add(int u, int v) {
	e[++cnt].next=ihead[u]; ihead[u]=cnt; e[cnt].to=v;
	e[++cnt].next=ihead[v]; ihead[v]=cnt; e[cnt].to=u;
}
void dfs(int x, int fa) {
	int t1=1, t2=0, s1=1, s2=1, y;
	rdm(x, i) if((y=e[i].to)!=fa) {
		ll tp=0;
		dfs(y, x);
		int mn=min(min(f[y][0], f[y][1]), f[y][2]);

		if(f[y][0]==mn) tp+=d[y][0];
		if(f[y][1]==mn) tp+=d[y][1];
		if(f[y][2]==mn) tp+=d[y][2];
		s1=mul(s1, tp); tp=0;
		s2=mul(s2, d[y][0]);

		t1+=mn;
		t2+=f[y][0];
	}
	f[x][1]=t1;
	f[x][2]=t2;

	d[x][1]=s1;
	d[x][2]=s2;

	int sz=0;
	rdm(x, i) if(e[i].to!=fa) st[++sz]=e[i].to;
	r[sz+1]=0; sumr[sz+1]=1; suml=1; l=0;
	for3(i, sz, 1) {
		y=st[i];
		int mn=min(f[y][0], f[y][1]); ll tp=0;
		if(f[y][0]==mn) tp+=d[y][0];
		if(f[y][1]==mn) tp+=d[y][1];
		r[i]=r[i+1]+mn;
		sumr[i]=mul(sumr[i+1], tp);
	}
	f[x][0]=N;
	for1(i, 1, sz) {
		if(l+f[st[i]][1]+r[i+1]<f[x][0]) {
			f[x][0]=l+f[st[i]][1]+r[i+1];
			d[x][0]=mul(d[st[i]][1], mul(suml, sumr[i+1]));
		}
		else if(l+f[st[i]][1]+r[i+1]==f[x][0]) {
			d[x][0]=Plus(d[x][0], mul(d[st[i]][1], mul(suml, sumr[i+1])));
		}
		if(f[st[i]][0]==N) break;
		l+=f[st[i]][0];
		suml=mul(suml, d[st[i]][0]);
	}
}
int main() {
	read(n);
	for1(i, 1, n-1) add(getint(), getint());
	int root=1;
	dfs(root, -1);
	int ans1=min(f[root][1], f[root][0]), ans2=0;
	if(ans1==f[root][0]) ans2=Plus(ans2, d[root][0]);
	if(ans1==f[root][1]) ans2=Plus(ans2, d[root][1]);
	printf("%d\n%d\n", ans1, ans2);
	return 0;
}

  


背景

大内密探,负责秘密保护皇上,还有保护皇宫内外一切产业。——大内密探零零七

描述

在古老的皇宫中,有N个房间以及N-1条双向通道,每条通道连接着两个不同的房间,所有的房间都能互相到达。皇宫中有许多的宝物,所以需要若干个大内密探来守护。一个房间被守护当切仅当该房间内有一名大内密探或者与该房间直接相邻的房间内有大内密探。

现在身为大内密探零零七的你想知道要把整个皇宫守护好至少需要多少名大内密探以及有多少种安排密探的方案。两种方案不同当且仅当某个房间在一种方案有密探而在另一个方案内没有密探。

格式

输入格式

第一行一个正整数N.(1<=N<=100000)
后面N-1行,每行两个正整数a和b,表示房间a和房间b之间有一条无向通道。

房间的编号从1到N

输出格式

第一行输出正整数K,表示最少安排的大内密探。

第二行输出整数S,表示有多少种方案安排最少的密探,由于结果可能较大,请输出方案数mod 1000000007的余数。

样例1

样例输入1[复制]

7
2 1
3 1
4 2
5 1
6 2
7 6

样例输出1[复制]

3
4

限制

每个测试点1s

提示

30%保证:n <= 10
70%保证:n <= 1000
100%保证:n <= 100000

时间: 2024-10-09 14:18:51

【vijos】1770 大内密探(树形dp+计数)的相关文章

Vijos p1770 大内密探 树形DP+计数

4天终于做出来了,没错我就是这么蒟蒻.教训还是很多的. 建议大家以后编树形DP不要用记忆化搜索,回溯转移状态个人感觉更有条理性. 大神题解传送门 by iwtwiioi 我的题解大家可以看注释"//"部分 本题我用的树形DP中dp[x][fa][need]表示编号为x的节点的父亲选(1)没选(0),x的父亲需(1)不需要(0)其他节点来覆盖. 若父亲节点选了,则need肯定为0,所以不存在fa==1而need==1的状态,相当于浪费了¼的空间.毕竟数据范围比较小,而且程序要有可读性!程

[vijos 1770]大内密探

描述 在古老的皇宫中,有N个房间以及N-1条双向通道,每条通道连接着两个不同的房间,所有的房间都能互相到达.皇宫中有许多的宝物,所以需要若干个大内密探来守护.一个房间被守护当切仅当该房间内有一名大内密探或者与该房间直接相邻的房间内有大内密探. 现在身为大内密探零零七的你想知道要把整个皇宫守护好至少需要多少名大内密探以及有多少种安排密探的方案.两种方案不同当且仅当某个房间在一种方案有密探而在另一个方案内没有密探. 格式 输入格式 第一行一个正整数N.(1<=N<=100000) 后面N-1行,每

Vijos p1518河流 树形DP

https://vijos.org/p/1518 这题代码我基本是抄的,实在太难想了.但是也学到了一些东西. 比如:多叉树转二叉树存,这个细细一想,确实使得在dfs的时候,实现起来方便很多. 说一说具体 dfs的思路,思路和网上那个一模一样的,我刚学树形dp,可能上网看看总结下套路比较好. 设dfs(cur, hasPoint, k, dis)表示,现在处理到cur这个节点,离cur最近的那个仓库节点是hasPoint, 剩下k次设置仓库的机会,还有就是cur的爸爸距离hasPoint的距离.

vijos 1180 选课 树形DP

描述 学校实行学分制.每门的必修课都有固定的学分,同时还必须获得相应的选修课程学分.学校开设了N(N<300)门的选修课程,每个学生可选课程的数量M是给定的.学生选修了这M门课并考核通过就能获得相应的学分. 在选修课程中,有些课程可以直接选修,有些课程需要一定的基础知识,必须在选了其它的一些课程的基础上才能选修.例如<Frontpage>必须在选修了<Windows操作基础>之后才能选修.我们称<Windows操作基础>是<Frontpage>的先修课

【vijos】1892 树上的最大匹配(树形dp+计数)

https://vijos.org/p/1892 这个必须得卡评测机+手动开栈才能卡过QAQ 手动开栈我百度的... int size=256<<20; //256MB char *p=(char*)malloc(size)+size; __asm__("movl %0, %%esp\n" :: "r"(p)); 然后我交了无数发,然后才卡过.... 我们设状态 f[i][0]表示i节点与儿子的边一个也不选 f[i][1]表示i节点只选一条与儿子的边 g

CodeForces 158E Phone Talks 树形dp+计数

题目链接:点击打开链接 题意:给定n个点的树,常数d 给出每个点的权值,问有多少种划分方法使得划分后每个连通块里的最大权值-最小权值<=d 思路:点击打开链接 枚举每个点i 使得i是集合中的最小值. 则枚举时已经使得i是最小值,然后这个问题就变成单纯的划分问题了,上面链接里的题解已经很详尽了 import java.io.PrintWriter; import java.text.DecimalFormat; import java.util.ArrayDeque; import java.ut

UVA 11174 Stand in a Line 树形dp+计数

题目链接:点击打开链接 题意:白书的P103. 加个虚根就可以了...然后就是一个多重集排列. import java.io.PrintWriter; import java.util.ArrayList; import java.util.Scanner; public class Main { static int N = 40100; ArrayList<Integer>[] G = new ArrayList[N]; static long mod = 1000000007; long

Codeforces 486D Valid Sets 树形dp+计数

题目链接:点击打开链接 题意:给定常数d 和 n,表示n个点的树,每个点有点权 问:有多少个点集使得点集中的点两两可达且最大点权-最小点权<=d 思路: 对于每个点,计算包含这个点且这个点作为点集中的最小点权时的个数,枚举每个点. import java.io.PrintWriter; import java.text.DecimalFormat; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.

Vijos 1523 贪吃的九头龙 【树形DP】

贪吃的九头龙 背景 安徽省芜湖市第二十七中学测试题 NOI 2002 贪吃的九头龙(dragon) Description:OfficialData:OfficialProgram:Converted by JackDavid127 描述 传说中的九头龙是一种特别贪吃的动物.虽然名字叫"九头龙",但这只是说它出生的时候有九个头,而在成长的过程中,它有时会长出很多的新头,头的总数会远大于九,当然也会有旧头因衰老而自己脱落. 有一天,有M个脑袋的九头龙看到一棵长有N个果子的果树,喜出望外,