P1054 遗失的赋值 官方题解
Gioush OJ · P1054 遗失的赋值
【数据点 1∼21\sim 21∼2】
- 这一档保证 。枚举全部 个 序列。
- 对每个序列检查 的总数与全部长度为 的区间和,统计合法序列即可。
- 使用滑动窗口可以在线性时间内检查一个序列,总时间复杂度为 。
【性质观察】
- 相邻两个窗口只有最左端与新加入的最右端不同,因此
- 一旦知道 ,就可以由窗口和之差唯一确定 。
- 于是下标对 取模相同的位置形成一条链,每条链只需要决定最前面的一个 值。
【数据点 3∼53\sim 53∼5】
- 这一档保证 。先在线性时间内预处理每条同余链在初值为 或 时是否合法,以及整条链中 的数量。
- 随后枚举前 个位置的 种取值。每次只需检查 条链,并核对 与 。
- 时间复杂度为 ,空间复杂度为 。
【数据点 6∼96\sim 96∼9】
- 对一条同余链分别尝试初值 。若两种初值都不合法,则整组数据无解。若只有一种合法,这条链已经被确定。
- 若两种初值都合法,就把它称为自由链。选择初值 会让前 个位置中的 增加一个,并让总数增加链长。
- 可以对自由链做计数 DP,记录选择了多少条链以及这些链贡献的 的总数。
- 利用链长只有两种,分别对两类自由链做组合 DP,可以做到 。
【数据点 10∼1510\sim 1510∼15】
- 这一档保证 ,所以所有同余链的长度都等于 。
- 设已经确定的链在前 个位置中贡献 个 ,在整个序列中贡献 个 。
- 必须从自由链中选择 条使用初值 ,并检查
- 若等式成立,答案就是从全部自由链中选择 条的方案数。
【正解】
令 ,前 条同余链长度为 ,其余链长度为 。
- 设两类自由链数量分别为 ,需要选择其中 条使用初值 。
- 根据第一段窗口和与整个序列中 的总数,有
- 因而 ,,答案为
【实现与复杂度】
- 预处理阶乘与逆阶乘,就可以在 时间内计算组合数。
- 每个位置只会在对应同余链中被扫描一次,因此单组时间复杂度为 。
- 组合数及最终答案均对 取模。链中 的总数与两个方程的右侧需要使用
long long。
【参考代码】
/*Author:EhundateghDate:2026/8/2Name:assign.cppYou steal,I kill.*/#include <cstdio>#include <cstring>#include <algorithm>#define MAXN 1000010using namespace std;const int Mod=998244353;int c,T,n,k,m,Line[MAXN],Delta[MAXN],Fac[MAXN],Inv[MAXN];int Mul(int a,int b){return 1ll*a*b%Mod;}int Power(int a,int b){ int Ret=1; while(b){ if(b&1) Ret=Mul(Ret,a); a=Mul(a,a);b>>=1; } return Ret;}int C(int n,long long m){ if(m<0||m>n) return 0; return Mul(Fac[n],Mul(Inv[m],Inv[n-m]));}void Init(){ Fac[0]=1; for(int i=1;i<MAXN;i++) Fac[i]=Mul(Fac[i-1],i); Inv[MAXN-1]=Power(Fac[MAXN-1],Mod-2); for(int i=MAXN-1;i>=1;i--) Inv[i-1]=Mul(Inv[i],i); return;}void Solve(){ scanf("%d%d%d",&n,&k,&m); for(int i=1;i<=n-k+1;i++) scanf("%d",&Line[i]); for(int i=1;i<=n-k;i++){ Delta[i]=Line[i+1]-Line[i]; if(Delta[i]<-1||Delta[i]>1){printf("0\n");return;} } int Long=n%k,Length=n/k,FreeLong=0,FreeShort=0; long long First=0,Total=0; for(int r=1;r<=k;r++){ int Now=0;bool Can[2]={1,1};long long Count[2]={0,0}; for(int i=r;i<=n;i+=k){ if(i!=r) Now+=Delta[i-k]; for(int x=0;x<=1;x++){ if(x+Now<0||x+Now>1) Can[x]=0; else Count[x]+=x+Now; } } if(!Can[0]&&!Can[1]){printf("0\n");return;} if(Can[0]&&Can[1]){ if(r<=Long) FreeLong++; else FreeShort++; } else{ int x=Can[1]; First+=x;Total+=Count[x]; } } long long NeedFirst=Line[1]-First,NeedTotal=m-Total; long long TakeLong=NeedTotal-NeedFirst*Length; long long TakeShort=NeedFirst-TakeLong; printf("%d\n",Mul(C(FreeLong,TakeLong),C(FreeShort,TakeShort))); return;}int main(){ Init(); scanf("%d%d",&c,&T); while(T-->0) Solve(); return 0;}