P1054 · OFFICIAL SOLUTION

P1054 遗失的赋值 官方题解

Gioush OJ · P1054 遗失的赋值

【数据点 1∼21\sim 21∼2】

  • 这一档保证 n20n\leq20。枚举全部 2n2^n0101 序列。
  • 对每个序列检查 11 的总数与全部长度为 kk 的区间和,统计合法序列即可。
  • 使用滑动窗口可以在线性时间内检查一个序列,总时间复杂度为 O(n2n)\mathcal{O}(n2^n)

【性质观察】

  • 相邻两个窗口只有最左端与新加入的最右端不同,因此
si+1si=xi+kxi. s_{i+1}-s_i=x_{i+k}-x_i.
  • 一旦知道 xix_i,就可以由窗口和之差唯一确定 xi+kx_{i+k}
  • 于是下标对 kk 取模相同的位置形成一条链,每条链只需要决定最前面的一个 0101 值。

【数据点 3∼53\sim 53∼5】

  • 这一档保证 k20k\leq20。先在线性时间内预处理每条同余链在初值为 0011 时是否合法,以及整条链中 11 的数量。
  • 随后枚举前 kk 个位置的 2k2^k 种取值。每次只需检查 kk 条链,并核对 s1s_1mm
  • 时间复杂度为 O(n+k2k)\mathcal{O}(n+k2^k),空间复杂度为 O(n)\mathcal{O}(n)

【数据点 6∼96\sim 96∼9】

  • 对一条同余链分别尝试初值 0,10,1。若两种初值都不合法,则整组数据无解。若只有一种合法,这条链已经被确定。
  • 若两种初值都合法,就把它称为自由链。选择初值 11 会让前 kk 个位置中的 11 增加一个,并让总数增加链长。
  • 可以对自由链做计数 DP,记录选择了多少条链以及这些链贡献的 11 的总数。
  • 利用链长只有两种,分别对两类自由链做组合 DP,可以做到 O(n+k2)\mathcal{O}(n+k^2)

【数据点 10∼1510\sim 1510∼15】

  • 这一档保证 knk\mid n,所以所有同余链的长度都等于 n/kn/k
  • 设已经确定的链在前 kk 个位置中贡献 ff11,在整个序列中贡献 gg11
  • 必须从自由链中选择 s1fs_1-f 条使用初值 11,并检查
mg=(s1f)nk. m-g=(s_1-f)\frac{n}{k}.
  • 若等式成立,答案就是从全部自由链中选择 s1fs_1-f 条的方案数。

【正解】

h=n/kh=\lfloor n/k\rfloor,前 nmodkn\bmod k 条同余链长度为 h+1h+1,其余链长度为 hh

  • 设两类自由链数量分别为 a,ba,b,需要选择其中 x,yx,y 条使用初值 11
  • 根据第一段窗口和与整个序列中 11 的总数,有
{x+y=s1f,(h+1)x+hy=mg. \begin{cases} x+y=s_1-f,\\ (h+1)x+hy=m-g. \end{cases}
  • 因而 x=(mg)h(s1f)x=(m-g)-h(s_1-f)y=(s1f)xy=(s_1-f)-x,答案为
(ax)(by). \dbinom{a}{x}\dbinom{b}{y}.

【实现与复杂度】

  • 预处理阶乘与逆阶乘,就可以在 O(1)\mathcal{O}(1) 时间内计算组合数。
  • 每个位置只会在对应同余链中被扫描一次,因此单组时间复杂度为 O(n)\mathcal{O}(n)
  • 组合数及最终答案均对 998,244,353998{,}244{,}353 取模。链中 11 的总数与两个方程的右侧需要使用 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;}