组合数学总结

---恢复内容开始---

  又水过一个专题。。。教练说这题都不好做///

  进了组合数学,当年叱吒的DP居然成了暴力。。。一些神奇的操作令我大口吃屎大开脑洞。

  一些常写的代码

  

 1 int qpow(int x,int k)
 2 {
 3     int ans=1;
 4     for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod;
 5     return ans;
 6 }
 7 long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
 8 int main()
 9 {
10     scanf("%d%d",&n,&k);fac[0]=1;
11     for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
12     inv[n]=qpow(fac[n],mod-2);
13     for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod;

 

  然后有一些常用的知识

  :乘法原理  加法原理  容斥原理  古典概型转组合数。。。 插空挡板等等

  看一下题目长什么样。。

  A.排队(自主研发) 这是个高考数学题 就是用插空法,但是要注意先考虑n 名男同学,m 名女同学和两名老师要排队,女生和老师都不能相邻,如果先让老师不邻,再考虑女生不相邻会算少,老师相邻时中间插女生会漏掉。

  

#75880 #A. 排队 Accepted 100 674 ms 604 KiB starsing (平安) 2019-06-26 18:50:09
#75800 #A. 排队 Wrong Answer 20 428 ms 608 KiB starsing (平安) 2019-06-26 15:58:51

 

  

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int a[4000000],b[4000020],c[4000020],n,m;
void ch(int a[],int i)
{
    int x=0,j;
    for(j=1;j<=a[0];j++)
    {
        a[j]=a[j]*i+x;
        x=a[j]/10;
        a[j]%=10;
    }
    a[j]=x;
    while(a[j]>9)
    {
        a[j+1]=a[j]/10;
        a[j]%=10;
        j++;
    }
    while(a[j]==0&&j>1)
        j--;
    a[0]=j;
}
void add(int a[],int b[])
{
    int i=1,x=0;
    while(i<=a[0]||i<=b[0])
    {
        //cout<<a[i]<<" "<<b[i]<<" ";
        c[i]=a[i]+b[i]+x;
        //cout<<c[i]<<endl;
        x=c[i]/10;
        c[i]=c[i]%10;
        i++;
    
    }
    if(x!=0&&i>1)
    {
        c[0]=i;
        c[i]=x;
    }
    else c[0]=i-1;
}
int main()
{
    scanf("%d%d",&n,&m);
    if(m>n+3) return printf("0\n"),0;
    a[0]=a[1]=1;b[0]=b[1]=1;
    ch(a,n);ch(a,n+1);ch(b,(2*n+2)*m);
    for(int i=n-m+4;i<=n+3;i++) ch(a,i);
    for(int i=n-m+4;i<=n+2;i++) ch(b,i);
    add(a,b);
    for(int i=1;i<=n;i++) ch(c,i);
    for(int i=c[0];i;i--) printf("%d",c[i]);
    puts("");
}
//排队 题解
/*
g++ 1.cpp -o 1
./1
1 1
*/
View Code

 

 

 

  B.Perm排列计数(自主研发) Pi<Pi/2的1~n排列令我大口吃屎。。。居然是下标。。。。然后搞一个二叉树,左右子树不影响,递归DP一下(题眼),bobo说学过树的都应该想到。

  

#include<iostream>
#include<cstdio>
using namespace std;
int n,p;
int f[2000020],s[2000020],fac[2000020],inv[2000020];
void dfs(int k)
{
    s[k]=1;
    if((k<<1)<=n) dfs(k<<1);
    if((k<<1|1)<=n) dfs(k<<1|1);
    s[k]+=s[k<<1]+s[k<<1|1];
    //if(s[k]==1) f[k]=1
    f[k]=1ll*fac[s[k]-1]*inv[s[k<<1]]%p*inv[s[k]-s[k<<1]-1]%p;
    if(f[k]==0)f[k]=1;
    if(f[k<<1]) f[k]=1ll*f[k]*f[k<<1]%p;
    if(f[k<<1|1]) f[k]=1ll*f[k]*f[k<<1|1]%p;
    //cout<<k<<" "<<s[k]<<" "<<f[k]<<endl;
}
int qpow(int x,int k)
{
    int ans=1;
    for(;k;k>>=1,x=1ll*x*x%p) if(k&1) ans=1ll*x*ans%p;
    return ans;
}
int main()
{
    scanf("%d%d",&n,&p);fac[0]=1;
    for(int i=1;i<=n*2+1;i++) f[i]=1;
    for(int i=1;i<=n;i++) fac[i]=1ll*i*fac[i-1]%p;
    inv[n]=qpow(fac[n],p-2);
    for(int i=n;i>=1;i--) inv[i-1]=1ll*i*inv[i]%p;
    dfs(1);
    printf("%d\n",f[1]);
}
/*
g++ 1.cpp -o 1
./1
20 23
*/
View Code

 

 

 

  C.集合计数(颓了一半题解) 会做么?不会就好题。是在大小为n的集合中选任意个集合,交集元素为k的方案个数。

  我首先想到了把交集为k个提出来,然后求n-k个选交集为0,即f[n-k][0],可以从2^(2^(n-k))-1个减f[n-k][j]转移过来,递推n^2实现很简单就没打,然后想颓了一眼题解(容斥原理),但是难以理解,想了一天又去找DeepinC大神,发现其实是容斥集合的定义和本题的集合不太一样,容斥的集合是包含一个元素的所有方案组成的集合。。。容斥应该作为一个随想随用的算法,不要心理上耸掉,只要好好搞一个容斥系数就可以了

  

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int mn=1e6+20,mod=1e9+7;
int f[mn],fac[mn],inv[mn],n,k;
int qpow(int x,int k)
{
    int ans=1;
    for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod;
    return ans;
}
long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
int main()
{
    scanf("%d%d",&n,&k);fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
    inv[n]=qpow(fac[n],mod-2);
    for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod;
    long long ans=0,tmp=C(n,k),tp=1;n-=k;
    int op=(n&1)?-1:1;
    for(int i=n;~i;i--)
    {
        ans=(ans+op*C(n,i)*tp%mod)%mod;
        tp=(tp*tp%mod+2*tp%mod)%mod;
        op=-op;
    }
    ans=ans*tmp%mod;
    printf("%lld\n",(ans+mod)%mod);
}
//for sigma i=0->n C(n,i)(2^(2^(n−i))−1)
/*
g++ 1.cpp -o 1
./1
4 1
*/
View Code

 

 

  D.DZY loves math 2(全颓题解) 劳资他娘的颓了题解还不会,只能等我变强之后在回过头再做。。。


  E.虔诚的母猪人墓主人 (暴力水过,正解算是颓了题解10个字) 这是个数据结构题,思维量很小,就想到一个离散化不影响答案,

  然后每一行有一段段的横区间,区间内左右组合数一定,要维护竖着的ΣC上*C下就行了,没sort调了3节课骂了无数的街。。

  

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;
const unsigned int mod=2147483647;
struct point
{
    int x,y;
    bool friend operator <(const point &a,const point &b)
    {
        if(a.x==b.x) return a.y<b.y;
        return a.x<b.x;
    }
}t[100020];
unsigned int x[100020],y[100020],lx[100020],ly[100020],n,m,nx,ny,w;
unsigned int b[100020],s[100020],pre[100020];
unsigned int a[100020],C[100020][15];
vector<int> r[100020];
int rd()
{
    int s=0,w=1;
    char cc=getchar();
    while(cc<'0'||cc>'9') {if(cc=='-') w=-1;cc=getchar();}
    while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar();
    return s*w;
}
void add(int i,int w) {for(;i<=ny;i+=(i&(-i))) a[i]+=w;}
unsigned int ask(int i)
{
    unsigned int ans=0;
    for(;i;i-=(i&(-i))) ans+=a[i];
    return ans;
}
bool cmp(const int &a,const int &b)
{
    return t[a].y<=t[b].y;
}
int main()
{
    //freopen("25.in","r",stdin);
    n=rd(),m=rd();
    w=rd();
    for(int i=1;i<=w;i++)
    {
        x[i]=lx[i]=rd();
        y[i]=ly[i]=rd();
    }
    int k=rd();
    for(int i=0;i<=w;i++) C[i][0]=1;
    for(int i=1;i<=w;i++) 
        for(int j=1;j<=min(i,k);j++)
        C[i][j]=C[i-1][j]+C[i-1][j-1];
    sort(lx+1,lx+w+1);sort(ly+1,ly+w+1);
    nx=unique(lx+1,lx+w+1)-lx-1,ny=unique(ly+1,ly+w+1)-ly-1;
    for(int i=1;i<=w;i++)
    {
        t[i].x=lower_bound(lx+1,lx+nx+1,x[i])-lx;
        t[i].y=lower_bound(ly+1,ly+ny+1,y[i])-ly;
    }
    sort(t+1,t+w+1);
    for(int i=1;i<=w;i++)
    {
        r[t[i].x].push_back(i);
        pre[i]=s[t[i].y]++;
    }
    unsigned int ans=0;
    for(int i=1;i<=nx;i++)
    {
        //cout<<i<<endl;
        int len=r[i].size();
        sort(r[i].begin(),r[i].end(),cmp);
        if(i!=1)
        {
            for(int j=k;j<=len-k;j++)
            {
                int ai=t[r[i][j]].y,bi=t[r[i][j-1]].y;
                if(ai<bi) swap(ai,bi);
                //if(ai==bi+1) continue;
                ans+=(ask(ai-1)-ask(bi))*C[j][k]*C[len-j][k];
                //printf("%d %d %d %d\n",ask(ai-1)-ask(bi),j,C[j][k],ans);
            }
        }
        for(int j=0;j<len;j++)
        {
            int pr=pre[r[i][j]],nt=s[t[r[i][j]].y]-pr-1,ai=t[r[i][j]].y;
            int wi=C[pr+1][k]*C[nt][k]-ask(ai)+ask(ai-1);
            //printf("%d %d %d %d\n",j,ask(ai)-ask(ai-1),pr,nt);
            //cout<<r[i][j]<<" "<<pr+1<<" "<<nt<<endl;
            add(ai,wi);
        }
    }
    ans&=mod;
    printf("%d\n",ans);
}
/*
n%(1<<k)=n&((1<<k)-1)
g++ 1.cpp -o 1
./1
5 6
13
0 2
0 3
1 2
2 0
1 3
3 3
2 1
2 4
2 5
2 6
3 2
5 2
4 3
2
*/
听说这是个省选题

 

 

 

  F. 地精部落    (半题解) 他们都用两维数组做得。。。这是个DP题,要找无连续3个数单调,即锯齿状的序列,问题出在维护信息过多很难P。。然后从状压打起,逐渐减少维护信息,然后再从长度为i,最后一位为k,是峰或谷的状态定义逐渐发现转移错误,后来经tdcp点拨,外加自己思考,发现第二维会转移重复,然后想到其实谁是谁无所谓,只要关心排名是多少就行了,第二维定义为排名为k的方案。

一般的题解第一句话就是设XXX,转移XXX,然后代码。。。其实最关键的是状态的定义,定义对了转移就显然,状态的定义就要抓住题目的特殊条件,如果维护信息过多就先从状压想起,逐渐减少无用信息,一般就能想到正经状态了

  

---恢复内容结束---

  又水过一个专题。。。

  进了组合数学,当年叱吒的DP居然成了暴力。。。一些神奇的操作令我大口吃屎大开脑洞。

  一般有一些常写的代码

  

 1 int qpow(int x,int k)
 2 {
 3     int ans=1;
 4     for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod;
 5     return ans;
 6 }
 7 long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
 8 int main()
 9 {
10     scanf("%d%d",&n,&k);fac[0]=1;
11     for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
12     inv[n]=qpow(fac[n],mod-2);
13     for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod;

 

  然后有一些常用的知识

  :乘法原理加法原理  容斥原理 

  看一下题目长什么样。。

  A.排队 这是个高考数学题 就是用插空法,但是要注意先考虑老师不相邻,在考虑女生不相邻会算少,老师相邻时中间插女生会漏掉。

  B.Per排列 Pi<Pi/2令我大口吃屎。。。居然是下标。。。。然后搞一个二叉树左右子树不影响(题眼)

  C.集合计数 这tm题日了我两天。。

 

---恢复内容结束---

  又水过一个专题。。。

  进了组合数学,当年叱吒的DP居然成了暴力。。。一些神奇的操作令我大口吃屎大开脑洞。

  一些常写的代码

  

 1 int qpow(int x,int k)
 2 {
 3     int ans=1;
 4     for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod;
 5     return ans;
 6 }
 7 long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
 8 int main()
 9 {
10     scanf("%d%d",&n,&k);fac[0]=1;
11     for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
12     inv[n]=qpow(fac[n],mod-2);
13     for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod;

 

  然后有一些常用的知识

  :乘法原理加法原理  容斥原理 

  看一下题目长什么样。。

  A.排队(自主研发) 这是个高考数学题 就是用插空法,但是要注意先考虑n 名男同学,m 名女同学和两名老师要排队,女生和老师都不能相邻,如果先让老师不邻,再考虑女生不相邻会算少,老师相邻时中间插女生会漏掉。

  

#75880 #A. 排队 Accepted 100 674 ms 604 KiB starsing (平安) 2019-06-26 18:50:09
#75800 #A. 排队 Wrong Answer 20 428 ms 608 KiB starsing (平安) 2019-06-26 15:58:51

 

  

#include<iostream>
#include<cstring>
#include<cstdio>
#include<string>
using namespace std;
int a[4000000],b[4000020],c[4000020],n,m;
void ch(int a[],int i)
{
    int x=0,j;
    for(j=1;j<=a[0];j++)
    {
        a[j]=a[j]*i+x;
        x=a[j]/10;
        a[j]%=10;
    }
    a[j]=x;
    while(a[j]>9)
    {
        a[j+1]=a[j]/10;
        a[j]%=10;
        j++;
    }
    while(a[j]==0&&j>1)
        j--;
    a[0]=j;
}
void add(int a[],int b[])
{
    int i=1,x=0;
    while(i<=a[0]||i<=b[0])
    {
        //cout<<a[i]<<" "<<b[i]<<" ";
        c[i]=a[i]+b[i]+x;
        //cout<<c[i]<<endl;
        x=c[i]/10;
        c[i]=c[i]%10;
        i++;
    
    }
    if(x!=0&&i>1)
    {
        c[0]=i;
        c[i]=x;
    }
    else c[0]=i-1;
}
int main()
{
    scanf("%d%d",&n,&m);
    if(m>n+3) return printf("0\n"),0;
    a[0]=a[1]=1;b[0]=b[1]=1;
    ch(a,n);ch(a,n+1);ch(b,(2*n+2)*m);
    for(int i=n-m+4;i<=n+3;i++) ch(a,i);
    for(int i=n-m+4;i<=n+2;i++) ch(b,i);
    add(a,b);
    for(int i=1;i<=n;i++) ch(c,i);
    for(int i=c[0];i;i--) printf("%d",c[i]);
    puts("");
}
//排队 题解
/*
g++ 1.cpp -o 1
./1
1 1
*/
View Code

 

 

 

  B.Perm排列计数(自主研发) Pi<Pi/2的1~n排列令我大口吃屎。。。居然是下标。。。。然后搞一个二叉树,左右子树不影响,递归DP一下(题眼),bobo说学过树的都应该想到。

  

#include<iostream>
#include<cstdio>
using namespace std;
int n,p;
int f[2000020],s[2000020],fac[2000020],inv[2000020];
void dfs(int k)
{
    s[k]=1;
    if((k<<1)<=n) dfs(k<<1);
    if((k<<1|1)<=n) dfs(k<<1|1);
    s[k]+=s[k<<1]+s[k<<1|1];
    //if(s[k]==1) f[k]=1
    f[k]=1ll*fac[s[k]-1]*inv[s[k<<1]]%p*inv[s[k]-s[k<<1]-1]%p;
    if(f[k]==0)f[k]=1;
    if(f[k<<1]) f[k]=1ll*f[k]*f[k<<1]%p;
    if(f[k<<1|1]) f[k]=1ll*f[k]*f[k<<1|1]%p;
    //cout<<k<<" "<<s[k]<<" "<<f[k]<<endl;
}
int qpow(int x,int k)
{
    int ans=1;
    for(;k;k>>=1,x=1ll*x*x%p) if(k&1) ans=1ll*x*ans%p;
    return ans;
}
int main()
{
    scanf("%d%d",&n,&p);fac[0]=1;
    for(int i=1;i<=n*2+1;i++) f[i]=1;
    for(int i=1;i<=n;i++) fac[i]=1ll*i*fac[i-1]%p;
    inv[n]=qpow(fac[n],p-2);
    for(int i=n;i>=1;i--) inv[i-1]=1ll*i*inv[i]%p;
    dfs(1);
    printf("%d\n",f[1]);
}
/*
g++ 1.cpp -o 1
./1
20 23
*/
View Code

 

 

 

  C.集合计数(颓了一半题解) 会做么?不会就好题。是在大小为n的集合中选任意个集合,交集元素为k的方案个数。

  我首先想到了把交集为k个提出来,然后求n-k个选交集为0,即f[n-k][0],可以从2^(2^(n-k))-1个减f[n-k][j]转移过来,递推n^2实现很简单就没打,然后想颓了一眼题解(容斥原理),但是难以理解,想了一天又去找DeepinC大神,发现其实是容斥集合的定义和本题的集合不太一样,容斥的集合是包含一个元素的所有方案组成的集合。。。容斥应该作为一个随想随用的算法,不要心理上耸掉,只要好好搞一个容斥系数就可以了

  

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int mn=1e6+20,mod=1e9+7;
int f[mn],fac[mn],inv[mn],n,k;
int qpow(int x,int k)
{
    int ans=1;
    for(;k;k>>=1,x=1ll*x*x%mod) if(k&1) ans=1ll*ans*x%mod;
    return ans;
}
long long C(int n,int m) {return 1ll*fac[n]*inv[m]%mod*inv[n-m]%mod;}
int main()
{
    scanf("%d%d",&n,&k);fac[0]=1;
    for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%mod;
    inv[n]=qpow(fac[n],mod-2);
    for(int i=n;i;i--) inv[i-1]=1ll*inv[i]*i%mod;
    long long ans=0,tmp=C(n,k),tp=1;n-=k;
    int op=(n&1)?-1:1;
    for(int i=n;~i;i--)
    {
        ans=(ans+op*C(n,i)*tp%mod)%mod;
        tp=(tp*tp%mod+2*tp%mod)%mod;
        op=-op;
    }
    ans=ans*tmp%mod;
    printf("%lld\n",(ans+mod)%mod);
}
//for sigma i=0->n C(n,i)(2^(2^(n−i))−1)
/*
g++ 1.cpp -o 1
./1
4 1
*/
View Code

 

 

  D.DZY loves math 2(全颓题解) 劳资他娘的颓了题解还不会,只能等我变强之后在回过头再做。。。


  E.虔诚的母猪人墓主人 (暴力水过,正解算是颓了题解10个字) 这是个数据结构题,思维量很小,就想到一个离散化不影响答案,

  然后每一行有一段段的横区间,区间内左右组合数一定,要维护竖着的ΣC上*C下就行了,没sort调了3节课骂了无数的街。。

  

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;
const unsigned int mod=2147483647;
struct point
{
    int x,y;
    bool friend operator <(const point &a,const point &b)
    {
        if(a.x==b.x) return a.y<b.y;
        return a.x<b.x;
    }
}t[100020];
unsigned int x[100020],y[100020],lx[100020],ly[100020],n,m,nx,ny,w;
unsigned int b[100020],s[100020],pre[100020];
unsigned int a[100020],C[100020][15];
vector<int> r[100020];
int rd()
{
    int s=0,w=1;
    char cc=getchar();
    while(cc<'0'||cc>'9') {if(cc=='-') w=-1;cc=getchar();}
    while(cc>='0'&&cc<='9') s=(s<<3)+(s<<1)+cc-'0',cc=getchar();
    return s*w;
}
void add(int i,int w) {for(;i<=ny;i+=(i&(-i))) a[i]+=w;}
unsigned int ask(int i)
{
    unsigned int ans=0;
    for(;i;i-=(i&(-i))) ans+=a[i];
    return ans;
}
bool cmp(const int &a,const int &b)
{
    return t[a].y<=t[b].y;
}
int main()
{
    //freopen("25.in","r",stdin);
    n=rd(),m=rd();
    w=rd();
    for(int i=1;i<=w;i++)
    {
        x[i]=lx[i]=rd();
        y[i]=ly[i]=rd();
    }
    int k=rd();
    for(int i=0;i<=w;i++) C[i][0]=1;
    for(int i=1;i<=w;i++) 
        for(int j=1;j<=min(i,k);j++)
        C[i][j]=C[i-1][j]+C[i-1][j-1];
    sort(lx+1,lx+w+1);sort(ly+1,ly+w+1);
    nx=unique(lx+1,lx+w+1)-lx-1,ny=unique(ly+1,ly+w+1)-ly-1;
    for(int i=1;i<=w;i++)
    {
        t[i].x=lower_bound(lx+1,lx+nx+1,x[i])-lx;
        t[i].y=lower_bound(ly+1,ly+ny+1,y[i])-ly;
    }
    sort(t+1,t+w+1);
    for(int i=1;i<=w;i++)
    {
        r[t[i].x].push_back(i);
        pre[i]=s[t[i].y]++;
    }
    unsigned int ans=0;
    for(int i=1;i<=nx;i++)
    {
        //cout<<i<<endl;
        int len=r[i].size();
        sort(r[i].begin(),r[i].end(),cmp);
        if(i!=1)
        {
            for(int j=k;j<=len-k;j++)
            {
                int ai=t[r[i][j]].y,bi=t[r[i][j-1]].y;
                if(ai<bi) swap(ai,bi);
                //if(ai==bi+1) continue;
                ans+=(ask(ai-1)-ask(bi))*C[j][k]*C[len-j][k];
                //printf("%d %d %d %d\n",ask(ai-1)-ask(bi),j,C[j][k],ans);
            }
        }
        for(int j=0;j<len;j++)
        {
            int pr=pre[r[i][j]],nt=s[t[r[i][j]].y]-pr-1,ai=t[r[i][j]].y;
            int wi=C[pr+1][k]*C[nt][k]-ask(ai)+ask(ai-1);
            //printf("%d %d %d %d\n",j,ask(ai)-ask(ai-1),pr,nt);
            //cout<<r[i][j]<<" "<<pr+1<<" "<<nt<<endl;
            add(ai,wi);
        }
    }
    ans&=mod;
    printf("%d\n",ans);
}
/*
n%(1<<k)=n&((1<<k)-1)
g++ 1.cpp -o 1
./1
5 6
13
0 2
0 3
1 2
2 0
1 3
3 3
2 1
2 4
2 5
2 6
3 2
5 2
4 3
2
*/
听说这是个省选题

 

 

 

  F. 地精部落    (半题解) 他们都用两维数组做得。。。这是个DP题,要找无连续3个数单调,即锯齿状的序列数,问题出在维护信息过多很难P。。然后从状压打起,逐渐减少维护信息,然后再从长度为i,最后一位为k,是峰或谷的状态定义逐渐发现转移错误,后来经tdcp点拨,外加自己思考,发现第二维会转移重复,然后想到其实谁是谁无所谓,只要关心排名是多少就行了,第二维定义为排名为k的方案。

一般的题解第一句话就是设XXX,转移XXX,然后代码。。。其实最关键的是状态的定义,定义对了转移就显然,状态的定义就要抓住题目的特殊条件,如果维护信息过多就先从状压想起,逐渐减少无用信息,一般就能想到正经状态了

  

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,p,ans=0;
int f[2][4320][2],sum[2][4320][2];
int main()
{
    scanf("%d%d",&n,&p);
    f[2&1][2][1]=f[2&1][1][0]=1;
    sum[2&1][2][1]=sum[2&1][1][0]=1;sum[2&1][2][0]=1;
    for(int i=3;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            f[i&1][j][0]=(sum[i&1^1][i-1][1]-sum[i&1^1][j-1][1])%p;
            f[i&1][j][1]=(sum[i&1^1][j-1][0])%p;
            sum[i&1][j][0]=(sum[i&1][j-1][0]+f[i&1][j][0])%p;
            sum[i&1][j][1]=(sum[i&1][j-1][1]+f[i&1][j][1])%p;
            //printf("%d %d %d %d\n",i,j,f[i][j][0],f[i][j][1]);
        }
    }
    int ans=0;
    for(int j=1;j<=n;j++)
    {
        ans+=f[n&1][j][0]+f[n&1][j][1];
        ans%=p;
    }
    while(ans<0) ans+=p;
    printf("%d\n",ans);
}
/*
g++ 1.cpp -o 1
./1
10 101520127
*/
View Code

 

  G.看电影(打表+一节物理自习) 这令我无颜说是自主研发的。。。。看这题样例给的这么紧,先打打表。。。然后狗出了规律。。

  目前还没搞懂到底是为什么,只能再等我变强之后再回过头来去看他。。。。

  总之组合数学是个坑,做了几道简单题发现令人绝望。。。。

  方法:打表找规律,不会就DP,难P就暴力,不会就种地。。。。

 

posted @ 2019-07-06 18:59  starsing  阅读(368)  评论(1编辑  收藏  举报