计数专题

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

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

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

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

#include<bits/stdc++.h>
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<n-2){
			ans[++cnt]=a[now];
			in[a[now]]--;
			if(!in[a[now]]&&a[now]<minn)now=a[now];
			else{
				minn++;
				while(in[minn])minn++;
				now=minn;
			}
		}
		int res=0;
		for(int i=1;i<=n-2;i++)
		res^=(i*ans[i]);
		cout<<res;
	}else{
		for(int i=1;i<=n-2;i++)cin>>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]<minn)now=a[i];
			else{
				minn++;
				while(in[minn])minn++;
				now=minn;
			}
		}
		int res=0;
		for(int i=1;i<=n-1;i++)
		res^=(i*ans[i]);
		cout<<res;
	} 
     return 0;
}

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<cstdio>
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 <bits/stdc++.h>
#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)

下面的链接讲得很详细

https://www.cnblogs.com/wzxbeliever/p/11686144.html

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

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

对于每棵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的点的价值 就是最终的答案 很巧妙!!!!

杨老师的照相排列:

链接:https://www.acwing.com/problem/content/273/

标签是个简单题 想了半天还是去看了题解 我还是太菜了

这是个非常经典的dp 想一下怎么摆放满足条件 这样类型的题目一般都是从大到小(从小到大)开始摆放

不难想到我们只要摆放的时候一直满足一个阶梯形状 就满足条件 因为我们是从大到小放的

code:

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
int a[6];
ll dp[32][32][32][32][32]; 
int main(){
	int k;
	scanf("%d",&k);
	while(k){
		memset(dp,0,sizeof(dp));
		dp[0][0][0][0][0]=1;
		memset(a,0,sizeof(a));
		for(int i=1;i<=k;i++)scanf("%d",&a[i]);
		for(int i=0;i<=a[1];i++)
		for(int j=0;j<=a[2];j++)
		for(int k=0;k<=a[3];k++)
		for(int l=0;l<=a[4];l++)
		for(int m=0;m<=a[5];m++){
			if(i<a[1])dp[i+1][j][k][l][m]+=dp[i][j][k][l][m];
			if(j<a[2]&&j<i)dp[i][j+1][k][l][m]+=dp[i][j][k][l][m];
		    if(k<a[3]&&k<j)dp[i][j][k+1][l][m]+=dp[i][j][k][l][m];
		    if(l<a[4]&&l<k)dp[i][j][k][l+1][m]+=dp[i][j][k][l][m];
		    if(m<a[5]&&m<l)dp[i][j][k][l][m+1]+=dp[i][j][k][l][m];
		}
		cout<<dp[a[1]][a[2]][a[3]][a[4]][a[5]]<<endl;
		scanf("%d",&k);
	}
     return 0;
}

https://www.luogu.com.cn/problem/P2606

分析;又是一个与排列有关的计数题,

P(i)>P(i/2)这个条件很重要啊

也有P(2*i)>P(i),

也有P(2*i+1)>P(i)

像这种下标二倍的关系就要和二叉树考虑在一起

二叉树:i左儿子就是2*i,i的右儿子就是2*i+1

而且这颗二叉树是个完全二叉树

线性求完全二叉树的方案数 一颗完全二叉树 的左子树和右子树一定分别为完全为二叉树

所以左右子树的大小是一定的

根节点的值必须为最小值。再考虑剩下的i-1个节点。

很容易想到,

可以在这i-1个节点中选出l个节点作为左子树,剩下的r个节点作为右子树。所以得出转移:

f[i]=C(i-1,l)×f[l]×f[r]

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 1e6 + 5;
int n, PYZ, f[N], fac[N], Log[N], inv[N];
int qpow(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1) res = 1ll * res * a % PYZ;
        a = 1ll * a * a % PYZ;
        b >>= 1;
    }
    return res;
}
int C(int x, int y) {
    if (!y) return 1;
    int u = C(x / PYZ, y / PYZ), v = x % PYZ, w = y % PYZ, z;
    if (v < w) z = 0;
    else z = 1ll * (1ll * fac[v] * inv[w] % PYZ) * inv[v - w] % PYZ;
    return 1ll * u * z % PYZ;
}
int main() {
    int i, kx, l = 1, r = 1; n = read(); PYZ = read();
    fac[0] = 1; Log[0] = -1;
    for (i = 1; i <= n; i++) fac[i] = 1ll * fac[i - 1] * i % PYZ,
        Log[i] = Log[i >> 1] + 1;
    kx = min(PYZ - 1, n); inv[kx] = qpow(fac[kx], PYZ - 2);
    for (i = kx - 1; i >= 0; i--) inv[i] = 1ll * inv[i + 1] * (i + 1) % PYZ;
    f[1] = f[2] = 1; f[3] = 2;
    for (i = 4; i <= n; i++) {
        if (i - (1 << Log[i]) + 1 <= (1 << Log[i] - 1)) l++;
        else r++;
        f[i] = 1ll * (1ll * C(i - 1, l) * f[l] % PYZ) * f[r] % PYZ;
    }
    printf("%d\n", f[n]);
    return 0;
}

https://www.luogu.com.cn/problem/P1370

题目大意:Σ(1<=l<=r<=n)F(l, r),其中F(l,r)表示[l,r]之间的本质不同的子序列有多少个

分析:

如果本题数据是N2的话我就会做,但是要求是O(n)求出

同样的区间计数题固定端点L,再考虑dp

设dp[i]表示F[i,i]+F[i,i+1]+F[i,i+2]+....+F[i,n]

如果没有重复的数字话

dp[i]=(dp[i+1]<<1)+2

但是有重复怎么办?考虑容斥

若ai==aj(i<j)

则dp[i]-=dp[j+1]+1;

原因是 后面所有的F[j+1,j+1].....都会有一次重复(接ai,接aj)

那个1就是F[j,j]中选j或不选j中的选j的方案

并且j是i后面第一个与ai相同的位置

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 998244353
#ifdef ONLINE_JUDGE
char *TT,*mo,but[(1<<15)+2];
#define getchar() ((TT==mo&&(mo=(TT=but)+fread(but,1,1<<15,stdin)),TT==mo)?0:*TT++)
#endif
inline int read(){
    int x=0,c=0,f=1;
    for(;c<'0'||c>'9';c=getchar())f=c!='-';
    for(;c>='0'&&c<='9';c=getchar())x=x*10+c-'0';
    return f?x:-x;
}
int head[100010];
int a[100010],b[100010];
ll dp[100010];
ll ans;
int n;
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        a[i]=read();
        b[i]=a[i];
    } 
    sort(b+1,b+n+1);
    int cnt=unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++)a[i]=lower_bound(b+1,b+cnt+1,a[i])-b;
    dp[n]=2;
    head[a[n]]=n;
    for(int i=n-1;i>0;i--){
        dp[i]=(dp[i+1]*2+2)%mod;
        if(head[a[i]]){
            dp[i]=(dp[i]-dp[head[a[i]]+1]+mod-1)%mod;
            head[a[i]]=i;
        }
        head[a[i]]=i;
    }
    for(int i=1;i<=n;i++){
        ans=(ans+dp[i])%mod;
    }
    cout<<ans;
    return 0;
}

链接:https://ac.nowcoder.com/acm/problem/13611

题目描述
shy有一颗树,树有n个结点。有k种不同颜色的染料给树染色。一个染色方案是合法的,当且仅当对于所有相同颜色的点对(x,y),x到y的路径上的所有点的颜色都要与x和y相同。请统计方案数。

分析:有两种做法 一种是讲树转化为dfs序(一般两点之间的路径都是这样) 变为线性dp

还有一种做法就是计数

#include <bits/stdc++.h>
using namespace std;
static auto faster_iostream = []() { ios::sync_with_stdio(0); cin.tie(0); return 0; }();
typedef long long ll;
const int N = 1e3 + 5, M = 1e9 + 7;
ll qpow(ll x, ll n) { ll res = 1; for (; n; n >>= 1, x = x * x % M) if (n & 1) res = res * x % M; return res; }

ll fac[N];

ll comb(ll n, ll m) {
  return fac[n] * qpow(fac[m] * fac[n-m] % M, M - 2) % M;
}

int main() {
  int n, k;
  cin >> n >> k;

  fac[0] = fac[1] = 1;
  for (int i = 2; i <= max(n, k); i++) {
    fac[i] = fac[i-1] * i % M;
  }
  ll ans = 0;
  for (int i = 0; i <= min(n - 1, k - 1); i++) {
    ans += comb(n - 1, i) * comb(k, i + 1) % M * fac[i + 1] % M;
    ans %= M;
  }
  cout << ans << '\n';
}
posted @ 2022-04-25 09:32  wzx_believer  阅读(61)  评论(0)    收藏  举报