树与图上的计数专题


很明显可以用小根堆来处理 但是数据范围不允许

考虑线性想法 这个做法很好很实用

针对每次维护最小的且每次最多增加一个最小 都可以用这样的方法 !!!!完全可以替代小根堆

最后pufer转化为树反过来就好了 思路是一样的

#include
using namespace std;
#define lowbit(x) x&(-x)
#define int ll
#define ll long long
const int maxn=5e6+5;
int cnt;
int a[maxn],ans[maxn],in[maxn];
int n,c; 
signed main(){
	cin>>n>>c;
	if(c==1){
		for(int i=1;i<=n-1;i++)cin>>a[i],in[a[i]]++;
		int minn;
		for(int i=1;i<=n-1;i++)
		if(!in[i]){
			minn=i;break;
		}
		int now=minn;
		while(cnt>a[i],in[a[i]]++;
		a[n-1]=n;in[n]++;
		int minn;
		for(int i=1;i<=n-1;i++)
		if(!in[i]){
			minn=i;break;
		}
		int now=minn;
		for(int i=1;i<=n-1;i++){
			ans[now]=a[i];
			in[a[i]]--;
			if(!in[a[i]]&&a[i]

https://www.luogu.com.cn/problem/P2290 就是一道经典的板子题

题目要求完全二分图个数 考虑从完全图来推 完全图pufer会剩下两个点 到完全二分图这里这两个点一定是分居一边

一边的一个点一定有出边连向另一边 如果一一对应的话就是分别有n-1个和m-1个

选n-1次有m个选择方案 选m-1个有n个选择方案 所以答案就是ksm(n-1,m)× ksm(m-1,n)

注意long long 相乘可能会爆 所以要用到快速乘

点击查看代码
#include
typedef long long ll;
typedef long double ld;
ll n,m,p;
ll mul(ll a,ll b){
	ll res=0;
	while(b){
		if(b&1)res=(res+a)%p;
		b>>=1;a=a*2%p; 
	}
	return res;
}
ll ksm(ll a,ll b)
{
    ll ret=1;
    for(;b;b>>=1,a=mul(a,a))
        if(b&1) ret=mul(ret,a);
    return ret;
}
int main()
{
    scanf("%lld%lld%lld",&n,&m,&p);
    printf("%lld\n",mul(ksm(n,m-1),ksm(m,n-1)));
}

点击查看代码
#include 
#define ll long long
#define ull unsigned long long
#define met(a, b) memset(a, b, sizeof(a))
#define rep(i, a, b) for(int i = a; i <= b; i++)
#define bep(i, a, b) for(int i = a; i >= b; i--)
#define re return 0
using namespace std;
const ll mod = 1e9 + 7;
const double PI = acos(-1);
const ll INF = 2e18+1;
const int inf = 1e9 + 15;
const double eps = 1e-7;
const int maxn = 1e6 + 5;
ll d[3003][3003], p[3003][3003];
string ma[3003];
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);
    int n, m; cin >> n >> m;
    n--, m--;
    rep(i, 0, n) cin >> ma[i];
    d[0][0] = p[0][0] = 1;
    rep(i, 0, n){
        rep(j, 1, m){
            if(ma[i][j] == '.'){
                if(i) d[i][j] += d[i-1][j];
                if(j) d[i][j] += d[i][j-1];
                d[i][j] %= mod;
            }
        }
    }
    rep(i, 1, n){
        rep(j, 0, m){
            if(ma[i][j] == '.'){
                if(i) p[i][j] += p[i-1][j];
                if(j) p[i][j] += p[i][j-1];
                p[i][j] %= mod;
            }
        }
    }
    cout << ((d[n-1][m]*p[n][m-1])%mod - (d[n][m-1]*p[n-1][m])%mod + mod) % mod << endl;
    re;
}

这个题目平移一条直线让我想到了卡特兰数

跨过y=x的线就相当于到了y=x+1的线 再对称一下就是(-1,1)到(n,n)

下面的链接讲得很详细

叶子期望=(各个情况叶子总和)/各个情况

难点就在于怎么算出各个情况下叶子总数

对于每棵n个点的二叉树,如果里面有k个叶节点,那么我们分别把这k个叶子删去会得到k棵n?1个点的二叉树;

而每棵n?1个点的二叉树恰好有n个位置可以悬挂一个新的叶子,所以每棵n?1个点的二叉树被得到了n次;

这个是实验出来的 我也不知道怎么推 不过确实神奇

综上,我们即可得出结论:所有n个点的二叉树的叶子个数和等于n-1个点的二叉树个数×n。

这个题是卡特兰数另一个金典的公式的几何形态

一个点的度数为d 在prufer序列中出现的次数是d-1 每个点度数的价值可以预处理出来

总容量n-2 但是有n个点的价值是要计算的 因为有些点的度数是1 容量为0 不会出现在prufer序列当中 但是该价值任然要计算的

一般的背包是没法处理的 可以多开一维 但是复杂度不允许 考虑对每个点的价值都-f(1) 最后减了多少个f(1)就是选了多少个度数不为1的点

最后加上n个度数为1的点的价值 就是最终的答案 很巧妙!!!!