来自学长的馈赠2

70分rank 28

 T1:数论(求逆元)+快速幂求期望(线性性)

T2:图论

T3:数论:catalan数

T4:预设性DPhttps://www.cnblogs.com/TSTYFST/p/16515258.html

T1:

https://tg.hszxoj.com/contest/444/problem/1

求x取值的期望

首先介绍一下我调了3-4个小时的暴力(g大神帮忙成果)

dp[i][j]第i次选数后,x=j的概率

如果直接循环选的是那些数会T,于是合并同类项,如果A[I]==A[J]就开一个值域的数组表示这个val的出现次数

转移就是累加,然后注意/(n)要利用逆元,看清楚n的取值范围

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<string>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<iomanip>
#include<bitset>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#define _f(i,a,b)  for(register int i=a;i<=b;++i)

#define f_(i,a,b)  for(register int i=a;i>=b;--i)
#define ll long long
#define INF 1000000000

#define chu printf
//太大会炸

using namespace std;
inline int re()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
const int N=1000+10;
const ll MOD=1e9+7;
ll f[3000][400];
int n,m,mod;
int a[100000+100];
inline ll ny(ll x,int y)
{
    ll ans=1,a=x;
    while(y)
    {
        if(y&1)
        {
            ans=ans*a%MOD;
        }
        y>>=1;
        a=a*a%MOD;
    }
    return ans;
}
inline ll qpow(ll x,int y)
{
    ll ans=1,a=x;
    while(y)
    {
        if(y&1)
        {
            ans=ans*a%mod;
        }
        y>>=1;
        a=a*a%mod;
    }
    return ans;
}
int main()
{
    n=re(),m=re(),mod=re();
    if(mod==2)
    {
        chu("1");return 0;
    }
    if(n==1)
    {
        int xp=re();
        chu("%lld",qpow(xp,m)%MOD);return 0;
    }
    for(int i=1;i<=n;i++)
    {
        int xp=re();
        a[xp]++;
    }
    f[0][1]=1;
    for(int i=0;i<=m-1;i++)
    {
        for(int j=1;j<mod;j++)
        {
            for(int k=1;k<mod;k++)
            {
                int can=(j*k%mod);
                f[i+1][can]=f[i+1][can]+f[i][j]*a[k];
                f[i+1][can]%=MOD;
            }
        }
    }
    ll ans=0;
    ll Ny=ny(n,MOD-2);
    Ny=ny(Ny,m);
    for(int i=1;i<mod;++i)
    {
        ans+=f[m][i]*i%MOD;
        ans%=MOD;
    }
    ans=ans*Ny%MOD;
    chu("%lld",ans);
    return 0;
}
/*
2 1000 281
1 2
1 1000000000 107
53
*/
50%

 

 T1 概率期望的题目 正解: 其实懂了DP得方法这个也就差不多了

DP的循环本质上就是(cnt1+cnt2+cnt3+...+cntmod-1)^m

这个多项式不能合并(因为cnt1乘以cnt1它的实际意义加和应该加到[cnt1*cnt1%mod]里面)

所以不能直接加再快速幂 然后但是它可以 类似矩阵快速幂,用一个g数组存()本身不断乘方的值 快速幂就可以解决了

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<string>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<iomanip>
#include<bitset>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#define _f(i,a,b)  for(register int i=a;i<=b;++i)

#define f_(i,a,b)  for(register int i=a;i>=b;--i)
#define ll long long
#define ull unsigned  long long
#define INF 1000000000
#define chu printf
//太大会炸

using namespace std;
inline int re()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
const int N=100000+10,MOD=1e9+7;
int f[N],g[N],fg[N],n,m,mod;
inline int qpow(int a,int b)
{
    int anse=1;
    while(b)
    {
        if(b&1)
        {
            anse=(ll)anse*a%MOD;
        }
        b>>=1;
        a=(ll)a*a%MOD;
    }
    return anse;
}
signed main()
{
    //freopen("seq2.in","r",stdin);
    n=re(),m=re(),mod=re();
    _f(i,1,n)
    {
        int x=re();g[x]++;
    }
    //计算^m,g:a,f:ans
    f[1]=1;
    int ha=m;
    while(m)
    {
        if(m&1)
        {//看情况,fg[1]貌似要变成1
            _f(i,1,mod-1)
            {
                _f(j,1,mod-1)
                {
                    int can=i*j%mod;
                    fg[can]=((ll)fg[can]+(ll)f[i]*g[j]%MOD)%MOD;
                }
            }
            _f(i,1,mod-1)f[i]=fg[i],fg[i]=0;
        }
        _f(i,1,mod-1)
        {
            _f(j,1,mod-1)
            {
                int can=i*j%mod;
                fg[can]=((ll)fg[can]+(ll)g[i]*g[j]%MOD)%MOD;
            }
        }
        _f(i,1,mod-1)g[i]=fg[i],fg[i]=0;
        m>>=1;
    }
    int ans=0;
    _f(i,1,mod-1)
    {
        ans=((ll)ans+(ll)f[i]*i%MOD)%MOD;
    }
    chu("%lld",1LL*ans*qpow(qpow(n,ha),MOD-2)%MOD);
    return 0;
}
View Code

 

 

 

T2:

(30s)没拿到暴力10分(又是低错,+=写成=)

图论:其实就是树的重心+消元思想

代码里有讲解

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<string>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<iomanip>
#include<bitset>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#define _f(i,a,b)  for(register int i=a;i<=b;++i)

#define f_(i,a,b)  for(register int i=a;i>=b;--i)
#define ll long long
#define INF 1000000000

#define chu printf
//太大会炸

using namespace std;
inline int re()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
const int N=100000+10;
const ll MOD=1e9+7;
struct node
{
    int to,nxt;
}e[200000+100];
int tot,head[100000+10];
inline void add(int x,int y)
{
    e[++tot].to=y,e[tot].nxt=head[x],head[x]=tot;
}
int siz[100000+10],f[100000+10],n;
inline void dfs(int x,int fa)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==fa)continue;
        dfs(to,x);
        siz[x]+=siz[to];
        f[x]=f[x]+f[to]+siz[to];
    }
}
inline void deal(int x,int fa)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==fa)continue;
        f[to]=f[x]+siz[1]-siz[to]-siz[to];
        deal(to,x);
    }
    head[x]=0;
}
int xi[100000+10];
inline void dfs_2(int x,int fa)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==fa)continue;
        xi[to]++,xi[x]--;
        dfs_2(to,x);
    }
}
inline void dfs_3(int x,int fa)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==fa)continue;
        siz[to]=(siz[1]+f[x]-f[to])/2;
        dfs_3(to,x);
    }
}
inline void dfs_4(int x,int fa)
{
    for(int i=head[x];i;i=e[i].nxt)
    {
        int to=e[i].to;
        if(to==fa)continue;
        siz[x]-=siz[to];
        dfs_4(to,x);
    }
}
int main()
{
    int T_t=re();
    while(T_t--)
    {
        n=re();
        tot=0;
        _f(i,1,n-1)
        {
            int u=re(),v=re();add(u,v);add(v,u);
        }
        int t=re();
        if(t==0)
        {
            _f(j,1,n)siz[j]=re(),f[j]=0;
            dfs(1,-1);//求im,先求出im[1],再递推
           // chu("dfs\n");
            deal(1,-1);
            _f(op,1,n)chu("%d ",f[op]);
            chu("\n");
        }
        else//如果给出b,那我只能按照一条链的算 ,1是起点
        {
           _f(j,1,n)f[j]=re(),siz[j]=0,xi[j]=0;
           dfs_2(1,-1);
           ll zuo=0;
           _f(j,1,n)zuo+=(ll)f[j]*xi[j];
           zuo+=2*f[1];
           zuo/=(n-1);
           siz[1]=zuo;
          // chu("siz[:%d\n",siz[1]);
           dfs_3(1,-1);
          // _f(i,1,n)chu("siz[:%d\n",siz[i]);
            dfs_4(1,-1);
           _f(i,1,n)chu("%d ",siz[i]),head[i]=0;
           chu("\n");
        }
    }
    return 0;
}
/*
发现就算拼劲全力也就40分
3
2
1 2
1
17 31
2
1 2
0
31 17
7
1 4
1 3
3 5
3 7
1 2
2 6
0
25 4 7 21 8 3 6

66 126 98 98 156 194 160

1
7
1 2 27
2 3 3
3 4 2
4 5 1
5 6 4
6 7 8

1
5
1 3
1 2
3 5
3 4
1
45 63 31 41 37

*/
View Code

 T3

给你坐标,只能走n步,有要求的让你走来走去

数论:数列:catalan数列

具体知识看数论

opt=0:

枚举哪些步数向上走,哪些向下走,乘法原理

opt=1:catalan

opt=2

这个比较有意思,我需要保证每次dot改变方向的时候都在原点,不妨设dp[x],走x步到达原点的方案数

怎么转移?

枚举第一次到达原点的方案数,可以保证方案之间一定不会重复(经常使用的技巧)

怎么保证是第一次到达?catalan(j/2-1),留出第一步和最后一步,剩下的还是catalan,就可以了

opt=3

一样

#include<iostream>
#include<cstring>
#include<cmath>
#include<cstdio>
#include<string>
#include<cstdlib>
#include<ctime>
#include<algorithm>
#include<iomanip>
#include<bitset>
#include<map>
#include<queue>
#include<deque>
#include<vector>
#define _f(i,a,b)  for(register int i=a;i<=b;++i)

#define f_(i,a,b)  for(register int i=a;i>=b;--i)
#define ll long long
#define INF 1000000000

#define chu printf
//太大会炸

using namespace std;
inline int re()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0')
    {
        if(ch == '-')
        {
            f = -1;
        }
        ch = getchar();
    }
    while(ch >= '0' && ch <= '9')
    {
        x = (x << 1) + (x << 3) + (ch^48);
        ch = getchar();
    }
    return x * f;
}
const int N=1000+10;
const ll MOD=1e9+7;
int n,opt;
ll ny[100000+100],nys[100000+100],fac[100000+100];
ll f[100000+100];
inline ll C(int x,int y)
{
    return fac[x]*nys[x-y]%MOD*nys[y]%MOD;
}
inline ll Cat(int x)
{
    return C(x*2,x)*ny[x+1]%MOD;
  //return (C(2*x,x)-C(2*x,x-1)+MOD)%MOD;
}
void pre()
{
    ny[0]=nys[0]=fac[0]=ny[1]=nys[1]=fac[1]=1;
    _f(i,2,100010)
    {
        fac[i]=fac[i-1]*i%MOD;
        ny[i]=(MOD-MOD/i)*ny[MOD%i]%MOD;
        nys[i]=nys[i-1]*ny[i]%MOD;
      //  chu("ny;%lld(%lld)\n",nys[i],nys[i-1]);
    }
}
ll ans;
int main()
{
    pre();
    n=re(),opt=re();
    if(opt==0)
    {
        for(int k=0;k<=n;k+=2)
        {
            //chu("C:%lld\n",C(k,k/2));
            ans+=C(k,k/2)*C((n-k),(n-k)/2)%MOD*C(n,k)%MOD;
            ans%=MOD;
        }
        chu("%lld",ans);
    }
    else if(opt==1)
    {
        chu("%lld",Cat(n/2));//
    }
    else if(opt==2)
    {
        f[2]=4;f[0]=1;
        for(int i=4;i<=n;i+=2)//i步数回到原点的方案数
        {
            for(int j=2;j<=i;j+=2)//j步数第一次回到原点
            {
                f[i]=(f[i]+4*f[i-j]%MOD*Cat(j/2-1)%MOD)%MOD;
//这里注意f[0]要初始化成1,因为当全部默认成第一次回到原点(一直朝着一个方向走),也是要算进去的
            }
        }
       // chu("f[4]:%lld\n",f[4]);
        chu("%lld",f[n]);
    }
    else
    {
        for(int k=0;k<=n;k+=2)//k是步数
        {
            ans+=Cat(k/2)*Cat(n/2-k/2)%MOD*C(n,k)%MOD;
            ans%=MOD;
        }
        chu("%lld",ans);
    }
    return 0;
}
/*


*/
View Code

 T4:

给你一个排列n,求相邻数取max的加和<=K的方案数
dp[i][j][k]:当前已经安排了i个数(从小到大),有j个空位,max为k的方案数
空位:两两数字之间
预设性DP
表示遇到统计答案时状态转移提前预设出还没进行选择的选项,以方便进行转移
像这道题,如果正常的话肯定是想着
dp[i][j][k]:当前安排的数为i(状态压缩),有j个空位,max为k的方案数
但是一来空间开不下,二来时间也会T
所以预设出我2 2数之间的空位,这样a_b是不会有贡献的,ab的贡献会算进去
关键是转移,我虚设出的空位不一定是在哪个确切的位置,因为它本身就是由排列组合计算出的无数种方案叠加的结果
所以转移也利用的是乘法原理(省略很多冗余计算)

类比:
https://tg.hszxoj.com/contest/398/problem/2
提高过度的中国象棋
只能每行每列放最多两个
f[i][j][K]:表示到i行为止,有j列放了1个子,k行放了0个子的方案数
5种情况枚举就行,你会发现有时候状态压缩能干的事好像预设也可以干
const int N=2000+10,M=320;
int mod=9999973;
int f[108][108][108];
int n,m;
int main()
{
    freopen("chess.in","r",stdin);
    freopen("chess.out","w",stdout);
    n=re(),m=re();
    if(n<m)
    {
        swap(n,m);
    }
    f[1][1][m-1]=m;//第一行放了一个 
    f[1][0][m]=1;//第一行只能j+k==m因为不可能一列有两个 一个也不放
    f[1][2][m-2]=m*(m-1)/2; //放了两个 
    _f(i,2,n)
    {
        _f(j,0,m)
        _f(k,0,m-j)
        {
            f[i][j][k]=f[i-1][j][k];//le7*
            if(j-1>=0)f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j-1][k+1]*(k+1)%mod);//有0的放1
            f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j+1][k]*(j+1)%mod);//有1的放1
            f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j][k+1]*j*(k+1)%mod);//有0的放1+有1的放1
            if(j-2>=0)f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j-2][k+2]*(k+2)*(k+1)/2%mod);//有0的放2
            f[i][j][k]=(f[i][j][k]+1LL*f[i-1][j+2][k]*(j+2)*(j+1)/2%mod);//有1的放2
            f[i][j][k]%=mod;
            //chu("f[%d][%d][%d]:%d\n",i,j,k,f[i][j][k]);
        }
    }
    int ans=0;
    _f(i,0,m)
    _f(j,0,m-i)
    {
        ans+=f[n][i][j];
        ans%=mod;
    }
    chu("%d",ans);
    return 0;
}

f[i][j][k]:
第i行有j列放了1个棋子,k列放了0个棋子的方案数量 
chess
const int N=1000+10;
const int MOD=998244353;
ll f[2][60][2500];//f[i][j][k]:安排到i数,有j个空位(几个没关系,表示几个数之间),maxtot为k的方案数
int main()
{
    int n=re(),m=re(),mx=n*n;
    int now=0;
    f[1][0][0]=1;
    _f(i,2,n)
    {

        _f(j,0,50)
        {
            _f(k,0,2400)f[now][j][k]=0;
        }
        int mx_now=min(i*i,m);
        _f(j,0,i)
        {
            _f(k,0,mx_now+2*i)
            {
               if(j>0)f[now][j][k]=(f[now][j][k]+f[now^1][j-1][k]*2)%MOD;//放两边无贡献,多了一个空位
                  // chu("from:%d %d %d\n",i-1,j-1,k);
                if(k>i-1)f[now][j][k]=(f[now][j][k]+f[now^1][j][k-i]*2)%MOD;//放两边有1个贡献,没有多空位
                //if(j==0) continue;
                   // chu("from:%d %d %d\n",i-1,j,k-i);
                if(j>0)f[now][j][k]=(f[now][j][k]+f[now^1][j-1][k]*(j-1))%MOD;//放中间无贡献,多一个空位
                  //  chu("from:%d %d %d\n",i-1,j-1,k);
                if(k>i-1&&j>0)f[now][j][k]=(f[now][j][k]+f[now^1][j][k-i]*j*2)%MOD;//放中间有贡献,没有多空位
                   // chu("from:%d %d %d\n",i-1,j,k-i);
                f[now][j][k]=(f[now][j][k]+f[now^1][j+1][k-2*i]*(j+1))%MOD;//放中间有2贡献,少了一个空位
                    //chu("from:%d %d %d\n",i-1,j+1,k-2*i);
                //中,有2,-1
              //  chu("from:%d %d %d\n",i-1,j-1,k);
             // chu("(%lld)f[%d[%d[%d:%lld\n",f[now^1][j+1][k-2*i],i,j,k,f[now][j][k]);

            }
        }
         now^=1;
    }
    ll ans=0;now^=1;
    _f(i,0,m)ans+=(f[now][0][i]),ans%=MOD;
    chu("%lld",ans);
    return 0;
}
View Code

 

posted on 2022-07-21 18:00  HZOI-曹蓉  阅读(93)  评论(5编辑  收藏  举报