总结-辣鸡学长连学弟考试题都不会做
突然想起来写一写前一段时间做学弟的考试题被虐的的一些总结吧.
之前老张带着队长等三个人去了WC,高二就剩下了我和凤姐在机房.
老张要我那几天给高一的学弟们发他给的题,顺带讲一下.
坑爹的是竟然没有题解?!呃……而且我不会做啊(暴汗)……
于是接下来几天就陷入了死磕std的尴(huan)尬(le)局面……
(时至今日都还没改完这几个题,真是辣鸡啊……)
02.07 T1
题意概述
求 ∑ni=1ik mod 1234567891 ( 1≤n≤109 , 1≤k≤100 )
题目分析
这玩意儿叫做自然数幂和问题,其中解法很多,可以参见杜教WC2013的课件.
下面就讲一讲一种,利用二项式定理求解的方法.
二项式定理如下
则有
将j从1到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(1≤n≤233333)
的自然数序列
{a}(0≤ai≤2333333)
,求出有多少个长度
s>1
的子序列,使得
所得答案 mod 998244353
题目分析
这个题得感谢哈狗学长.
先分析一下 2333 ,是一个质数.
合法序列需满足
1.
s>1
;
2.
∀1<i≤s
,
aki≤aki−1
;
3.
∀1<i≤s
,
Cakiaki−1
不为
2333
的倍数.
重点分析第三个要求,因为组合数结果一定为整数
由组合数公式可得
要使结果不为2333的倍数,一定有分子分母2333因子数相等.
又因为 ∀1≤i≤n,ai<23332 ,所以可以表示为(以下用 P 表示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<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
~
答案
mod m
,
m
为输入数.(
题目分析
渐进地考虑峰个数的变化,分析插入一个值对峰个数的贡献.
对于一个
1
~
若将
n+1
插在峰与谷之间(这样的位置有
2k
个),则原来的一个峰会退化成谷,而
n+1
必然会成为峰,即峰个数不变;
若将
n+1
插在谷与谷之间(这样的位置有
n+1−2k
个),则没有峰会退化,则峰的个数增加
1
.
定义状态
由前面可得,转移方程如下
但是n太大了,一步一步递推显然是不行的.
那能不能用矩阵快速幂来加速呢?
轩神告诉我,若在转移方程中,转移而来的除了
dp
值之外,其他的应当为常数.
所以这个方程不能用单一的转移方程来加速.
但是由于
m
较小,在
所以可以将这些转移矩阵都算出来,
1
~
代码实现
#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
题意概述
求出有多少个满足
0≤x<1
的有理数
x
,使得
且对于任意满足要求的 x=pq ,都有 gcd(p,q)=1 且 q≤n .
(多组数据, 1≤T≤104 , 1≤n≤104 , 1≤a≤10 , 103≤b≤104 )
题目分析
跟着汪神一起想了想这个题,最终发现是个套路题.
等比数列前
n
项和公式
所以有
即
即对于
q=1
~
n
,满足
然后……就是套路了.
因为
1≤a≤10
,
103≤b≤104
,
所以
abmax=1100
,则
b−abmin=99100
.
即无论如何
≤99100q
的
p
是一定要取的,所以可以筛下
那么总共不会超过
代码实现
#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
~
每次选择是k倍数的人踢出去,然后剩下的人重新从
0
开始编号,直至所有人都被踢出去.(0也是k的倍数)
现在需要求出第
最终答案输出
p 为大于等于
(多组数据, 1≤T≤10 , 1≤n≤106 , 1≤k≤109 )
题目分析
晚上改题的时候,突然知道这个题怎么做了,感动.
若定义
dp[i]
表示i号位置是在第几轮被踢出去的,那么对于一个位置
p
若
若
p%k!=0
,则当前
p
位置会在重组后到一个新的位置,且恰好比这个新位置的
如此,即可在
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;
}