【题解】编程社清明节假期做题记录
编程社清明节假期做题记录&题解
题目难度顺序个人评价:
T4(KMP,字符串Hash均可做)<T1(线段树,分块均可做)<T5(线段树板子题)<T3(KMP-Next
数组应用或字符串Hash)<T6(熟练泼粪(雾))<T2(数论-二项式定理,矩阵快速幂)
文章同步发表于:
若 \(\LaTeX\) 没有在 5s 内加载出来请尝试反复刷新缓存。
T1 敌兵布阵
Description
题意简述
有 \(N(N\leqslant5\times 10^4)\) 个营地,第 \(i\) 个营地初始时有 \(a_i\) 个人 \((1\leqslant a_i\leqslant 50)\),接下来输入若干条命令:
Add i j
表示第 \(i\) 个营地增加 \(j\) 个人。 \((j\leqslant30)\)Sub i j
表示第 \(i\) 个营地减少 \(j\) 个人。 \((j\leqslant30)\)Query i j
表示询问营地 \(i\sim j\) 的总人数。End
表示当前数据命令结束。
Solution
简单来说就是单点修改,区间查询。
法一 线段树
Poster Here
这不线段树板子题吗,果然GM只是不想让我们闲着而已。
既然如此,尝试不看以前的代码,手打一遍吧。
法二 分块
Poster There will be a good blog soon...
这不块状链表板子题吗,果然GM只是不想让我们闲着而已。
既然如此,尝试不看以前的代码,手打一遍吧。
人类的本质。。。
Code
线段树
AC on 04/02
请仔细思考后展开。
#include<cstdio>
#include<cstring>
#define lt p<<1
#define rt lt|1
#define mid (a[p].l+a[p].r>>1)
const int maxn=5e4+5;
int T,n,x,y;
int _a[maxn];
char type[15];
namespace Segment_Tree{
struct Structure_of_Segment_Tree{int l,r,sum;}a[maxn<<2];
inline void PushUp(int p){
a[p].sum=a[lt].sum+a[rt].sum;
return;
}
void Build(int p,int l,int r){
a[p].l=l;a[p].r=r;
a[p].sum=0;
if(l==r){
a[p].sum=_a[l];
return;
}
Build(lt,l,mid);
Build(rt,mid+1,r);
PushUp(p);
return;
}
void Update(int p,int x,int v){
if(a[p].l==a[p].r){
a[p].sum+=v;
return;
}
if(x<=mid)Update(lt,x,v);
else Update(rt,x,v);
PushUp(p);
return;
}
int Query(int p,int l,int r){
if(l<=a[p].l&&a[p].r<=r)
return a[p].sum;
int val=0;
if(l<=mid)
val+=Query(lt,l,r);
if(r>mid)
val+=Query(rt,l,r);
return val;
}
}using namespace Segment_Tree;
int main(){
scanf("%d",&T);
for(int t=1;t<=T;++t){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&_a[i]);
Build(1,1,n);
printf("Case %d:\n",t);
scanf("%s",type+1);
while(strcmp(type+1,"End")){
scanf("%d%d",&x,&y);
switch(type[1]){
case 'A':{
Update(1,x,y);
break;
}
case 'S':{
Update(1,x,-y);
break;
}
case 'Q':{
printf("%d\n",Query(1,x,y));
break;
}
default:{
puts("114514");
break;
}
}
scanf("%s",type+1);
}
}
return 0;
}
分块
AC on 04/03
请仔细思考后展开。
#include<cmath>
#include<cstdio>
#include<cstring>
const int maxk=233;
const int maxn=5e4+5;
int T,n,x,y;
char type[15];
namespace Block{
int k,siz;
int sum[maxk];
int l[maxk],r[maxk];
int a[maxn],pos[maxn];
inline void init(void){
siz=sqrt(n);
k=(n+siz-1)/siz;
for(int i=1;i<=k;++i){
l[i]=r[i-1]+1;
r[i]=l[i]+siz-1;
sum[i]=0;
}
r[k]=n;
for(int i=1;i<=n;++i){
pos[i]=(i+siz-1)/siz;
sum[pos[i]]+=a[i];
}
return;
}
inline void Update(int x,int v){
a[x]+=v;
sum[pos[x]]+=v;
return;
}
inline int Query(int ql,int qr){
int res=0;
if(pos[ql]==pos[qr]){
for(int i=ql;i<=qr;++i)
res+=a[i];
return res;
}
for(int i=ql;i<=r[pos[ql]];++i)
res+=a[i];
for(int i=pos[ql]+1;i<pos[qr];++i)
res+=sum[i];
for(int i=l[pos[qr]];i<=qr;++i)
res+=a[i];
return res;
}
}using namespace Block;
int main(){
scanf("%d",&T);
for(int t=1;t<=T;++t){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
init();
printf("Case %d:\n",t);
scanf("%s",type+1);
while(strcmp(type+1,"End")){
scanf("%d%d",&x,&y);
switch(type[1]){
case 'A':{
Update(x,y);
break;
}
case 'S':{
Update(x,-y);
break;
}
case 'Q':{
printf("%d\n",Query(x,y));
break;
}
default:{
puts("114514");
break;
}
}
scanf("%s",type+1);
}
}
return 0;
}
T2 Yet Another Number Sequence
本篇题解同步发表于 洛谷 。(如果能点个赞那就最好不过了(光速逃
Description
题意简述
设 \(F_i\) 表示Fibonacci数列的第 \(i\) 项,即 \(F_1=1,F_2=2,F_i=F_{i-1}+F_{i-2}\)。
设 \(A_i=F_i\times i^k(k\leqslant 40)\),你的任务是求出其前 \(n\) 项和 \((n\leqslant 10^{18})\bmod 10^9+7\) 的结果。
Solution
注意到Fibonacci,\(n\leqslant 10^{18}\) 等关键词,明摆着就是一道矩阵加速嘛。
但是,,,\((i-1)^k\) 怎么转移到 \(i^k\) 呢?
BDFS了一大堆后,发现了方法:二项式定理。
\(k\) 那么小,那你干脆把 \((i-1)^1,(i-1)^2,(i-1)^3,\cdots,(i-1)^k\) 丢到初始矩阵里去不就行了?
顺便还得把 \(i^1,i^2,i^3,\cdots,i^k\) 丢进去,然后 \((i-1)^x\) 就可以直接转换成 \(i^x\)。
那么 \(i^x\) 也得转换为 \((i+1)^x\),所以关键在于 \(i^x\) 怎么转换为 \((i+1)^x\)。
-
那这不是又回到原问题上了吗!
-
咱不要急,先慢慢看
经过作者一阵乱搞,发现矩阵乘法是处理不了 \(A_i\) 的。
所以只能把矩阵里的 \(i^1,i^2,\cdots,i^k\) 都乘上一个 \(F_i\) 来充数。
那么我们看一看 \(F_{i+1}\times(i+1)^k\) 是怎么得到的:
是时候亮出二项式定理了!
众所周知,二项式定理是一个酱紫的东西:
代入到 \([(i-1)+2]^k\) 中来,就是:
而我们刚好有 \((i-1)^j\)。
\(C_x^j\times2^{x-j}\) 就是 \((i-1)^j\) 在加速矩阵里对应的系数。
又代入到 \([(i)+1]^x\) 中,得:
挺巧的,我们也有 \(i^j\)。
\(C_x^j\) 就是 \(i^j\) 在加速矩阵中对应的系数。
设 \(S_x=\sum\limits_{i=1}^xA_i\)。
那么初始矩阵就长这样:
转移一步后长这样:
转移 \(n-1\) 步后的最终矩阵长这样:
加速矩阵长这样:
(这个\(\LaTeX\) 是给人打的吗。。。)
好了,除了初始化麻烦点,代码还是比较简单的。
Code
时间复杂度 \(\mathcal O(\)能过\()\)
感谢@w23c3c3大巨佬让我逃离了手推 \(k=40\) 大数据然后真·清明节快乐的厄运。
请仔细思考后展开。
#include<cstdio>
#include<cstring>
#define int long long
const int mod=1e9+7;
const int maxn=105;
struct Matrix{
int n,m;
int c[maxn][maxn];
Matrix(int N=0,int M=0){
n=N,m=M;
memset(c,0,sizeof(c));
}
Matrix operator*(const Matrix a)const{
int l=n,r=a.m;
Matrix x=Matrix(l,r);
for(int i=1;i<=l;++i){
for(int j=1;j<=r;++j){
for(int k=1;k<=m;++k){
x.c[i][j]+=c[i][k]*a.c[k][j];
x.c[i][j]%=mod;
}
}
}
return x;
}
}A,B;
int n,k,x=4;
int f[45][45];
Matrix pow(Matrix&x,int b){
Matrix ans=Matrix(x.n,x.m);
for(int i=1;i<=x.m;++i)
ans.c[i][i]=1;
while(b){
if(b&1)
ans=x*ans;
x=x*x;
b>>=1;
}
return ans;
}
inline void init(void){
f[0][0]=1;
for(int i=1;i<=k;++i){
f[i][0]=1;
for(int j=1;j<=i;++j)
f[i][j]=(f[i-1][j-1]+f[i-1][j])%mod;
}
return;
}
signed main(){
scanf("%lld%lld",&n,&k);
init();
A=Matrix(1,3+(k<<1));
A.c[1][1]=1;A.c[1][2]=2;A.c[1][3]=1;
for(int i=1;i<=k;++i){
A.c[1][3+i]=1;
A.c[1][k+3+i]=x%mod;
x<<=1;
}
B=Matrix(3+(k<<1),3+(k<<1));
B.c[2][1]=1;
B.c[1][2]=B.c[2][2]=1;
B.c[3][3]=B.c[3+(k<<1)][3]=1;
for(int i=1;i<=k;++i){
B.c[1][i+k+3]=(1ll<<i)%mod;
B.c[3+k+i][3+i]=1;
B.c[2][3+i+k]=1;
}
for(int i=1;i<=k;++i){
for(int j=i;j<=k;++j){
B.c[3+i][3+k+j]=(((1ll<<j-i)%mod)*f[j][i])%mod;
B.c[3+k+i][3+k+j]=f[j][i];
}
}
A=A*pow(B,n-1);
printf("%lld",A.c[1][3]);
return 0;
}
T3 JM的荧光棒工厂
Description
题意简述
给定字符串 \(S(1\leqslant|S|\leqslant2\times10^5)\),求出所有既是 \(S\) 的前缀,又是 \(S\) 的后缀的子串长度。
Solution
法一 KMP
主要考察 KMP Next
数组的理解和应用。
经典KMP题了。
法二 字符串Hash
因为某种不可描述的原因,咕掉了 我自裁qwq
枚举一下 \(1\sim |S|-1\) 的所有长度,比对这个长度的前缀和后缀的Hash值是否相等即可。
Code
KMP
AC on 04/03
请仔细思考后展开。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define int long long
using std::sort;
const int maxn=2e5+5;
int tot,len;
char S[maxn];
int ans[maxn];
int Next[maxn];
void get_Next(void){
int i=0,j=-1;
Next[0]=-1;
while(i<len){
if(j==-1||S[i]==S[j]){
i++;
j++;
Next[i]=j;
}
else j=Next[j];
}
return;
}
signed main(){
scanf("%s",S);
len=strlen(S);
get_Next();
while(len>0){
ans[++tot]=len;
len=Next[len];
}
printf("%lld\n",tot-1);
sort(ans+1,ans+tot+1);
for(int i=1;i<tot;++i)
printf("%lld ",ans[i]);
return 0;
}
字符串Hash
AC on 04/05
请仔细思考后展开。
#include<cstdio>
#include<cstring>
#define int unsigned long long
const int p=13331;
const int maxn=2e5+5;
int tot,len;
char s[maxn];
int ans[maxn];
int __HaSh__[maxn];
inline int qkp(int ds,int zs){
int ans=1;
while(zs){
if(zs&1)ans*=ds;
ds*=ds;
zs>>=1;
}
return ans;
}
inline int Get_Hash(int l,int r){
int q=qkp(p,r-l+1);
return __HaSh__[r]-__HaSh__[l-1]*q;
}
signed main(){
scanf("%s",s+1);
len=strlen(s+1);
for(int i=1;i<=len;++i)
__HaSh__[i]=__HaSh__[i-1]*p+s[i];
for(int i=1;i<len;++i){
if(Get_Hash(1,i)==Get_Hash(len-i+1,len))
ans[++tot]=i;
}
printf("%llu\n",tot);
for(int i=1;i<=tot;++i)
printf("%llu ",ans[i]);
return 0;
}
T4 剪花布条
Description
题意简述
给定两个字符串 \(S,M(|S|,|M|\leqslant1000)\),求 \(S\) 中最多有多少个互不重叠的 \(M\)。
Solution
法一 KMP
又是一道KMP板子题。只是在KMP匹配成功时 \(j\) 要从 \(0\) 开始从头匹配。
法二 字符串Hash
同样也很板,直接模拟一遍匹配过程即可。但是常数与KMP相比稍大。
Code
KMP
AC on 04/03
请仔细思考后展开。
#include<cstdio>
#include<cstring>
const int maxn=1005;
int Next[maxn];
int ans,ALen,BLen;
char S[maxn],M[maxn];
void KMP(char *A,char *B){
ans=0;
int i=0,j=-1;
Next[0]=-1;
BLen=strlen(B);
while(i<BLen){
if(j==-1||B[i]==B[j]){
i++;
j++;
Next[i]=j;
}
else j=Next[j];
}
i=0,j=0;
while(i<ALen&&j<BLen){
if(j==-1||A[i]==B[j]){
i++;
j++;
}
else j=Next[j];
if(j==BLen){
ans++;
j=0;
}
}
return;
}
int main(){
while(~scanf("%s",S)){
if((ALen=strlen(S))==1){
if(S[0]=='#')
return 0;
}
scanf("%s",M);
KMP(S,M);
printf("%d\n",ans);
}
return 0;
}
字符串Hash
AC on 04/05
请仔细思考后展开。
#include<cstdio>
#include<cstring>
#define int unsigned long long
const int p=13331;
const int maxn=1005;
int __HaSh__[maxn];
char s[maxn],m[maxn];
int lens,lenm,ans,now,hamh;
inline int qkp(int ds,int zs){
int ans=1;
while(zs){
if(zs&1)ans*=ds;
ds*=ds;
zs>>=1;
}
return ans;
}
inline int Get_Hash(int l,int r){
int q=qkp(p,r-l+1);
return __HaSh__[r]-__HaSh__[l-1]*q;
}
signed main(){
while(~scanf("%s",s+1)){
ans=hamh=0;
if((lens=strlen(s+1))==1){
if(s[1]=='#')
return 0;
}
scanf("%s",m+1);
lenm=strlen(m+1);
for(int i=1;i<=lens;++i)
__HaSh__[i]=__HaSh__[i-1]*p+s[i];
for(int i=1;i<=lenm;++i)
hamh=hamh*p+m[i];
now=1;
while(now<=lens-lenm+1){
if(Get_Hash(now,now+lenm-1)==hamh){
ans++;
now+=lenm;
}
else now++;
}
printf("%llu\n",ans);
}
return 0;
}
T5 函数
Description
题意简述
有 \(Q\) 个询问 \((1\leqslant Q\leqslant 10^5)\),对于每个测试点,给定 \(mod(1\leqslant mod\leqslant 10^{14})\),对于每个询问,给定 \(n,r(1\leqslant r\leqslant n\leqslant 10^5)\),其中 \(mod\) 不一定是质数,求 \(C_n^r\times(n-r)!\bmod mod\)。
Solution
若 \(mod\) 是质数,可以用逆元。(虽然我不会)
但是,这里的 \(mod\) 不一定是质数了,我们就要想办法避免除法。
开始推式子:
此时暴力的时间复杂度为 \(\mathcal O(nQ)\),要炸。
所以考虑把 \(1\sim 10^5\) 中的连乘值处理出来,然后线段树区间查询 \(r+1\sim n\) 的连乘。
Code
这道不要脸的题居然要开 __int128
,差评。
注:int
有 \(\text{30pts}\),long long
有 \(\text{50pts}\)
AC on 04/03
请仔细思考后展开。
#include<cstdio>
#include<cstring>
#define int __int128
#define lt p<<1
#define rt lt|1
#define mid (a[p].l+a[p].r>>1)
const int maxn=1e5+5;
const int LEN=(1<<20);
int Q,n,mod,r;
namespace Segment_Tree{
struct Structure_of_Segment_Tree{int l,r,mul;}a[maxn<<2];
inline void PushUp(int p){
a[p].mul=(a[lt].mul*a[rt].mul)%mod;
return;
}
void Build(int p,int l,int r){
a[p].l=l;a[p].r=r;
a[p].mul=1;
if(l==r){
a[p].mul=l%mod;
return;
}
Build(lt,l,mid);
Build(rt,mid+1,r);
PushUp(p);
return;
}
int Query(int p,int l,int r){
if(l<=a[p].l&&a[p].r<=r)
return a[p].mul;
int val=1;
if(l<=mid)
val=Query(lt,l,r);
if(r>mid)
val=(val*Query(rt,l,r))%mod;
return val;
}
}using namespace Segment_Tree;
inline int nec(){
static char buf[LEN],*p=buf,*e=buf;
if(p==e){
if((e=buf+fread(buf,1,LEN,stdin))==buf)return EOF;
p=buf;
}
return *p++;
}
inline bool read(int&x){
static char neg=0,c=nec();
neg=0,x=0;
while((c<'0'||c>'9')&&c!='-'){
if(c==EOF)return 0;
c=nec();
}
if(c=='-'){
neg=1;
c=nec();
}
do{x=x*10+c-'0';}while((c=nec())>='0');
if(neg)x=-x;
return 1;
}
void print(int x){
if(x>9)print(x/10);
putchar(x%10+'0');
return;
}
signed main(){
#ifdef ONLINE_JUDGE
freopen("function.in","r",stdin);
freopen("function.out","w",stdout);
#endif
read(Q);read(mod);
Build(1,1,1e5+5);
while(Q--){
read(n);read(r);
print(Query(1,r+1,n));
putchar('\n');
}
return 0;
}
T6 树的统计
Description
Problem Link, and this,this,this,this,this.
题意简述
原题目描述够简了。
Solution
树剖板子题,有手就行,不讲。快来!这里有个人水博客!
Code
AC on 04/03
请仔细思考后展开。
#include<cstdio>
const int maxn=3e4+5;
const int inf=0x3f3f3f3f;
struct _{
int l,r;
int mx,sum;
}a[maxn<<2];
char s[15];
int n,q,x,y,tot,cnt;
int to[maxn<<1],nxt[maxn<<1];
int h[maxn],dep[maxn],w[maxn],rk[maxn];
int f[maxn],hvst[maxn],siz[maxn],st[maxn],top[maxn];
void add(int x,int y){
to[++tot]=y;
nxt[tot]=h[x];
h[x]=tot;
return;
}
int max(int x,int y){return x>y?x:y;}
void swap(int&x,int&y){
if(x==y)return;
x^=y^=x^=y;
return;
}
void dfs1(int x,int fa){
siz[x]=1;
int mxsiz=0;
for(int i=h[x];i;i=nxt[i]){
int v=to[i];
if(v==fa)continue;
dep[v]=dep[x]+1;
f[v]=x;
dfs1(v,x);
siz[x]+=siz[v];
if(siz[v]>mxsiz){
mxsiz=siz[v];
hvst[x]=v;
}
}
return;
}
void dfs2(int x,int tp){
top[x]=tp;
st[x]=++cnt;
rk[cnt]=x;
if(!hvst[x])
return;
dfs2(hvst[x],tp);
for(int i=h[x];i;i=nxt[i]){
int v=to[i];
if(v!=f[x]&&v!=hvst[x])
dfs2(v,v);
}
return;
}
void build(int p,int l,int r){
a[p].l=l,a[p].r=r;
if(l==r){
a[p].mx=a[p].sum=w[rk[l]];
return;
}
int mid=l+r>>1;
int lt=p<<1;int rt=lt|1;
build(lt,l,mid);
build(rt,mid+1,r);
a[p].mx=max(a[lt].mx,a[rt].mx);
a[p].sum=a[lt].sum+a[rt].sum;
return;
}
void Update(int p,int x,int v){
if(a[p].l==a[p].r){
a[p].sum=a[p].mx=v;
return;
}
int lt=p<<1;int rt=lt|1;
int mid=a[p].l+a[p].r>>1;
if(x<=mid)Update(lt,x,v);
else Update(rt,x,v);
a[p].mx=max(a[lt].mx,a[rt].mx);
a[p].sum=a[lt].sum+a[rt].sum;
return;
}
int QueryMax(int p,int l,int r){
if(l<=a[p].l&&r>=a[p].r)
return a[p].mx;
int val=-inf;
int mid=a[p].l+a[p].r>>1;
if(l<=mid)
val=max(val,QueryMax(p<<1,l,r));
if(r>mid)
val=max(val,QueryMax((p<<1)|1,l,r));
return val;
}
int QuerySum(int p,int l,int r){
if(l<=a[p].l&&r>=a[p].r)
return a[p].sum;
int val=0;
int mid=a[p].l+a[p].r>>1;
if(l<=mid)
val+=QuerySum(p<<1,l,r);
if(r>mid)
val+=QuerySum((p<<1)|1,l,r);
return val;
}
int QueryMaxPath(int x,int y){
int res=-inf;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])
swap(x,y);
res=max(res,QueryMax(1,st[top[x]],st[x]));
x=f[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
res=max(res,QueryMax(1,st[x],st[y]));
return res;
}
int QuerySumPath(int x,int y){
int res=0;
while(top[x]!=top[y]){
if(dep[top[x]]<dep[top[y]])
swap(x,y);
res+=QuerySum(1,st[top[x]],st[x]);
x=f[top[x]];
}
if(dep[x]>dep[y])swap(x,y);
res+=QuerySum(1,st[x],st[y]);
return res;
}
int main(){
dep[1]=1;
scanf("%d",&n);
for(int i=1;i<n;++i){
scanf("%d%d",&x,&y);
add(x,y);add(y,x);
}
for(int i=1;i<=n;++i)
scanf("%d",&w[i]);
dfs1(1,-1);
dfs2(1,1);
build(1,1,n);
scanf("%d",&q);
while(q--){
scanf("%s%d%d",s+1,&x,&y);
if(s[1]=='C')
Update(1,st[x],y);
else if(s[2]=='M')
printf("%d\n",QueryMaxPath(x,y));
else printf("%d\n",QuerySumPath(x,y));
}
return 0;
}
end.
—— · EOF · ——
真的什么也不剩啦 😖