2023.1.15/16 日寄
2023.1.15 日寄
一言
在现实断裂的地方,梦,汇成了海。——顾城
昨日复习内容:组合数学
「APIO2016」 划艇
题意
\(~~~~\) \(n\) 个位置,每个位置有取值区间,对于取值了的位置满足从左往右递增,求取值的方案数(至少应有一个位置取值)。
\(~~~~\) \(1\leq N\leq 500,1\leq l_i,r_i\leq 10^9\).
题解
\(~~~~\) 一眼 DP:\(dp_{i,j}\) 表示前 \(i\) 个最后选 \(j\) ,但过不了。
\(~~~~\) 然后注意到我们只关心区间的端点,所以我们把区间端点拉下来离散化变成 \(\mathcal{O(n)}\) 级别,然后dp的第二维就可以用区间左端点代替。
\(~~~~\) 对于不同区间的我们可以前缀和直接做,对于同一区间的呢?如果一个长为 \(L+1\) 的区间内有 \(k\) 个位置要选,那方案为 \(\binom{L}{k}\) ,可以用插板法去考虑。所以我们直接在dp后面加入一维来记录某个取值区间的选择次数,然后对于在同一区间和不在同一区间的分别dp就好了。
代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
const int MOD=1e9+7;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=Mul(ret,a);
b>>=1;a=Mul(a,a);
}
return ret;
}
int L[1005],R[1005],brr[1005],tot;
int dp[1005][1005],Inv[1005],Sum[1005],Num[1005];
int main() {
int n;read(n);Inv[1]=1;
for(int i=2;i<=1000;i++) Inv[i]=Dec(0,Mul(Inv[MOD%i],MOD/i));
for(int i=1;i<=n;i++) read(L[i]),read(R[i]),brr[++tot]=L[i],brr[++tot]=R[i]+1;
sort(brr+1,brr+1+tot); tot=unique(brr+1,brr+1+tot)-brr-1;
dp[0][0]=1;
for(int i=0;i<=tot;i++) Sum[i]=1;
for(int i=1;i<=n;i++)
{
L[i]=lower_bound(brr+1,brr+1+tot,L[i])-brr;
R[i]=lower_bound(brr+1,brr+1+tot,R[i]+1)-brr;
for(int j=L[i];j<R[i];j++)
{
Num[j]++;
for(int k=Num[j];k>=2;k--) dp[j][k]=Add(dp[j][k],Mul(Inv[k],Mul(dp[j][k-1],brr[j+1]-brr[j]-k+1)));
dp[j][1]=Add(dp[j][1],Mul(Sum[j-1],Dec(brr[j+1],brr[j])));
}
for(int j=1;j<=tot;j++)
{
Sum[j]=Sum[j-1];
for(int k=1;k<=Num[j];k++) Sum[j]=Add(Sum[j],dp[j][k]);
}
}
printf("%d",Dec(Sum[tot-1],1));
return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
落日绣帘卷,亭下水连空。知君为我新作,窗户湿青红。长记平山堂上,欹枕江南烟雨,杳杳没孤鸿。认得醉翁语,山色有无中。
一千顷,都镜净,倒碧峰。忽然浪起,掀舞一叶白头翁。堪笑兰台公子,未解庄生天籁,刚道有雌雄。一点浩然气,千里快哉风。
*/
题意
\(~~~~\) 求长为 \(n\) 且恰好有 \(k\) 个逆序对的排列个数。
\(~~~~\) \(1\leq n,k\leq 10^5,1\leq k\leq \binom{n}{2}\).
题解
\(~~~~\) 做一个转化就会注意到题目的实质是求给 \(n\) 个变量取值,满足 \(x_i\in[0,i-1]\) 且 \(\sum x_i=k\) 的方案数。
\(~~~~\) 很难不去想容斥,那我们计算限制一些位置 \(x_i\geq i\),假设这部分限制的和为 \(s\) ,那么就会有 \(\binom{k-s+n-1}{n-1}\) 的其余选法。显然这会带上 \((-1)^p\) 的容斥系数,其中 \(p\) 是限制位的个数。剩下部分就带上系数算就好了。
\(~~~~\) 那所以我们只需要求从 \(1\sim n\) 中选出若干个数,和为 \(1\sim k\) 且限制个数为奇/偶的方案数。
\(~~~~\) 这可以做一个转化:初始为空的数列,每次要么给数列中所有元素 \(+1\),要么给数列中所有元素 \(+1\) 且再加入一个 \(1\)。最后数列里的数就是被限制的数。那么记 \(dp_{i,j}\) 表示选择 \(i\) 个数(转化中加入数列的数),和为 \(j\) 的方案,那么:
可以发现由于最终数列里的数互不相同,那么数量 \(N\) 满足 \(\frac{N(N+1)}{2}\geq k\) ,所以 \(N\) 是 \(\sqrt{k}\) 级别的。
代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
const int MOD=1e9+7;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=Mul(ret,a);
b>>=1;a=Mul(a,a);
}
return ret;
}
int Fac[200005],Inv[200005],dp[451][100005];
inline int C(int n,int r){return r<=n?Mul(Fac[n],Mul(Inv[r],Inv[n-r])):0;}
int main() {
int n,k;read(n);read(k);
Fac[0]=1;
for(int i=1;i<=n+k;i++) Fac[i]=Mul(Fac[i-1],i);
Inv[n+k]=qpow(Fac[n+k],MOD-2);
for(int i=n+k-1;i>=0;i--) Inv[i]=Mul(Inv[i+1],i+1);
dp[0][0]=1;
for(int i=1;i<=450;i++)
{
for(int j=0;j<=k;j++)
{
if(j>=i) dp[i][j]=Add(dp[i][j-i],dp[i-1][j-i]);
if(j>=n+1) dp[i][j]=Dec(dp[i][j],dp[i-1][j-(n+1)]);
}
}
int Ans=0;
for(int i=0;i<=450;i++)
for(int j=0;j<=k;j++)
if(dp[i][j]) Ans=Add(Ans,Mul(((i&1)?MOD-1:1),Mul(C(k-j+n-1,n-1),dp[i][j])));
printf("%d",Ans);
return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
落日绣帘卷,亭下水连空。知君为我新作,窗户湿青红。长记平山堂上,欹枕江南烟雨,杳杳没孤鸿。认得醉翁语,山色有无中。
一千顷,都镜净,倒碧峰。忽然浪起,掀舞一叶白头翁。堪笑兰台公子,未解庄生天籁,刚道有雌雄。一点浩然气,千里快哉风。
*/
「THUPC2019」过河卒二
题意
\(~~~~\) 从 \((1,1)\) 每次向上、向右或者向右上走一格,不经过一些禁止点(共 \(k\) 个)的情况下走出 \(n\times m\) 的棋盘的方案数。对 \(59393\) 取模。
\(~~~~\) \(1\leq n\leq 10^9,1\leq m\leq 10^5,1\leq k\leq 20\).
题解
\(~~~~\) 注意到一个显然的转化,题目求的是从 \((1,1)\) 到 \((n+1,m+1)\) 的方案数,因为走出棋盘过后走到 \((n+1,m+1)\) 的方案就唯一了。(但我没有注意到
\(~~~~\) 然后没有限制的话我们可以枚举向右上走的次数 \(i\) ,所以方案为:
\(~~~~\) 加入了限制那就仿照 BZOJ 两双手 的做法来容斥做做就好了。
代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
struct node{
int x,y;
}P[25];
const int MOD=59393;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=Mul(ret,a);
b>>=1;a=Mul(a,a);
}
return ret;
}
int Ans[25][25];
int F[25],G[25];
int Fac[60005],Inv[60005],PC[2000005];
inline int C(int n,int r){return r>n?0:Mul(Fac[n],Mul(Inv[r],Inv[n-r]));}
inline int Lucas(int n,int r)
{
if(n<MOD) return C(n,r);
return Mul(Lucas(n/MOD,r/MOD),Lucas(n%MOD,r%MOD));
}
inline int Solve(int N,int M)
{
int ret=0;
for(int i=0;i<=min(N,M);i++) ret=Add(ret,Mul(Lucas(N+M-i,i),Lucas(N+M-2*i,N-i)));
return ret;
}
bool cmp(node a,node b){return a.x==b.x?a.y<b.y:a.x<b.x;}
int main() {
Fac[0]=1;
for(int i=1;i<MOD;i++) Fac[i]=Mul(Fac[i-1],i);
Inv[MOD-1]=qpow(Fac[MOD-1],MOD-2);
for(int i=MOD-2;i>=0;i--) Inv[i]=Mul(Inv[i+1],i+1);
int n,m,k;read(n);read(m);read(k);
for(int i=1;i<=k;i++) read(P[i].x),read(P[i].y);
sort(P+1,P+1+k,cmp);
for(int i=1;i<=k;i++)
{
for(int j=i+1;j<=k;j++)
{
if(!(P[i].x<=P[j].x&&P[i].y<=P[j].y)) continue;
Ans[i][j]=Solve(P[j].x-P[i].x,P[j].y-P[i].y);
}
}
for(int i=1;i<=k;i++) F[i]=Solve(P[i].x-1,P[i].y-1);
for(int i=1;i<=k;i++) G[i]=Solve(n+1-P[i].x,m+1-P[i].y);
for(int i=1;i<(1<<k);i++) PC[i]=PC[i>>1]+(i&1);
int Res=Solve(n,m);
for(int i=1;i<(1<<k);i++)
{
int lst=0,Tmp=0;
for(int j=1;j<=k;j++)
{
if((i>>(j-1))&1)
{
if(lst==0) Tmp=F[j];
else Tmp=Mul(Tmp,Ans[lst][j]);
lst=j;
}
}
Tmp=Mul(Tmp,G[lst]);
Res=Add(Res,Mul(((PC[i]&1)?MOD-1:1),Tmp));
}
printf("%d",Res);
return 0;
}
/*
清夜无尘。月色如银。酒斟时、须满十分。浮名浮利,虚苦劳神。叹隙中驹,石中火,梦中身。
虽抱文章,开口谁亲。且陶陶、乐尽天真。几时归去,作个闲人。对一张琴,一壶酒,一溪云。
落日绣帘卷,亭下水连空。知君为我新作,窗户湿青红。长记平山堂上,欹枕江南烟雨,杳杳没孤鸿。认得醉翁语,山色有无中。
一千顷,都镜净,倒碧峰。忽然浪起,掀舞一叶白头翁。堪笑兰台公子,未解庄生天籁,刚道有雌雄。一点浩然气,千里快哉风。
*/
Magic
题意
\(~~~~\) \(m\) 种数共 \(n\) 个,每种都有 \(a_i\) 个,求有多少方案有恰好 \(k\) 个相邻颜色。
\(~~~~\) \(1\leq m\leq 2\times 10^4,1\leq n\leq 10^5\).
题解
\(~~~~\) 看到恰好 \(k\) 个还是去想容斥做法,那么就有
\(~~~~\) 其中 \(f_j\) 为至少有 \(j\) 个相邻颜色的方案,那么我们只需要求这个就好。
\(~~~~\) 考虑 DP,设 \(dp_{i,j}\) 表示前 \(i\) 种颜色,划分成了 \(j\) 个连续颜色段(相邻的颜色段颜色可能是相邻的),那转移就是对每种新颜色考虑划分成多少段以及插入到原段中:
\(~~~~\) 两个组合数分别就是对颜色的划分和插入。
\(~~~~\) 那么 \(f_i=dp_{m,n-i}\)。
\(~~~~\) 不过很显然 \(n^2\) dp 太慢了,过不完所有点,所以我们需要优化。
\(~~~~\) 按照这种套路,把 \(\binom{j}{k}\) 拆开:
\(~~~~\) 显然的分治NTT形式。
代码
查看代码
#include <bits/stdc++.h>
using namespace std;
template<typename T>void read(T &x)
{
T sig=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')sig=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=sig;
}
const int MOD=998244353,gg=3;
inline int Add(int a,int b){return (a+b)%MOD;}
inline int Dec(int a,int b){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=Mul(ret,a);
b>>=1;a=Mul(a,a);
}
return ret;
}
int N,Lg,arr[500005];
const int gi=qpow(gg,MOD-2);
int Fac[500005],Inv[500005],To[500005];
void NTT(int *S,int op)
{
for(int i=0;i<N;i++) if(i<To[i]) swap(S[i],S[To[i]]);
for(int i=1;i<N;i<<=1)
{
int W=qpow((op==1)?gg:gi,(MOD-1)/(i<<1));
for(int j=0;j<N;j+=i<<1)
{
int w=1;
for(int k=0;k<i;k++,w=Mul(w,W))
{
int x=S[j+k],y=Mul(S[i+j+k],w);
S[j+k]=Add(x,y); S[i+j+k]=Dec(x,y);
}
}
}
if(op==-1)
{
int inv=qpow(N,MOD-2);
for(int i=0;i<N;i++) S[i]=Mul(S[i],inv);
}
}
inline int C(int n,int r){return n<r?0:Mul(Fac[n],Mul(Inv[r],Inv[n-r]));}
int A[500005],B[500005],CC[500005];
void MulPoly(vector<int> &F,vector<int> &G,vector<int> &res)
{
for(N=1,Lg=0;N<(int)(F.size()+G.size()-1);N<<=1,Lg++);
for(int i=0;i<N;i++) To[i]=(To[i>>1]>>1)|((i&1)<<(Lg-1));
for(int i=0;i<(int)F.size();i++) A[i]=F[i]; for(int i=(int)F.size();i<N;i++) A[i]=0;
for(int i=0;i<(int)G.size();i++) B[i]=G[i]; for(int i=(int)G.size();i<N;i++) B[i]=0;
NTT(A,1); NTT(B,1);
for(int i=0;i<N;i++) CC[i]=Mul(A[i],B[i]);
NTT(CC,-1); res.clear();
for(int i=0;i<(int)(F.size()+G.size()-1);i++) res.push_back(CC[i]);
}
vector <int> F[400005],FF[400005];
void Solve(int p,int l,int r)
{
if(l==r){FF[p]=F[l];return;}
int mid=(l+r)>>1;
Solve(p<<1,l,mid); Solve(p<<1|1,mid+1,r);
MulPoly(FF[p<<1],FF[p<<1|1],FF[p]);
}
int f[100005];
int main() {
Fac[0]=1; for(int i=1;i<=100000;i++) Fac[i]=Mul(Fac[i-1],i);
Inv[100000]=qpow(Fac[100000],MOD-2);
for(int i=99999;i>=0;i--) Inv[i]=Mul(Inv[i+1],i+1);
int m,n,k;read(m);read(n);read(k);
for(int i=1;i<=m;i++)
{
read(arr[i]);
F[i].push_back(0);
for(int j=1;j<=arr[i];j++) F[i].push_back(Mul(C(arr[i]-1,j-1),Inv[j]));
}
Solve(1,1,m);
for(int i=0;i<=n;i++)
f[i]=Mul(FF[1][n-i],Fac[n-i]);
int Ans=0;
for(int i=k;i<=n;i++) Ans=Add(Ans,Mul(((i-k)&1?(MOD-1):1),Mul(f[i],C(i,k))));
printf("%d",Ans);
return 0;
}
/*
西风吹老洞庭波,一夜湘君白发多。
醉后不知天在水,满船清梦压星河。
*/
鲜花
\(~~~~\) 当晚CF被D题搞心态怒罚10发。
\(~~~~\) 我的评价是:成就心态之道。(说起这个前一天晚上打屠掉了九个百分点的胜率,还是靠最后祥和猫猫人救回来的,看来最近水逆
2023.1.16 日寄
一言
落叶在晚风中下坠
日落了,却再没人会写诗,我对自己说——《日落了,却没人写诗》
今日理论复习内容:数论
「ICPC World Finals 2020」后缀数位
题意
\(~~~~\) 给出 \(b\) ,求 \(b\times k\leq a,k\in Z\) 条件下最多的末尾连续数字 \(d\) 的个数。
\(~~~~\) \(1\leq b<10^6,0\leq d\leq 9,b\leq a<10^{10^4}\).
题解
\(~~~~\) 小奥既视感。
\(~~~~\) 我们设最后得到的数为 \(10^lx+\underbrace{\overline{ddd\dots d}}_{\text{l个d}}\),那么这个数的答案为 \(l\),满足该数与 \(0\) 在模 \(b\) 意义下同余。
\(~~~~\) 显然也就是 \(10^lx \equiv -\underbrace{\overline{ddd\dots d}}_{\text{l个d}} \pmod b\)。
\(~~~~\) 我们从大到小枚举 \(l\) ,那根据裴蜀定理有解的条件就应该是 \(\gcd(10^l,b)|-\underbrace{\overline{ddd\dots d}}_{\text{l个d}}\),并且由扩展欧几里得可以解出 \(x\) ,那我们只需把这个数拼回去研究其是否满足 \(\leq a\) 即可。
\(~~~~\) 稳妥起见我们还可以枚举当前 \(l\) 下所有可行的 \(x\),因为这些 \(x\) 后面可能也有 \(d\).
代码
查看代码
#include <bits/stdc++.h>
#define ll long long
#define PII pair<int,int>
#define mp(a,b) make_pair(a,b)
using namespace std;
template<typename T>void read(T &x)
{
T f=1;x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=x*10+ch-'0';ch=getchar();}
x*=f;
}
char A[100005];
inline int Add(int a,int b,int MOD){return (a+b)%MOD;}
inline int Dec(int a,int b,int MOD){return (a-b+MOD)%MOD;}
inline int Mul(int a,int b,int MOD){return 1ll*a*b%MOD;}
inline int qpow(int a,int b,int MOD)
{
int ret=1;
while(b)
{
if(b&1) ret=Mul(ret,a,MOD);
b>>=1;a=Mul(a,a,MOD);
}
return ret;
}
inline int exgcd(int a,int b,int &x,int &y)
{
if(b==0){x=1;y=0;return a;}
int res=exgcd(b,a%b,x,y);
int Tmp=x;x=y;y=Tmp-a/b*y;
return res;
}
PII Solve(int a,int b,int m)//ax+by=m
{
int x,y,GCD=exgcd(a,b,x,y);
if(m%GCD) return mp(-1,-1);
ll ret=0;
ret=(1ll*x*m/GCD%b+b)%(b/GCD);
return mp(ret,GCD);
}
int b,d,len;
char Now[100005];
int Pow10[100005],Powd[100005];
bool cmp(int x,int i)//数字x后面接上i个d
{
int Len=0,Tmp=x;
while(Tmp) Tmp/=10,Len++;
if(Len+i<len) return true;
if(Len+i>len) return false;
for(int j=Len;j>=1;j--) Now[j]=x%10+'0',x/=10;
for(int j=Len+1;j<=len;j++) Now[j]=d+'0';
for(int j=1;j<=len;j++)
{
if(Now[j]>A[j]) return false;
if(Now[j]<A[j]) return true;
}
return true;
}
int main() {
// freopen("42.in","r",stdin);
read(b);read(d);scanf("%s",A+1);
len=strlen(A+1);
Pow10[0]=1; Powd[0]=0;
for(int i=1;i<=len;i++)
{
Pow10[i]=Pow10[i-1]*10%b;
Powd[i]=(Powd[i-1]*10+d)%b;
}
int Ans=0,Lenb=0,Down=0,Tmp=b;
while(Tmp) Lenb++,Tmp/=10;
for(int Len=len;Len>=Down;Len--)
{
PII res=Solve(Pow10[Len],b,((-Powd[Len])+b)%b);
if(res.first==-1) continue;
if(res.first==0) res.first+=b/res.second;
int x=res.first;
int X=pow(10,(int)log10(x)+1);
while(cmp(x,Len)&&x<X)
{
int cnt=0,Tmpx=x;
while(Tmpx&&Tmpx%10==d)
cnt++,Tmpx/=10;
Ans=max(Ans,Len+cnt);
x+=b/res.second; Down=max(0,Len-Lenb);
}
}
printf("%d",Ans);
return 0;
}
/*
西风吹老洞庭波,一夜湘君白发多。
醉后不知天在水,满船清梦压星河。
是一个同余方程,但需要判断解的大小
解的末尾可能也是d
那会被更上面的枚举算到吗?
会的吧
复杂度肯定都对,但我都还要写一个大整数比较
625 0 10000
*/
摆家具 / furniture
题意
\(~~~~\) 在 \(k\) 维空间中共 \(n^k\) 个点,每一维坐标取值 \([0,n]\)。每个点有点权。\(q\) 次询问,每次询问从某个点开始仅变换某一维上的坐标,变换 \(T\) 次后所有方式到达的点权值和。对 \(998244353\) 取模。
\(~~~~\) \(n\geq 2,k\geq 1,n^k\leq 10^6,q\leq 5\times 10^5,0\leq T< 998244353\).
「THUPC2019」 题解
\(~~~~\) 又是看题解题。
\(~~~~\) 注意到对于初始点来说我们到达两个点的方案数不同当且仅当这两个点与之坐标不同的维数不同,原因很显然。另外注意到 \(k\) 是 \(\log_2n^k\) 级别,也就是约 \(20\) 的。
\(~~~~\) 所以我们尝试用不同的维度数(以下称该值为距离)来刻画点,那我们只需要关注对于某个点距离为 \(i\) 的点的点权和和到达它们的方案数。
\(~~~~\) 记 \(f_{i,j}\) 表示距离点 \(i\) 为 \(j\) 的点的权值和,\(g_{i,j}\) 为经过 \(i\) 次操作后到达与初始状态距离为 \(j\) 的点的方案,最终答案即为:
\(~~~~\) 注意到这里下面除去了满足该条件的总点数,因为已经说明了到每个点的方案数肯定都是一样的,所以就可以这样求单个的方案数。
\(~~~~\) 现在的问题就是怎么求 \(f\) 和 \(g\),一个一个来看:
\(~~~~\) 对于 \(f_{i,j}\) 发现其只能从 \(f_{i,j-1}\) 或 \(f_{x,j}\) 转移过来,那么枚举位转移,做到 \(\mathcal{O(n^kk^2)}\)
\(~~~~\) 对于 \(g\) 来说,\(g_{i,j}\) 只与 \(g_{i-1,j-1},g_{i-1,j},g_{i-1,j+1}\) 有关,所以可以考虑用矩阵来维护 \(g\)。
\(~~~~\) 但是 \(q\) 次询问每次都做 \(\mathcal{O(\log MOD k^3)}\) 的东西太慢了,怎么办呢?我们设一个阈值 \(B\),分别处理 \(M^0,M^1,M^2,\dots,M^{B-1}\) 和 \(M^B,M^{2B},\dots,M^{B^2}\) ,那么询问的时候就只需要 \(\mathcal{O(k^3)}\) 合并两边就好了。当然这里 \(B=\sqrt{MOD}\) 即可。
\(~~~~\) 然后还需要稍加卡常,这里用到了fread快读快输,少取模就可以过了。
代码
查看代码
#include <bits/stdc++.h>
#define ll long long
using namespace std;
static char buf[1000000],*p1=buf,*p2=buf,obuf[1000000],*p3=obuf;
#define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++
#define putchar(x) (p3-obuf<1000000)?(*p3++=x):(fwrite(obuf,p3-obuf,1,stdout),p3=obuf,*p3++=x)
template<typename item>
inline void read(register item &x){
x=0;register int f=1;register char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
x*=f;
}
static char cc[20];
template<typename item>
inline void print(register item x){
register int len=0;
if(x<0)x=-x,putchar('-');
while(x)cc[len++]=x%10+'0',x/=10;
while(len--)putchar(cc[len]);
}
int n,k,q;
int Pow[1000],Way[1000];
int Fac[1000005],Inv[1000005];
int f[22][1000005],Val[1000005],g[1000005];
const int MOD=998244353;
inline int Add(int a,int b){int c=a+b;return c>=MOD?c-MOD:c;}
inline int Dec(int a,int b){int c=a-b;return c<0?c+MOD:c;}
inline int Mul(int a,int b){return 1ll*a*b%MOD;}
inline int qpow(int a,int b)
{
int ret=1;
while(b)
{
if(b&1) ret=Mul(ret,a);
b>>=1;a=Mul(a,a);
}
return ret;
}
const int B=sqrt(MOD);
struct Matrix{
int mat[20][20];
Matrix(){memset(mat,0,sizeof(mat));}
}Pow1[B+5],Pow2[B+5];
inline Matrix operator * (const Matrix a,const Matrix b)
{
Matrix c;
for(int i=0;i<=k;i++)
for(int j=0;j<=k;j++)
for(int K=0;K<=k;K++) c.mat[i][j]=Add(c.mat[i][j],Mul(a.mat[i][K],b.mat[K][j]));
return c;
}
inline int C(int N,int r){return N<r?0:Mul(Fac[N],Mul(Inv[r],Inv[N-r]));}
int main() {
// freopen("5.in","r",stdin);
read(n);read(k);read(q);
int N=qpow(n,k);
Fac[0]=Inv[0]=1;
for(int i=1;i<=N;i++) Fac[i]=Mul(Fac[i-1],i);
Inv[N]=qpow(Fac[N],MOD-2);
for(int i=N-1;i>=1;i--) Inv[i]=Mul(Inv[i+1],i+1);
Pow[0]=1; Way[0]=1;
for(int i=1;i<=k;i++) Pow[i]=Mul(Pow[i-1],n),Way[i]=Mul(Way[i-1],n-1);
for(int i=0;i<=k;i++) Way[i]=qpow(Mul(Way[i],C(k,i)),MOD-2);
for(int i=0;i<N;i++) read(Val[i]),f[0][i]=Val[i];
for(int i=0;i<=k-1;i++)
{
for(int j=k;j>=0;j--)
{
for(int S=0;S<N;S++) g[S]=0;
for(int S=0;S<N;S++) g[S/Pow[i+1]*Pow[i+1]+S%Pow[i]]=Add(g[S/Pow[i+1]*Pow[i+1]+S%Pow[i]],f[j][S]);
for(int S=0;S<N;S++)
{
f[j][S]=Dec(g[S/Pow[i+1]*Pow[i+1]+S%Pow[i]],f[j][S]);
if(j) f[j][S]=Add(f[j][S],f[j-1][S]);
}
}
}
Matrix Bas;
for(int i=0;i<=k;i++)
{
Bas.mat[i][i]=Mul(n-2,k-i);
Pow1[0].mat[i][i]=Pow2[0].mat[i][i]=1;
}
for(int i=1;i<=k;i++) Bas.mat[i][i-1]=Mul(i,n-1);
for(int i=0;i<k;i++) Bas.mat[i][i+1]=k-i;
for(int i=1;i<=B;i++) Pow1[i]=Pow1[i-1]*Bas;
for(int i=1;i<=B;i++) Pow2[i]=Pow2[i-1]*Pow1[B];
int lst=1,a;ll t;
while(q--)
{
read(a);read(t); t=Mul(t%MOD,lst);
int x=t/B,y=t%B,Ans=0;
for(int i=0;i<=k;i++)
{
int ret=0;
for(int j=0;j<=k;j++) ret=Add(ret,Mul(Pow2[x].mat[k][j],Pow1[y].mat[j][i]));
Ans=Add(Ans,Mul(ret,Mul(f[i][a],Way[k-i])));
}
print(lst=Ans);putchar('\n');
}
fwrite(obuf,p3-obuf,1,stdout);
return 0;
}
/*
西风吹老洞庭波,一夜湘君白发多。
醉后不知天在水,满船清梦压星河。
g_{i,j} 表示经过i轮后与初始状态距离为j的方案数。
考虑从 g_{i,x} -> g_{i+1,x} 只与x-1,x和x+1有关的线性递推
*/