对洛谷“提高试炼场-其他数学问题”的爆破

题外话

由于本蒟蒻的数学太弱啦!所以在联赛前有必要刷一刷洛谷的“提高试炼场-其他数学问题”。
难度评级是根据本蒟蒻的感受来写的,满分是五颗星。

P1357 花园

难度评级: ※※※※
题目分析:
我们用一个m位二进制数表示后m个花圃的状态,1表示为C,0表示为P。
那么令t(i,j)表示由状态i转移到状态j的方案数(i和j都合法,即1的个数不超过k)。所谓转移,是指如果i表示第1~第m个花圃的状态,那么j代表第2~第m+1个花圃的状态。
一开始两个合法转移状态之间t(i,j)=1,合法转移状态为j右移一位,在左边添加一个1(或不添加)为i,这个可以dfs搞出来。
然后我们会发现i转移到j,可以看作i先转移到一个状态k,再由k转移到j,这是一个满足结合律的类似floyed的式子,可以用矩阵快速幂来搞。
由于所有的花圃是一个环,所以第1~m个花圃就是第n+1~n+m个花圃,所以我们求的答案就是一个合法状态转移n次,转移回原状态的方案数之和。
代码:

#include
#include
#include
#include
using namespace std;
#define LL long long
LL n,m,k,lim,mod=1000000007,ans;
struct node{LL t[64][64];}re,x;
int ok[64],bin[6];
void work(int zt,int num) {//处理合法转移
    ok[zt]=1;int kl=zt>>1;
    x.t[kl][zt]=1;
    if(num==k&&!(zt&1)) return;//可转移状态也必须合法
    x.t[kl+bin[m]][zt]=1;
}
void dfs(int x,int num,int zt) {//处理合法状态
    if(x==m+1) {work(zt,num);return;}
    dfs(x+1,num,zt);
    if(num1,num+1,zt|bin[x]);
}
node operator * (node a,node b) {//重载运算符
    int i,j,k;node c;
    for(i=0;i<=lim;++i)
        for(j=0;j<=lim;++j) {
        c.t[i][j]=0;
        for(k=0;k<=lim;++k)
            c.t[i][j]+=a.t[i][k]*b.t[k][j]%mod,c.t[i][j]%=mod;
    }
    return c;
}
void ksm() {//快速幂正文
    int bj=0;
    while(n) {
        if(n&1) {
            if(!bj) re=x,bj=1;
            else re=re*x;
        }
        x=x*x;n>>=1;
    }
}
int main()
{
    bin[1]=1;for(int i=2;i<=5;++i) bin[i]=bin[i-1]<<1;
    scanf("%lld%lld%lld",&n,&m,&k);
    lim=(1<1;dfs(1,0,0);ksm();
    for(int i=0;i<=lim;++i) if(ok[i]) ans+=re.t[i][i],ans%=mod;
    //统计答案
    printf("%lld",ans);
    return 0;
}

P1338 末日的传说

难度评级: ※※※
题目分析:
首先,逆序对越多,字典序越大(如果不明白打个表看看吧,我也没法严谨证明)
考虑”1”的位置。
假如1后面可以产生最多逆序对个数((n-1)*(n-2)/2),如果这个数要大于等于m的话,为了追求字典序最小,1放在第一个是没有问题的。但是如果这个数小于m的话,”1”务必要对逆序对个数做出一点贡献。
可以发现,不管1放在哪里,和1无关的逆序对个数最多都是m-(与1有关的逆序对个数)个。为了让字典序最小,也就是要让第一个数最小,又因为第一个数不是1,所以为了让第一个数字典序最小,去掉1后的序列字典序要最小,与1有关的逆序对个数要最多。
那么1放在最后就好啦!
放好1后,2~n其实也可以看作1~n-1来处理,所以一步步来即可。
代码:

#include
#include
#include
#include
using namespace std;
#define LL long long
int n,m,l,r;
int ans[50005];
int main()
{
    scanf("%d%d",&n,&m);
    l=1,r=n;
    for(int i=1;i<=n;++i) {
        int kl=(LL)(n-i)*(n-i-1)/2;
        if(kl>=m) ans[l++]=i;
        else m-=n-i,ans[r--]=i;
    }
    for(int i=1;i<=n;++i) printf("%d ",ans[i]);
    return 0;
}

P1641 生成字符串

难度评级: ※※※※※(对于以前没做过类似问题的人来说)
题目分析:
由于思维太巧了所以给的评级较高。
卡特兰数的变形。
对洛谷“提高试炼场-其他数学问题”的爆破_第1张图片
如图(从洛谷题解区搬过来的图(⊙﹏⊙)b)
把选择1看作向右上走,选择0看作往左下走,那么就是要从(0,0)走到(n+m,n-m)位置,走法总数为 Cmn+m 种。
而不合法的走法就是触碰到了直线y=-1。由对称性,所有从(0,0)出发碰到过y=-1走到(n+m,n-m)的走法均可以看作从(0,-2)出发走到(n+m,n-m),走法总数为 Cm1n+m 种。
答案就是 Cmn+mCm1n+m
代码:

#include
#include
#include
#include
using namespace std;
#define LL long long
LL mod=20100403,n,m,k1,k2,k3,k4,ans;
LL ksm(LL x,LL y) {
    LL re=1;
    while(y) {
        if(y&1) re=re*x%mod;
        x=x*x%mod,y>>=1;
    }
    return re;
}
int main()
{
    scanf("%lld%lld",&n,&m);
    k1=k2=k3=k4=1;
    for(int i=n+1;i<=n+m;++i) k1=k1*i%mod;
    for(int i=1;i<=m;++i) k2=k2*i%mod;
    ans=k1*ksm(k2,mod-2)%mod;
    for(int i=n+2;i<=n+m;++i) k3=k3*i%mod;
    for(int i=1;i<=m-1;++i) k4=k4*i%mod;
    ans=(ans-k3*ksm(k4,mod-2)%mod+mod)%mod;
    printf("%lld",ans);
    return 0;
}

P2154 虔诚的墓主人

难度评级: ※※※※
题目分析:
这题难度不在数学。
我们令一块公墓上下左右的常青树数量分别为u,d,l,r,那么这块公墓的虔诚度就是 CkuCkdCklCkr
会发现由于常青树很少,所以有很多公墓都是没有虔诚度的。所以比起公墓,还是枚举常青树比较科学。
我们首先离散一下常青树的横纵坐标。然后按照横坐标从小到大枚举每一列(指横坐标相同的),对于某一列相邻的两棵常青树,处理夹在他们中间的公墓贡献(这个等下说)。然后处理完这一列后,每一棵树纵坐标的贡献相应的发生变化,用树状数组维护。
我肯定没讲明白,看代码吧。
代码:

#include
#include
#include
#include
using namespace std;
#define uin unsigned int
uin read() {
    uin q=0;char ch=' ';
    while(ch<'0'||ch>'9') ch=getchar();
    while(ch>='0'&&ch<='9') q=q*10+ch-'0',ch=getchar();
    return q;
}
const int N=100005;
uin n,m,w,tx,ty,k,ans;
struct tree{uin x,y;}a[N];
uin by[N],bx[N],c[N][11],xjs[N],yjs[N],tr[N],tmp[N],nowy[N];
//by,bx:用于离散x和y坐标
//c:组合数
//xjs,yjs:每一个x和y坐标有多少常青树
//tr:树状数组
//tmp:记录每一个y坐标的贡献
//nowy:当前已经处理了的这个y坐标上的常青树有多少棵(即在左边)
void init() {//打表组合数
    for(int i=0;i<=w;++i) {
        c[i][0]=1;
        for(int j=1;j<=i&&j<=k;++j) c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
}
uin lowbit(uin x) {return x&(-x);}
uin find(uin x) {
    uin re=0;
    while(x) re+=tr[x],x-=lowbit(x);
    return re;
}
void add(uin x,uin num) {while(x<=ty) tr[x]+=num,x+=lowbit(x);}
void work() {
    uin cnt=0;
    for(int i=1;iif(a[i+1].x==a[i].x) {
            int ygx=find(a[i+1].y-1)-find(a[i].y);//y坐标总贡献
            ans+=ygx*c[cnt][k]*c[xjs[a[i].x]-cnt][k];
        }
        else cnt=0;
        //修改y坐标贡献
        add(a[i].y,-tmp[a[i].y]);
        tmp[a[i].y]=c[nowy[a[i].y]][k]*c[yjs[a[i].y]-nowy[a[i].y]][k];
        add(a[i].y,tmp[a[i].y]);
    }
}
bool cmp(tree xx,tree yy) {
    if(xx.x==yy.x) return xx.yreturn xx.xint main()
{
    n=read(),m=read(),w=read();
    for(int i=1;i<=w;++i) {
        a[i].x=read(),a[i].y=read();
        bx[i]=a[i].x,by[i]=a[i].y;
    }
    sort(a+1,a+1+w,cmp),sort(bx+1,bx+1+w),sort(by+1,by+1+w);
    tx=1;for(int i=1;i<=w;++i) if(bx[i]!=bx[tx]) bx[++tx]=bx[i];
    ty=1;for(int i=1;i<=w;++i) if(by[i]!=by[ty]) by[++ty]=by[i];
    //离散
    for(int i=1;i<=w;++i) {
        a[i].x=lower_bound(bx+1,bx+1+tx,a[i].x)-bx;
        a[i].y=lower_bound(by+1,by+1+ty,a[i].y)-by;
        ++xjs[a[i].x],++yjs[a[i].y];
    }
    k=read(),init(),work();
    printf("%d\n",ans&2147483647);
    return 0;
}

P2261 余数求和

难度评级: ※※※
题目分析:

i=0nkmodi=i=0nkkii

对于 ki=t 的第一个i来说,令 j=kki 那么k整除i~j的余数都是一样的。这样我们可以根据等差数列和数学公式变形就可以在根号复杂度内求解了!
代码:
把n和k写反了QAQ

#include
#include
#include
#include
using namespace std;
#define LL long long
LL n,k,ans;
int main()
{
    scanf("%lld%lld",&k,&n);
    LL i,j;ans=n*k;
    if(k>n) k=n;
    for(i=1;i<=k;i=j+1) {
        j=n/(n/i);if(j>k) j=k;
        ans-=(j-i+1)*(i+j)/2*(n/i);
    }
    printf("%lld",ans);
    return 0;
}

P2327 扫雷

难度评级:※
题目分析:
用f(x,zt,las)表示处理到第x个块,第x个块是否有雷(zt),上一个块是否有雷(las),这个状态已经获得的前提下,还可以产生多少种方案。
然后记忆化搜索即可。
代码:

#include
#include
#include
#include
using namespace std;
#define LL long long
const int N=10005;
LL f[N][2][2];int n,a[N];
LL dfs(int x,int zt,int las) {
    if(x==n+1) {
        if(zt) return 0;
        else return 1;
    }
    LL &re=f[x][zt][las];
    if(re!=-1) return re;
    if(!a[x]) {
        if(zt||las) return 0;
        re=dfs(x+1,0,0);
    }
    else if(a[x]==1) {
        if(zt&&las) return 0;
        if(las) re=dfs(x+1,0,0);
        else if(zt) re=dfs(x+1,0,1);
        else re=dfs(x+1,1,0);
    }
    else if(a[x]==2) {
        if(!zt&&!las) return 0;
        if(zt&&las) re=dfs(x+1,0,1);
        else if(zt) re=dfs(x+1,1,1);
        else if(las) re=dfs(x+1,1,0);
    }
    else {
        if(!zt||!las) return 0;
        re=dfs(x+1,1,1);
    }
    return re;
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%d",&a[i]);
    memset(f,-1,sizeof(f));
    printf("%lld",dfs(1,0,0)+dfs(1,1,0));
    return 0;
}

你可能感兴趣的:(数学)