突然想起来写一写前一段时间做学弟的考试题被虐的的一些总结吧.
之前老张带着队长等三个人去了WC,高二就剩下了我和凤姐在机房.
老张要我那几天给高一的学弟们发他给的题,顺带讲一下.
坑爹的是竟然没有题解?!呃……而且我不会做啊(暴汗)……
于是接下来几天就陷入了死磕std的尴(huan)尬(le)局面……
(时至今日都还没改完这几个题,真是辣鸡啊……)
求 ∑ni=1ik mod 1234567891 ( 1≤n≤109 , 1≤k≤100 )
这玩意儿叫做自然数幂和问题,其中解法很多,可以参见杜教WC2013的课件.
下面就讲一讲一种,利用二项式定理求解的方法.
二项式定理如下
则有
从前往后进行递推即可,时间复杂度 O(k2)
#include
#include
#include
#include
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;j1][j]*S[j]%MOD+MOD)%MOD;
S[i]=S[i]*inv(i+1)%MOD;//注意要÷(i+1)
}
printf("%lld\n",S[k]);
return 0;
}
给一个长度为 n(1≤n≤233333) 的自然数序列 {a}(0≤ai≤2333333) ,求出有多少个长度 s>1 的子序列,使得
这个题得感谢哈狗学长.
先分析一下 2333 ,是一个质数.
合法序列需满足
1. s>1 ;
2. ∀1<i≤s , aki≤aki−1 ;
3. ∀1<i≤s , Cakiaki−1 不为 2333 的倍数.
重点分析第三个要求,因为组合数结果一定为整数
由组合数公式可得
所以需满足
1. n≥m
2. n/P≥m/P
3. n%P≥m%P
定义 dp[i] 表示到以第i个位置结尾的子序列的方案数.
转移方程为 dp[i]=∑i−1j=1dp[j]|aj≥ai and aj/P≥ai/P and aj%P≥ai%P
可以考虑使用二维树状数组,由于是要在前面找比之小的,可以考虑将不等式左右同时取负.
#include
#include
#include
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;
}
对于一个 1 ~ n 的排列 {p} ,对于某个 1≤i≤n ,有 pi−1< pi>pi+1 ,则称 i 为一个峰(规定 p0=pn+1=0 ),求出有多少个 1 ~ n 的排列存在恰有 k 个峰.( 1≤n≤109 , k≤10 )
答案 mod m , m 为输入数.( m≤1000 ,多组数据 1≤T≤10 )
渐进地考虑峰个数的变化,分析插入一个值对峰个数的贡献.
对于一个 1 ~ n 的任意排列,若有k个峰.现在尝试将数 n+1 插入,使其变成一个 1 ~ n+1 的某一排列.
若将 n+1 插在峰与谷之间(这样的位置有 2k 个),则原来的一个峰会退化成谷,而 n+1 必然会成为峰,即峰个数不变;
若将 n+1 插在谷与谷之间(这样的位置有 n+1−2k 个),则没有峰会退化,则峰的个数增加 1 .
定义状态 dp[i][j] 表示 1 ~ n 的所有排列中,恰有 j 个峰的排列有多少.
由前面可得,转移方程如下
dp[i][j]=dp[i−1][j−1]∗(i−2∗j+2)+dp[i−1][j]∗2j
但是n太大了,一步一步递推显然是不行的.
那能不能用矩阵快速幂来加速呢?
轩神告诉我,若在转移方程中,转移而来的除了 dp 值之外,其他的应当为常数.
所以这个方程不能用单一的转移方程来加速.
但是由于 m 较小,在 mod m 意义下,实则将参数控制在了一定的范围.
所以可以将这些转移矩阵都算出来, 1 ~ m 转移矩阵依次相乘得到的矩阵作为一个转移矩阵,然后用矩阵快速幂优化.
#include
#include
#include
#include
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;
}
求出有多少个满足 0≤x<1 的有理数 x ,使得
跟着汪神一起想了想这个题,最终发现是个套路题.
等比数列前 n 项和公式
即对于 q=1 ~ n ,满足 p≤b−abq 且 gcd(p,q)=1 的数的个数.
然后……就是套路了.
因为 1≤a≤10 , 103≤b≤104 ,
所以 abmax=1100 ,则 b−abmin=99100 .
即无论如何 ≤99100q 的 p 是一定要取的,所以可以筛下 >99100q 且与 q 互素的数.
那么总共不会超过 500000 个数,但是询问的组数较多,可以选择离线处理,按照 ab 排序.
#include
#include
#include
#include
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//离线处理,按照a/b由大到小排序
for(int i=0;ifor(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;iprintf("%d\n",ans[i]);
return 0;
}
有n个人,编号依次为 0 ~ n−1 .
每次选择是k倍数的人踢出去,然后剩下的人重新从 0 开始编号,直至所有人都被踢出去.(0也是k的倍数)
现在需要求出第 i(1≤i≤n) 次踢出去人的初始编号 ai .
最终答案输出
晚上改题的时候,突然知道这个题怎么做了,感动.
若定义 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
#include
#include
#include
#include
#include
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//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;jpow*a[i][j]%MOD)%MOD;
pow=(pow*p)%MOD;
}
printf("%lld\n",ans);
}
return 0;
}