总结-辣鸡学长连学弟考试题都不会做

总结-辣鸡学长连学弟考试题都不会做

突然想起来写一写前一段时间做学弟的考试题被虐的的一些总结吧.
之前老张带着队长等三个人去了WC,高二就剩下了我和凤姐在机房.
老张要我那几天给高一的学弟们发他给的题,顺带讲一下.
坑爹的是竟然没有题解?!呃……而且我不会做啊(暴汗)……
于是接下来几天就陷入了死磕std的尴(huan)尬(le)局面……

(时至今日都还没改完这几个题,真是辣鸡啊……)

02.07 T1

题意概述

ni=1ik mod 1234567891 ( 1n109 , 1k100 )

题目分析

这玩意儿叫做自然数幂和问题,其中解法很多,可以参见杜教WC2013的课件.
下面就讲一讲一种,利用二项式定理求解的方法.

二项式定理如下

(a+b)k=i=0kCikaibki

则有

(j+1)k+1=i=0k+1Cik+1ji

(j+1)k+1jk+1=i=0kCik+1ji

将j从1到n求和
j=1n(j+1)k+1jk+1=j=1ni=0kCik+1ji

利用差分,则有
(n+1)k+11k+1=j=1ni=0kCik+1ji

对右边的式子求和顺序进行交换
(n+1)k+11k+1=i=0kCik+1j=1nji


Sd(n)=i=1nid

则有
(n+1)k+11k+1=i=0kCik+1Si(n)

(n+1)k+11k+1=(k+1)Sk(n)+i=0k1Cik+1Si(n)


(k+1)Sk(n)=(n+1)k+11i=0k1Cik+1Si(n)

从前往后进行递推即可,时间复杂度 O(k2)

代码实现
#include<cstdio>
#include<cassert>
#include<iostream>
#include<algorithm>

using namespace std;

typedef long long ll;
const int maxn=100+5;
const ll MOD=1234567891;

ll qpow(ll x,ll y) {
    ll ret=1;
    while(y>0) {
        if(y&1) ret=ret*x%MOD;
        x=x*x%MOD;y>>=1;
    }
    return ret;
}

#define inv(x) qpow(x,MOD-2)

ll C[maxn][maxn],S[maxn];

int main() {
    freopen("sum.in","r",stdin);
    freopen("sum.out","w",stdout);
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i=0;i<=k+1;i++) {//O(k^2)求组合数
        C[i][0]=1;
        for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
    }
    S[0]=n;
    for(int i=1;i<=k;i++) {//1~n递推
        S[i]=(qpow(n+1,i+1)-1)%MOD;
        for(int j=0;j<i;j++)
            S[i]=(S[i]-C[i+1][j]*S[j]%MOD+MOD)%MOD;
        S[i]=S[i]*inv(i+1)%MOD;//注意要÷(i+1)
    }
    printf("%lld\n",S[k]);
    return 0;
}

02.08 T1

题意概述

给一个长度为 n(1n233333) 的自然数序列 {a}(0ai2333333) ,求出有多少个长度 s>1 的子序列,使得

Cak2ak1Cak3ak2...Caksaks1mod 2333>0

所得答案 mod 998244353

题目分析

这个题得感谢哈狗学长.

先分析一下 2333 ,是一个质数.

合法序列需满足
1. s>1 ;
2. 1<is , akiaki1 ;
3. 1<is , Cakiaki1 不为 2333 的倍数.

重点分析第三个要求,因为组合数结果一定为整数
由组合数公式可得

Cmn=n!m!(nm)!

要使结果不为2333的倍数,一定有分子分母2333因子数相等.
又因为 1in,ai<23332 ,所以可以表示为(以下用 P 表示2333)
nP=mP+nmP

(nn%P)/P=(mm%P)/P+((nm)(nm)%P)/P

nn%P=mm%P+nm(nm)%P

n%P=m%P+(nm)%P

(nm)%P0

n%Pm%P

所以需满足
1. nm
2. n/Pm/P
3. n%Pm%P

定义 dp[i] 表示到以第i个位置结尾的子序列的方案数.
转移方程为 dp[i]=i1j=1dp[j]|ajai and aj/Pai/P and aj%Pai%P
可以考虑使用二维树状数组,由于是要在前面找比之小的,可以考虑将不等式左右同时取负.

代码实现
#include<cstdio>
#include<iostream>
#include<algorithm>

using namespace std;

const int P=2333;
const int MOD=998244353;
const int maxn=233333+10;

int bit[P+5][P+5];
int m[maxn],r[maxn];//m[i]=P-(a[i]/P),r[i]=P-(a[i]%P)

#define lowbit(x) (x&-x)

void add(int x,int y,int v) {
    for(int i=x;i<=P;i+=lowbit(i))
        for(int j=y;j<=P;j+=lowbit(j))
            (bit[i][j]+=v)%=MOD;
}

int sum(int x,int y) {
    int ret=0;
    for(int i=x;i>0;i-=lowbit(i))
        for(int j=y;j>0;j-=lowbit(j))
            (ret+=bit[i][j])%=MOD;
    return ret;
}

int dp[maxn],n,ans;

int main() {
    freopen("product.in","r",stdin);
    freopen("product.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        scanf("%d",&r[i]);
        m[i]=P-r[i]/P;//取反操作,便于找比之小的值
        r[i]=P-r[i]%P;
    }
    add(m[0]=1,r[0]=1,dp[0]=1);
    for(int i=1;i<=n;i++) {
        dp[i]=sum(m[i],r[i]);
        add(m[i],r[i],dp[i]);
        (ans+=(dp[i]-1))%=MOD;
    }
    printf("%d\n",ans);
    return 0;
}

02.08 T3

题意概述

对于一个 1 ~n的排列 {p} ,对于某个 1in ,有 pi1< pi>pi+1 ,则称 i 为一个峰(规定p0=pn+1=0),求出有多少个 1 ~n的排列存在恰有 k 个峰.(1n109, k10 )
答案 mod m , m 为输入数.(m1000,多组数据 1T10 )

题目分析

渐进地考虑峰个数的变化,分析插入一个值对峰个数的贡献.
对于一个 1 ~n的任意排列,若有k个峰.现在尝试将数 n+1 插入,使其变成一个 1 ~n+1的某一排列.
若将 n+1 插在峰与谷之间(这样的位置有 2k 个),则原来的一个峰会退化成谷,而 n+1 必然会成为峰,即峰个数不变;
若将 n+1 插在谷与谷之间(这样的位置有 n+12k 个),则没有峰会退化,则峰的个数增加 1 .

定义状态dp[i][j]表示 1 ~n的所有排列中,恰有 j 个峰的排列有多少.
由前面可得,转移方程如下
dp[i][j]=dp[i1][j1](i2j+2)+dp[i1][j]2j

但是n太大了,一步一步递推显然是不行的.
那能不能用矩阵快速幂来加速呢?
轩神告诉我,若在转移方程中,转移而来的除了 dp 值之外,其他的应当为常数.
所以这个方程不能用单一的转移方程来加速.
但是由于 m 较小,在mod m意义下,实则将参数控制在了一定的范围.
所以可以将这些转移矩阵都算出来, 1 ~m转移矩阵依次相乘得到的矩阵作为一个转移矩阵,然后用矩阵快速幂优化.

代码实现
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int maxk=10+5;
const int maxm=1000+10;

int MOD;

struct Matrix {
    int M[maxk][maxk],n;
    Matrix(){memset(M,0,sizeof(M));n=10;}
    void init() {
        memset(M,0,sizeof(M));
        for(int i=0;i<=n;i++) M[i][i]=1;
    }
    Matrix operator * (const Matrix& rhs) const {
        Matrix ret;
        for(int i=0;i<=n;i++)
            for(int j=0;j<=n;j++)
                for(int k=0;k<=n;k++)
                    (ret.M[i][j]+=M[i][k]*rhs.M[k][j]%MOD)%=MOD;
        return ret;
    }
    Matrix operator *= (const Matrix& rhs) {
        return *this=*this*rhs;
    }
    Matrix operator ^ (int y) const {
        Matrix ret,x=*this;
        ret.init();
        while(y>0) {
            if(y&1) ret*=x;
            x*=x;y>>=1;
        }
        return ret;
    }
}A[maxm],M;

int main() {
    freopen("peaks.in","r",stdin);
    freopen("peaks.out","w",stdout);
    int T,n,k;
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d",&n,&k,&MOD);
        for(int i=1;i<=MOD;i++) {
            A[i]=Matrix();
            for(int j=0;j<=k;j++) {
                A[i].M[j][j]=2*j%MOD;
                if(j) A[i].M[j-1][j]=((i-2*j+2)%MOD+MOD)%MOD;
            }
        }
        M.init();
        for(int i=1;i<=MOD;i++) M*=A[i];//算出一次循环的矩阵
        M=M^(n/MOD);//矩阵快速幂
        for(int i=1;i<=n%MOD;i++) M*=A[i];//余下的再乘起来
        printf("%d\n",M.M[0][k]);
    }
    return 0;
}

02.09 T1

题意概述

求出有多少个满足 0x<1 的有理数 x ,使得

a+ax+ax2+ax3+...b

且对于任意满足要求的 x=pq ,都有 gcd(p,q)=1 qn .
(多组数据, 1T104 , 1n104 , 1a10 , 103b104 )

题目分析

跟着汪神一起想了想这个题,最终发现是个套路题.

等比数列前 n 项和公式

Sn=a1(1qn)1q

所以有

a(1x)1xb

0x<1,x0


a(1x)b

a(1pq)b

aqb(qp)

pbabq

即对于 q=1 ~ n ,满足pbabq gcd(p,q)=1 的数的个数.

然后……就是套路了.
因为 1a10 , 103b104 ,
所以 abmax=1100 ,则 babmin=99100 .
即无论如何 99100q p 是一定要取的,所以可以筛下>99100q且与 q 互素的数.
那么总共不会超过500000个数,但是询问的组数较多,可以选择离线处理,按照 ab 排序.

代码实现
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

const int maxn=10000+10;

int gcd(int a,int b) {
    return b?gcd(b,a%b):a;
}

vector<int>v[maxn];
int phi[maxn],cnt[maxn];

void init(int n) {
    phi[1]=1;
    for(int i=2;i<=n;i++) if(!phi[i])
        for(int j=i;j<=n;j+=i) {
            if(!phi[j]) phi[j]=j;
            phi[j]=phi[j]/i*(i-1);
        }
    for(int i=2;i<=n;i++)
        for(int j=i-1;j>0;j--)
            if(100*j<=99*i) break;//一定会有,不用筛
            else if(gcd(i,j)==1) v[i].push_back(j),++cnt[i];
}

struct Ask {
    int n,a,b,id;
    bool operator < (const Ask& rhs) const {
        return a*rhs.b>b*rhs.a;
    }
    void input(int x) {
        scanf("%d%d%d",&n,&a,&b);id=x;
    }
}ask[maxn];

int ans[maxn];

int main() {
    freopen("series.in","r",stdin);
    freopen("series.out","w",stdout);
    init(10000);
    int T,n,a,b,id;
    scanf("%d",&T);
    for(int i=0;i<T;i++) ask[i].input(i);
    sort(ask,ask+T);//离线处理,按照a/b由大到小排序
    for(int i=0;i<T;i++) {
        n=ask[i].n;a=ask[i].a;b=ask[i].b;id=ask[i].id;
        for(int i=1;i<=n;i++) ans[id]+=phi[i];//先加上全部的,之后再见
        for(int i=1;i<=n;i++)
            while(cnt[i]&&v[i][cnt[i]-1]*b<=i*(b-a)) --cnt[i];离线处理,每次只会增多
        for(int i=1;i<=n;i++) ans[id]-=cnt[i];
    }
    for(int i=0;i<T;i++) printf("%d\n",ans[i]);
    return 0;
}

02.09 T2

题意概述

有n个人,编号依次为 0 ~n1.
每次选择是k倍数的人踢出去,然后剩下的人重新从 0 开始编号,直至所有人都被踢出去.(0也是k的倍数)
现在需要求出第i(1in)次踢出去人的初始编号 ai .
最终答案输出

i=1naipi1 mod 109+7

p 为大于等于n的最小质数.
(多组数据, 1T10 , 1n106 , 1k109 )

题目分析

晚上改题的时候,突然知道这个题怎么做了,感动.

若定义 dp[i] 表示i号位置是在第几轮被踢出去的,那么对于一个位置 p
p%k=0,则 dp[p]=1 .
p%k!=0 ,则当前 p 位置会在重组后到一个新的位置,且恰好比这个新位置的dp值大 1 ,即dp[p]=dp[p/k+1]+1.

如此,即可在 O(n) 的时间里得到每个位置出去是在第几轮.
然后按照轮数,从小到大,同一轮也从小到大遍历(用链表或者vector),依次编号即可得到 {a} .
按照题中要求,求出答案即可.

代码实现
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>

using namespace std;

typedef long long ll;
const int MOD=1e9+7;
const int maxn=1000000+10;

int is_prime(int n) {
    int m=sqrt(n);
    for(int i=2;i<=m;i++) if(n%i==0) return 0;
    return 1;
}

int d[maxn];
vector<int>a[maxn];

int main() {
    freopen("joseph.in","r",stdin);
    freopen("joseph.out","w",stdout);
    int T,n,k,p;
    scanf("%d",&T);
    while(T--) {
        scanf("%d%d",&n,&k);p=n;
        while(!is_prime(p)) ++p;
        for(int i=1;i<=n;i++) a[i].clear();
        for(int i=0;i<n;i++) {//O(n)的dp递推
            d[i]=(i%k?d[i-(i/k+1)]+1:1);
            a[d[i]].push_back(i);
        }
        ll ans=0,pow=1;
        for(int i=1;i<=n;i++)
            for(int j=0;j<a[i].size();j++) {
                ans=(ans+pow*a[i][j]%MOD)%MOD;
                pow=(pow*p)%MOD;
            }
        printf("%lld\n",ans);
    }
    return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值