【loli的胡策】训练1.14(组合数学+概率期望+乱搞)

T1

这里写图片描述
n,k,m<=1e6

题解:

这个题一看没有什么思路,其实可以想象成一个递推关系,仔细来说,对于题目描述的集合,第m位上的数字范围是[m,n-(k-m)],在枚举集合的时候,只要保证后面的元素大于前面的元素就行了,也就是说我们并不用管后面的数字,题目等价于:n=n-(k-m),k=m时的答案,于是现在只需要考虑k=m的情况

f(n,k) 表示现在是n个数,我要取第k个情况下的答案,注意这里的n个数并不代表固定的大小,因为我们取数只需要一个相对的大小
我们首先考虑 A[i][1] 的值,取数范围在 [1,n(k1)] ,根据字典序的定义,大小区分有两种:一个是 A[i][1]A[i+1][1] 相同,另一种是 A[i+1][1]=A[i][1]+1

我们先考虑第二种吧
如果 A[i][1]=xA[i+1][1]=x+1
那么 A[i][2..k] 一定是1..n的后缀
A[i+1][2..k] 一定是[x+2,x+3….]
这样 A[i][m]=n,A[i+1][m]=x+k
答案就是 n(x+k)
这一种类型的答案就是
(枚举转换点)
nki=1nik=nk1i=1i=C2nk

<script type="math/tex; mode=display" id="MathJax-Element-12"></script>
第一种呢?
(枚举第一个数的值(相对大小))
n(k1)i=1f(ni,k1)=n1i=k1f(i,k1)

g(n,k)=C2nk
因此 f(n,k)=g(n,k)+n(k1)i=1f(ni,k1)

我们发现只有这个 g(m,i) 在贡献啊,现在考虑每个 g(m,i) 对答案的贡献,因为 g(m,i)=C2mi 相当于是将m-i+1划分为三个正整数(隔板法)

再看这个 f(n,k)=n(k1)i=1f(ni,k1)
相当于每次k-1的时候,n减去一个正整数,最后减到m,也就是将n-m划分成k-i个正整数
然后我们观察一下这两部分
枚举i时将 ni+1 划分成 nm mi+1 ,之后将n-m划分成k-i个正整数,将m-i+1划分成3个正整数
总的贡献就是将n-i+1划分成k-i+3个正整数! Cki+1ni

考虑边界:当k=1时,g(n,k)=n-1(直接看断点数量),所以i=1时是 Cki+1ni

代码:

#include <cstdio>
#define LL long long 
using namespace std;
const int mod=1e9+7;
const int N=1e6;
LL mul[N+5],inv[N+5];int n,m,k;
LL C(int n,int m){return mul[n]*inv[m]%mod*inv[n-m]%mod;}
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d%d%d",&n,&k,&m);
    n=n-k+m; k=m;
    mul[0]=mul[1]=1;
    inv[0]=inv[1]=1;
    for (int i=2;i<=n;i++) mul[i]=mul[i-1]*i%mod;
    for (int i=2;i<=n;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
    for (int i=1;i<=n;i++) inv[i]=inv[i]*inv[i-1]%mod;

    LL ans=0;
    for (int i=1;i<=k;i++)
    {
        int x=n-i,y=k-i+2;
        if (i==1) y=k;
        ans=(ans+C(x,y))%mod;
    }
    printf("%lld",ans);
}

T2:

这里写图片描述
这里写图片描述

题解:

这道题目的暴力就是dfs出每个人的位置,dfs出每辆车的位置,再dfs出那辆车对应哪个人取个max值(反正<=4的随便跑╮(╯▽╰)╭)
正解呢?
考虑一条边的贡献,考虑ta两边,一边是a个乘客b辆车,另一边是c个乘客d辆车,那么这条边的贡献系数是min(a,d)+min(b,c),再乘上边的长度
于是枚举每条边,左边子树大小为x,右边为n-x
min(a,d)的求法?
左边子树有s辆车的方案数是 Csmxs(nx)ms
(从m中选择s辆车,这s辆车随机分布在这x个点上,剩下的m-s辆车分布在右子树上)
通过计算后缀和我们就可以算出左边子树大于s辆车的方案数
所以 min(a,d)=i=1[ia][id] ,即枚举i,再乘上左边多于i辆车,右边多于i个人的方案数即可
PS:其实你并不用考虑车和人的区别,因为总数一样,求法也一样
min(b,c)是一样的啊
时间复杂度 O(nm)

注意你在求贡献系数的时候,不能每一次都求一个ksm,因为会T啊
一开始预处理组合数的时候,不要到n为止。。。因为最大的。。。是max(n,m)啊

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
#define LL long long
using namespace std;
const int mod=1e9+7;
const int N=5000;
int n,m,tot,nxt[N*2],point[N],v[N*2],c[N*2],size[N];
LL a[N],b[N],C[N][N],ans;
void init()
{
    int sb=max(n,m);
    C[0][0]=C[1][0]=C[1][1]=1;
    for (int i=2;i<=sb;i++)
    {
        C[i][0]=C[i][i]=1;
        for (int j=1;j<i;j++) C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
    }
}
void addline(int x,int y,int z)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=z;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; c[tot]=z;
}
LL ksm(LL a,LL k)
{
    LL ans=1;
    for (;k;k>>=1,a=a*a%mod) if (k&1) ans=ans*a%mod;
    return ans;
}
LL calc(int x)//左子树大小 
{
    LL num1=1,num2=ksm(n-x,m),inv=ksm(n-x,mod-2);
    for (int i=0;i<=m;i++) a[i]=C[m][i]*num1%mod*num2%mod,num1=num1*x%mod,num2=num2*inv%mod;
    for (int i=m;i>=0;i--) a[i]=(a[i]+a[i+1])%mod;

    num1=1,num2=ksm(x,m),inv=ksm(x,mod-2);
    for (int i=0;i<=m;i++) b[i]=C[m][i]*num1%mod*num2%mod,num1=num1*(n-x)%mod,num2=num2*inv%mod;
    for (int i=m;i>=0;i--) b[i]=(b[i]+b[i+1])%mod;
    LL ans=0;
    for (int i=1;i<=m;i++) ans=(ans+a[i]*b[i]%mod)%mod;
    return ans*2%mod;
}
void dfs(int now,int fa)
{
    size[now]=1;
    for (int i=point[now];i;i=nxt[i])
      if (v[i]!=fa)
      {
        dfs(v[i],now);
        size[now]+=size[v[i]];
        ans=(ans+calc(size[v[i]])*(LL)c[i]%mod)%mod;
      }
}
int main()
{
    freopen("b.in","r",stdin);
    freopen("b.out","w",stdout);
    scanf("%d%d",&n,&m);
    init();
    for (int i=1;i<n;i++)
    {
        int x,y,z;scanf("%d%d%d",&x,&y,&z);
        addline(x,y,z);
    }
    dfs(1,0);
    printf("%lld",ans);
}

T3:

这里写图片描述
n<=1000

题解:

这道题相当于有n个点0…n-1,第i个点的两个出边是i*2%n,(i*2+1)%n,求一条哈密顿回路

哈密顿回路
哈密顿图(哈密尔顿图)是一个无向图,由指定的起点前往指定的终点,途中经过所有其他节点且只经过一次

可以给每个点恰好一条出边和一条入边,这样就构成了若干个环
可以发现,连边后是一个大环的充分条件是:对于i=0…(n/2-1),i与i+n/2在一个连通块中

证明:
考虑0与n/2,因为ta们在一个连通块内,而且ta们的出边一样,所以0与1在一个连通块内,再考虑1与n/2+1,可以发现1和2和3都在一个连通块中
于是我们一开始令i的出边是2*i,i+n/2的出边是2*i+1,
枚举i,如果i与n/2+i不在一个连通块内,就交换ta们的出边,这样就并成一个大环了
(其实并不知道为什么相当于,只能挖下坑来)

代码:

#include <cstdio>
#include <iostream>
using namespace std;
int n,a[1002];
int main()
{
    freopen("c.in","r",stdin);
    freopen("c.out","w",stdout);
    scanf("%d",&n);
    int m=n/2;
    int k=(m+1)/2;
    for (int i=0;i<m;i++)
        if (i<k) a[i]=2*i+1,a[i+m]=2*i;
        else a[i]=2*i,a[i+m]=2*i+1;
    for (int i=0;i<m;i++)
    {
        bool ff=0;
        for (int j=a[i];j!=i;j=a[j])
          if (j==i+m) {ff=1;break;}
        if (!ff) swap(a[i],a[i+m]);
    }
    for (int i=a[0];i;i=a[i]) printf("%d",i&1);
    printf("0\n");
}

小结:

今天的T1当我把一个肯定不会爆int的变量装在LL里的时候,就已经狗带了。。
T3的表好评,不过出题人说什么有50pts<=40结果只有4组= =
大家真是太给力了,我没写完只粘了3个图就有80+人看= =

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值