ZJOI2020 部分题解

40+100+100 + 30+100+10

Day1T1

  • 现在只会 \(40pts\)
  • 首先记录原串所有前缀的双 \(hash\) 值。
  • 然后,求出所有相同 本质相同的 ,符合条件的 子串,一起考虑它们对询问的答案。
  • 对于这些串,如果它们所在的位置分别为 \([l_1,r_1],[l_2,r_2],...,[l_k,r_k]\)
  • 我们把询问当做一个矩阵,贡献就是矩形加。\((1,l_1)-(r_1,n),(l_1+1,l_2)-(r_2,n),...,(l_{k-1}+1,l_k)-(r_k,n)\) 这些矩形都 \(+1\) 即可。
  • 这个可以差分前缀和,时间复杂度有关本质不同的子串个数(感觉为 \(O(n)\))。
  • 故总时间复杂度为 \(O(n^2)\)
#include<cstdio>
#include<map>
#include<vector>
using namespace std;
const int Mod1=1e9+7;
const int Mod2=1e9+9;
typedef long long ll;
typedef pair<int,int> pii;
int n,q; ll a[5100][5100]; 
ll hash1[210000],hash2[210000];
ll pow1[210000],pow2[210000];
char s[210000];
map<pii,int> mp;
vector<int> vec[210000];
inline ll hs1(int l,int r){
    return (hash1[r]-hash1[l-1]*pow1[r-l+1]%Mod1+Mod1)%Mod1;
}
inline ll hs2(int l,int r){
    return (hash2[r]-hash2[l-1]*pow2[r-l+1]%Mod2+Mod2)%Mod2;
}
int main(){
    scanf("%d%d",&n,&q);
    scanf("%s",s+1);
    pow1[0]=1; pow2[0]=1;
    for (int i=1;i<=n;i++) pow1[i]=pow1[i-1]*3%Mod1;
    for (int i=1;i<=n;i++) pow2[i]=pow2[i-1]*3%Mod2;
    for (int i=1;i<=n;i++) hash1[i]=(hash1[i-1]*3+(s[i]-'a'+1))%Mod1;
    for (int i=1;i<=n;i++) hash2[i]=(hash2[i-1]*3+(s[i]-'a'+1))%Mod2;
    for (int mid=1;mid*2<=n;mid++){
        mp.clear(); int cnt=0;
        for (int i=1;i+mid*2-1<=n;i++){
            int x1,x2,y1,y2;
            x1=hs1(i,i+mid-1); y1=hs1(i+mid,i+mid*2-1);
            x2=hs2(i,i+mid-1); y2=hs2(i+mid,i+mid*2-1);
            if (x1==y1&&x2==y2){
                if (!mp[pii(x1,x2)]){ mp[pii(x1,x2)]=++cnt; vec[cnt].push_back(0);}
                vec[mp[pii(x1,x2)]].push_back(i);
            }
        }
        for (int i=1;i<=cnt;i++){
            for (int j=1;j<(int)vec[i].size();j++){
                a[vec[i][j-1]+1][vec[i][j]+mid*2-1]++; 
                a[vec[i][j]+1][vec[i][j]+mid*2-1]--;
            }
            vec[i].clear();
        }
    }
    for (int i=1;i<=n;i++)
        for (int j=1;j<=n;j++)
            a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
    int x,y;
    while (q--){
        scanf("%d%d",&x,&y);
        printf("%lld\n",a[x][y]);
    }
    return 0;
}

Day1T2

  • 思路类似于去年的那道线段树,就是分成 \(5\) 类点来分别算贡献。(参考了pinkrabbit的 \(blog\)https://www.cnblogs.com/PinkRabbit/p/ZJOI2019D1T2.html)

  • 设当前区间为 \([l,r]\) ,父区间为 \([fa_l,fa_r]\)

  • \(tot_i\) 表示第 \(i\) 类点的个数,那么:

      tot1=seg_cnt-l*(n-r+1)-(getcnt(l-1)+getcnt(n-r));
      if (l==fa_l) tot2=l*(fa_r-r);
      else tot2=(l-fa_l)*(n-r+1);
      if (l==fa_l) tot3=getsum(n-fa_r+1,n-(r+1)+1);
      else tot3=getsum(fa_l,l-1);
      tot4=l*(n-r+1)-tot2;
      tot5=getcnt(l-1)+getcnt(n-r)-tot3;
    
  • 其中,\(getcnt(x)\)\(x(x+1)/2\)\(getsum(l,r)\)\(l+(l+1)+\cdots+r\)\(seg_{cnt}=getcnt(n)\)

  • 用同样的 \(f,g\) 的定义,一次操作后,有:\(<f[u],g[u]>\ =\ <\frac{tot_1*0+tot_2*1+tot_3*g[u]+tot_4*f[u]+tot_5*f[u]}{seg_{cnt}},\frac{tot_1*0+tot_2*1+tot_3*g[u]+tot_4*1+tot_5g[u]}{seg_{cnt}}>\)

  • \(f[u]=af[u]+bg[u]+c,g[u]=xg[u]+y\) ,一个 \(3*3\) 的矩阵即可。时间复杂度 \(O(n \log K)\)。为了卡常可以把矩乘展开。(没加快读)

#include<cstdio>
using namespace std;
typedef long long ll;
const int Mod=998244353;
int n,k,ans; ll seg_cnt,inv;
ll tot1,tot2,tot3,tot4,tot5;
ll a,b,c,x,y;
inline ll getcnt(int n){ return (1ll*n*(n+1)/2)%Mod;}
inline ll getsum(int x,int y){ return (1ll*(x+y)*(y-x+1)/2)%Mod;}
ll getinv(int x){
    if (x==1) return 1;
    return 1ll*(Mod-Mod/x)*getinv(Mod%x)%Mod;
}
struct mat{
    ll m11,m12,m13,m21,m22,m23,m31,m32,m33;
    mat(){ m11=m12=m13=m21=m22=m23=m31=m32=m33=0;}
    mat(ll v11,ll v12,ll v13,ll v21,ll v22,ll v23,ll v31,ll v32,ll v33){
        m11=v11; m21=v21; m31=v31;
        m12=v12; m22=v22; m32=v32;
        m13=v13; m23=v23; m33=v33;
    }
    void operator*=(const mat &x){
            ll t1,t2;
            t1=(m11*x.m11+m12*x.m21+m13*x.m31)%Mod;
            t2=(m11*x.m12+m12*x.m22+m13*x.m32)%Mod;
            m13=(m11*x.m13+m12*x.m23+m13*x.m33)%Mod;
            m11=t1; m12=t2;

            t1=(m21*x.m11+m22*x.m21+m23*x.m31)%Mod;
            t2=(m21*x.m12+m22*x.m22+m23*x.m32)%Mod;
            m23=(m21*x.m13+m22*x.m23+m23*x.m33)%Mod;
            m21=t1; m22=t2;

            t1=(m31*x.m11+m32*x.m21+m33*x.m31)%Mod;
            t2=(m31*x.m12+m32*x.m22+m33*x.m32)%Mod;
            m33=(m31*x.m13+m32*x.m23+m33*x.m33)%Mod;
            m31=t1; m32=t2;
    }
};
int qpow(){
    int a=k;
    mat res(1,0,0,0,1,0,0,0,1);
    mat   x(::a,::b,::c,0,::x,::y,0,0,1);
    while (a){
        if (a&1) res*=x;
        x*=x; a>>=1;
    }
    return res.m13;
}
int getans(){
    tot1%=Mod; tot2%=Mod; tot3%=Mod;
    tot4%=Mod; tot5%=Mod;
    a=tot4+tot5; b=tot3; c=tot2;
    x=tot3+tot5; y=tot2+tot4;
    a=a*inv%Mod; b=b*inv%Mod; c=c*inv%Mod;    //f[i][u]=a*f[i-1][u]+b*g[i-1][u]+c
    x=x*inv%Mod; y=y*inv%Mod;                //g[i][u]=x*g[i-1][u]+y
    return qpow();
}
void calc(int l,int r,int fa_l,int fa_r){
    tot1=seg_cnt-1ll*l*(n-r+1)-(getcnt(l-1)+getcnt(n-r));
    if (l==fa_l) tot2=1ll*l*(fa_r-r);
    else tot2=1ll*(l-fa_l)*(n-r+1);
    if (l==fa_l) tot3=getsum(n-fa_r+1,n-(r+1)+1);
    else tot3=getsum(fa_l,l-1);
    tot4=1ll*l*(n-r+1)-tot2;
    tot5=getcnt(l-1)+getcnt(n-r)-tot3;
    ans=(ans+getans())%Mod;
}
void solve(int l,int r){
    if (l==r) return;
    int mid;
    scanf("%d",&mid);
    solve(l,mid); solve(mid+1,r);
    calc(l,mid,l,r); calc(mid+1,r,l,r);
}
int main(){
    scanf("%d%d",&n,&k);
    seg_cnt=getcnt(n); inv=getinv(seg_cnt);
    tot1=seg_cnt-1; tot2=1; tot3=0; tot4=0; tot5=0; ans=getans();
    solve(1,n);
    printf("%d\n",ans);
    return 0;
}

Day1T3

  • 首先有一个结论:如果只有第一种操作,那么答案为 \(\frac{\sum\mid a_i-a_{i-1}\mid}{2}\)(NOIP2018D1T1)。在序列最前面和最后面都添2个0。
  • 然后,我们设 \(b_i\) 为第 \(i\) 个位置做了几次 \(2/3\) 操作,\(c_i\) 为第 \(i\) 个位置做了几次 \(1\) 操作。同样在序列最前面和最后面都添2个0。显然,\(a_i=b_i+c_i\)
  • 那么,我们可以发现:若 \(b_{i-2}+c_{i-1}\geqslant a_i\),那么 \(b_{i-2}\geqslant b_i,c_{i-1}\geqslant c_i\)。反之亦然。
  • 证明可以考虑:假设 \(b_{i-2}> b_i,c_{i-1}<c_i\),那么将 \(b_i--,c_i++\) 答案不会变劣,其它情况类似。
  • 那么我们可以考虑贪心:
    • 从左到右考虑每个位置 \(i\)。考虑 \(i\) 的时候确定 \(i-1\)
    • 首先,我们先使 \(b_i=b_{i-2},c_i=c_{i-1}\)
    • 我们记 \(res[i]\) 表示当前的贪心得到的 \(b_i+c_i\)\(a_i\) 的差值。(即 \(a_i-(b_i+c_i)\))。
    • 此时,我们要确定 \(i-1\)
    • \(res[i]>0,res[i-1]>0\),那么,我们可以将 \(c_i++,c_{i-1}++\)。那么 \(res[i]\)\(res[i-1]\)\(--\)
    • \(res[i]<0,res[i-1]<0\),那么,我们可以将 \(c_i--,c_{i-1}--\)。那么 \(res[i]\)\(res[i-1]\)\(++\)
    • 否则,我们可以将 \(c_{i-1}+=res[i-1]\)。同时 \(res[i-1]\) 清零。
    • 需要注意,如果 \(c_i<0\)\(c_{i+1}<0\),要强制变为 \(0\)
  • 最后得到的 \(b,c\) 即为最优解。证明可以考虑每一步的最优性,对答案的影响。
#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
int T,n,a[110000],b[110000],c[110000];
ll ans;
inline int getres(int x){ return a[x]-(b[x]+c[x]);}
inline int myabs(int x){ return x>0?x:-x;}
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d",&n); n+=4;
        for (int i=3;i<=n-2;i++) scanf("%d",&a[i]);
        b[1]=0; c[1]=0; b[2]=0; c[2]=0;
        int res1,res2,tmp;
        for (int i=3;i<=n;i++){
            b[i]=b[i-2]; c[i]=c[i-1];
            res1=getres(i-1); res2=getres(i);
            if (1ll*res1*res2>0){
                if (res1>0) tmp=min(res1,res2);
                else tmp=max(res1,res2);
                c[i-1]+=tmp; c[i]+=tmp;
                res1=getres(i-1); res2=getres(i);
            }
            b[i-1]+=res1; res1=getres(i-1);
            if (b[i-1]<0){
                tmp=-b[i-1];
                b[i-1]+=tmp;
                c[i-1]-=tmp; c[i]-=tmp;
                res2=getres(i);                
            }
            if (c[i-1]<0){
                tmp=-c[i-1];
                c[i-1]+=tmp; c[i]+=tmp;
                b[i-1]-=tmp;
                res2=getres(i);
            }
        }
        b[1]=0; b[2]=0; c[1]=0; c[2]=0;
        b[n-1]=0; b[n]=0; c[n-1]=0; c[n]=0;
        ans=0;
        for (int i=3;i<=n;i++) ans+=myabs(b[i]-b[i-2]);
        for (int i=2;i<=n;i++) ans+=myabs(c[i]-c[i-1]);
        printf("%lld\n",ans/2);
    }
    return 0;
}

Day2T1

  • 现在只会 \(30pts\)
  • 先考虑树的做法,以 \(1\) 号点为根考虑。
  • 首先每棵子树都是独立的,就是 \(Alice\) 往任何一棵子树走,和另外的子树没有关系。
  • \(g[i]\) 表示 \(i\) 的子树内,离 \(i\) 距离最近的在 \(S\) 中的点的距离,\(f[i]\) 表示初始时 \(Alice\)\(i\)\(Bob\) 至少需要先额外行动多少个回合才能获胜(不可能获胜则为 \(INF\))。
  • 那么 \(g[u]=\min_{v\in son[u]}g[v]+len(u,v)\)\(f[u]=\sum_{v\in son[u]}\max\{\min\{g[v]+1,f[v]\}-len(u,v)\}\)。(\(+1\) 是因为 \(Alice\) 先手,它要先到必须比 \(Alice\) 快一步)。
  • 所有在 \(S\) 中的点,\(g\)\(0\)\(f\) 也为 \(0\)(这个 \(f=0\) 可能不严谨,但是只要不对它父亲的 \(f\) 有贡献就可以了)。
  • 所有在 \(T\) 中的点,\(f\)\(INF\)\(Bob\) 不可能赢)。
  • 这样我们可以发现,能得 \(25pts\),分别是 \(1,2,10,11,12\)。其中 \(10,11,12\) 把一个 \(S\) 中的点拆开分成两个点,它就是一棵树了。在实现的时候不用显式拆开,因为这样的图对 \(dp\) 过程其实是没有影响的。
  • 剩下的 \(5pts\),是 \(15\)\(|T|=1\)。我们可以发现,实际上就是 \(Bob\)\(T\) 中不经过 \(1\) 号点的距离比 \(Alice\) 严格小就可以了,只需要输出 \(\max\{0,\min\{dist(s_i,t,\texttt{不经过1号点})\}-dist(1,t)+1\}\) 即可。
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
typedef pair<ll,int> pii;
const ll INF=1ll<<60;
int T,n,m,s,t;
ll f[510],g[510]; bool is_s[510],is_t[510];
int edgenum,vet[1100],val[1100],Next[1100],Head[510];
void addedge(int u,int v,int cost){
    vet[++edgenum]=v; val[edgenum]=cost;
    Next[edgenum]=Head[u]; Head[u]=edgenum;
}
void dfs(int u,int fa){
    int v;
    if (is_s[u]){ g[u]=0; return;}
    g[u]=INF;
    for (int e=Head[u];e;e=Next[e]){
        v=vet[e];
        if (v!=fa){
            dfs(v,u);
            g[u]=min(g[u],g[v]+val[e]);
        }
    }
}
void dfs2(int u,int fa){
    int v; 
    if (is_s[u]){ f[u]=0; return;}
    if (is_t[u]){ f[u]=INF; return;}
    ll sum=0;
    for (int e=Head[u];e;e=Next[e]){
        v=vet[e];
        if (v!=fa){
            dfs2(v,u);
            if (sum!=INF){
                if (f[vet[e]]==INF&&g[vet[e]]==INF) sum=INF;
                else sum+=max(min(g[vet[e]]+1,f[vet[e]])-val[e],0ll);
            }
        }
    }
    f[u]=sum;
}
ll dis[510]; bool vis[510];
priority_queue<pii,vector<pii>,greater<pii> > que;
void dijkstra(int s){
    for (int i=1;i<=n;i++) dis[i]=INF,vis[i]=false;
    que.push(pii(0,s));
    dis[s]=0; vis[1]=true; int u;
    while (!que.empty()){
        u=que.top().second; que.pop();
        if (vis[u]) continue;
        vis[u]=true;
        for (int e=Head[u];e;e=Next[e])
            if (dis[vet[e]]>dis[u]+val[e]){
                dis[vet[e]]=dis[u]+val[e];
                que.push(pii(dis[vet[e]],vet[e]));
            }
    }
} 
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d%d%d%d",&n,&m,&s,&t);
        edgenum=0; for (int i=1;i<=n;i++) Head[i]=0;
        int u,v,c;
        for (int i=1;i<=m;i++){
            scanf("%d%d%d",&u,&v,&c); c++;
            addedge(u,v,c); addedge(v,u,c);
        }
        for (int i=1;i<=n;i++) is_s[i]=false;
        for (int i=1;i<=n;i++) is_t[i]=false;
        for (int i=1;i<=s;i++) scanf("%d",&u),is_s[u]=true;
        for (int i=1;i<=t;i++) scanf("%d",&u),is_t[u]=true;
        if (t==1){
            int x;
            for (int i=1;i<=n;i++)
                if (is_t[i]) x=i;
            dijkstra(x);
            ll ans=INF;
            for (int i=1;i<=n;i++)
                if (is_s[i]) ans=min(ans,dis[i]);
            printf("%lld\n",min(1000000ll,max(ans-dis[1]+1,0ll)));
        } else{
            dfs(1,0); dfs2(1,0);
            printf("%lld\n",min(1000000ll,f[1]));
        }
    }
    return 0;
} 

Day2T2

  • 前置知识: \(min-max\) 容斥(期望形式):\(E(\min(S))=\sum_{T \subseteq S}(-1)^{|T|+1}E(\max(T))\)\(\min(S)=\sum_{T \subseteq S}(-1)^{|T|+1}\max(T)\)。本题中的 \(E(\min(S))\) 表示 \(S\) 中至少有一段长度为 \(k\) 的连续数字都出现的期望时间,\(E(\max(T))\) 表示 \(T\) 中每一段长度为 \(k\) 的连续数字都出现的期望时间( \(S,T\) 为段的集合)。
  • \(70pts\)(细节多,而且做复杂了,其实 \(0/1\) 可以直接记在 \(dp\) 值里)
  • \(dp[i][j][0/1]\) 表示前 \(i\) 个数,选了 \(j\) 个数,集合大小奇偶性 \(0/1\) 的方案数(其中第 \(i\) 个数必须选),然后暴力 \(dp\) ,枚举上一个选了的数,时间复杂度 \(O(kn^2)\)
  • 经过思考,由于值域是 \(1\cdots 2m\) ,可以设 \(dp[i][j][0/1]\) 表示当前值为 \(i\) 的方案数,然后对于每个 \(i\) 都计算(不管有没有在 \(a\) 中出现过),在转移的时候再判断,没有出现的位置就不要转移过来。
  • 初始时,\(dp[i][K][1]=1\)
for (int j=1;j<=m;j++)
for (int k=0;k<=1;k++){
    for (int t=1;t<i;t++)
        if (max(j-K,t-(i-j))>=0)
            dp[i][j][k]=(dp[i][j][k]+dp[t][max(j-K,t-(i-j))][k^1])%Mod;
/*
    for (int t=1;t<i-1;t++)
        if (max(j-1-K,t-(i-j))>=0)
            dp[i-1][j-1][k]=(dp[i-1][j-1][k]+dp[t][max(j-1-K,t-(i-j))][k^1])%Mod;
        */
}
  • 考虑 \(dp[i][j][k]\)\(dp[i-1][j][k]\),相差的是一段前缀(上界不同)和 \(dp[i-1][j-1][k\oplus 1]\),然后一个前缀和即可,但是实际上细节也不少(前缀和要把在 \(a\) 中出现过的更新进去,并且还要考虑 \(j-1==K\ and \ k==0\)的情况),也调了不少时间,代码(\(70pts\)):
#include<cstdio>
#include<algorithm>
using namespace std;
const int Mod=998244353;
int m,K,a[210000],len[210000];
int dp[2][5100][2],sum[10005][5005][2];
int ans,Ans[210000];
bool vis[410000];
int getinv(int x){
    if (x==1) return 1;
    return 1ll*(Mod-Mod/x)*getinv(Mod%x)%Mod;
}
inline int getnum(int x,int y,int z){
    if (x<0) return 0;
    return (sum[x][y+1][z]-sum[x][y][z]+Mod)%Mod;
}
int main(){
    scanf("%d%d",&m,&K);
    for (int i=1;i<=m;i++) scanf("%d",&a[i]);
    sort(a+1,a+m+1);
    for (int i=m;i>=1;i--){
        if (a[i+1]==a[i]+1) len[i]=len[i+1];
        len[i]++;
    }
    for (int i=1;i<=m;i++)
        if (len[i]>=K) vis[a[i]]=true;
    for (int j=1;j<=m;j++) Ans[j]=(Ans[j-1]+1ll*m*getinv(j))%Mod;
    if (vis[1]) sum[1][K][1]=1;
    int now=0;
    for (int i=2;i<=2*m;i++,now^=1){
        for (int j=1;j<=m;j++)
            for (int k=0;k<=1;k++)
                dp[now][j][k]=(dp[now^1][j-1][k]+getnum(i-K-1,j-(K+1),k^1))%Mod;
        dp[now^1][K][1]=(dp[now^1][K][1]+1)%Mod;
        if (vis[i-1]){
            for (int j=0;j<=m;j++)
                for (int k=0;k<=1;k++)
                    if (k&1) ans=(ans+1ll*dp[now^1][j][k]*Ans[j])%Mod;
                    else ans=(ans-1ll*dp[now^1][j][k]*Ans[j]%Mod+Mod)%Mod;
    }
        for (int j=1;j<=m;j++)
            for (int k=0;k<=1;k++){
                if (vis[i-1]) dp[now][j][k]=(dp[now][j][k]+dp[now^1][j-1][k^1])%Mod;
                if (vis[i]) sum[i][j][k]=(sum[i-1][j][k]+dp[now][j][k])%Mod;
                else sum[i][j][k]=sum[i-1][j][k];
            }
        if (vis[i]) sum[i][K][1]=(sum[i][K][1]+1)%Mod;
    }
    if (vis[2*m]){
        dp[now][K][1]=(dp[now][K][1]+1)%Mod;
        for (int j=0;j<=m;j++)
            for (int k=0;k<=1;k++)
                if (k&1) ans=(ans+1ll*dp[now][j][k]*Ans[j])%Mod;
                else ans=(ans-1ll*dp[now][j][k]*Ans[j]%Mod+Mod)%Mod;
    }
    printf("%d\n",ans);
    return 0;
}
  • \(100pts\)
  • 我们可以发现一些性质:如果说我选了两个 \(len=k\) 的段,它们有交集(1.)或者紧挨着一起(2.),并且第二段的开头和第一段的开头不相邻,那么它们的贡献实际上是没有的。如下图:

img

  • 为啥呢,由于 \(i..i+k-1\) 是连续的,\(i+t..i+t+k-1\) 也是连续的,那么 \(i..i+t+k-1\) 也是连续的(\(t\leq k\))。我们考虑这样:

img

  • 如果我们固定最上面的和最下面的必选,那么相当于固定了 \(min-max\) 容斥中的 \(\max(S)\),那么中间的 \(i+1..i+t-1\) 开头的这些段选或不选对这个 \(\max\) 是没有影响的,影响的只是前面的容斥系数。
  • \(i+1..i+t-1\) 开头的这些段选或不选的容斥系数之和,通过二项式定理,应该为 \(\binom{t-1}0(-1)^3+\binom{t-1}1(-1)^4+\binom{t-1}2(-1)^5+\cdots+\binom{t-1}{t-1}(-1)^{t+2}\),也就等于 \((-1)^3(1-1)^{t-1}=0\)。(每一项系数是 \((-1)^{|T|+1}\)
  • 要注意 \(t=1\) 的时候是不能被抵消的。也就是只有可能是单独的 \(len=k\) 的或开头连续的 \(len=k+1\) 的(也就是两个 \(len=k\) 开头连续的拼起来)。且所有段不能相交,两个 \(len=k\) 的段之间不能连续。
  • 现在考虑连续的 \(len=k+1\) 的段和 \(len=k\) 的段/ \(len=k\) 的段和 \(len=k+1\) 的段有没有贡献,这样的话相当于求长度为 \(2k+1\) 的会不会全被抵消:

img

  • 显然,我们发现它的贡献并不能被消掉,因为 \(i+k\) 这个数它是必须被选的,也就是右端点在 \(i+k..i+2k-1\) 之间的段必须选择一个。
  • 如果这些段都可以选或不选,通过二项式定理,那么贡献应该为 \(0\)
  • 但是必须选一个的话,那么 \(\binom k0(-1)^3\) 这一项消失了,那么贡献应该为 \(1\)。也就是我们可以强制钦定 \(k+1/k\)\(k/k+1\)\(k+1/k\) 有贡献。
  • 再考虑两个连续的 \(len=k+1\) 的段有没有贡献。同样的:

img

  • 情况1:右端点在 \(i+k+1..i+2k-1\) 之间的段必须选择一个。情况2:右端点在 \(i+k\)\(i+2k\) 都选了。

  • 两种情况满足其一才能算入答案。若均不满足,也就是 \(i+k+1..i+2k-1\) 都没选, \(i+k\)\(i+2k\) 只选了一个或都没选。

  • 如果这些段都可以选或不选,通过二项式定理,那么贡献应该为 \(0\)。全不满足的情况:

    1. \(i+k..i+2k\) 都没选,其它都选了,贡献 \((-1)^3=-1\)
    2. \(i+k..i+2k-1\) 都没选,其它都选了,贡献 \((-1)^4=1\)
    3. \(i+k+1..i+2k\) 都没选,其它都选了,贡献 \((-1)^4=1\)
  • 故总贡献即为 \(-1\),也就是 \((-1)^5\)。(而 \(k+1\) 本来就是长度为 \(k\)\(4\) 个段,贡献即为 \(-1\)

  • 所以现在可以转化为,只选 \(len=k/k+1\) 的段,其中 \(len_1=k,len_2=k/k+1\) 的两个段不能的开头不能相邻,所有的段不能相交,问方案数。

  • 我们可以在原序列中选出一些不相交的长为 \(k+1\) 的段,然后考虑段中最后一个位置选/不选。可以发现,这样选出来不会出现 \(len_1=k,len_2=k/k+1\) 的开头相邻的两个段。记长度为 \(n\) 的连续段,这个算出来的生成函数为 \(g_n(x)\)

  • 但是有个问题,如果我选择了 \(n-k+1..n\) 这个长度为 \(k\) 的段,它不能通过 \(n-k+1..n+1\) 这个段然后不选 \(n+1\) 来得到,因为 \(n+1\) 越界了。但是我们会发现,如果选了这个段之后,就必定不能选 \(n-2k+1..n-k\) 这个长度为 \(k\) 的段了,这个的生成函数恰好为 \(-x^kg_{n-k}(x)\)

  • \(f_n(x)\) 表示长度为 \(n\) 的连续段的答案生成函数。有

\[f_n(x)=g_n(x)-x^kg_{n-k}(x)\\ g_n(x)=\sum_{i=0}^{\lfloor \frac n{k+1}\rfloor} \binom{n-ik}{i} (x^{k+1}-x^k)^i \]

  • 暴力计算时间复杂度为 \(\sum\limits_{i=0}^{\lfloor \frac n{k+1}\rfloor}i=O(\frac{n^2}{k^2})\)。然后它就水过了,水过了。。。。
  • 当然,算 \(g_n\) 的这个可以优化,我们考虑通过分治来计算。先令 \(val_i=\binom{n-ik}{i}\),则有 \(ans(l,r)=\sum\limits_{i=l}^r val_i (x^{k+1}-x^k)^i=ans(l,mid)+ans(mid+1,r)\times(x^{k+1}-x^k)^{mid-l+1}\),其中 \((x^{k+1}-x^k)^y=(x-1)^yx^{ky}\),而计算 \((x-1)^y\) 的时间复杂度为 \(O(y)\),故这个分治的总时间复杂度为 \(T(l)=2T(\frac l2)+O(lk \log lk)\),而这里的 \(l\) 为原先的 \(\lfloor \frac n{k+1}\rfloor\),把 \(k\) 提出来可得时间复杂度为 \(O(k\times \frac nk\log^2 \frac nk+k\times\frac nk\log \frac nk\log k)\)。设 \(n,k\) 同阶,故总时间复杂度为 \(O(n \log^2 n)\)
  • 最后把这些生成函数乘起来,可以用分治 \(NTT\),我这里用了一个类似于合并果子的东西,每次取次数最小的两个多项式乘起来。故总时间复杂度为 \(O(m\log^2 m)\)
#include<cstdio>
#include<vector>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
typedef vector<int> vec;
typedef pair<int,int> pii;
const int Mod=998244353;
const int G=3; const int invG=(Mod+1)/3;
int m,k,a[210000],Ans[210000];
int fac[210000],inv[210000],invfac[210000];
vec poly[210000],v; int cnt;
priority_queue<pii,vector<pii>,greater<pii> > que;
inline int add(int x,int y){ return x+y>=Mod?x+y-Mod:x+y;}
inline int dec(int x,int y){ return x-y<0?x-y+Mod:x-y;}
inline int mul(int x,int y){ return 1ll*x*y%Mod;}
char Getchar(){
    static char now[1<<20],*S,*T;
    if (T==S){
        T=(S=now)+fread(now,1,1<<20,stdin);
        if (T==S) return EOF;
    }
    return *S++;
}
int read(){
    int x=0,f=1;
    char ch=Getchar();
    while (ch<'0'||ch>'9'){
        if (ch=='-') f=-1;
        ch=Getchar();
    }
    while (ch<='9'&&ch>='0') x=x*10+ch-'0',ch=Getchar();
    return x*f;
}
ll qpow(ll x,ll a){
    ll res=1;
    while (a){
        if (a&1) res=res*x%Mod;
        x=x*x%Mod; a>>=1;
    }
    return res;
}
inline ll getinv(int x){ return qpow(x,Mod-2);}
int rev[1100000];
int GPow[2][19][1100000];
void initG(){
    for (int p=1;p<=18;p++){
        int buf1=qpow(G,(Mod-1)/(1<<p));
        int buf0=qpow(invG,(Mod-1)/(1<<p));
        GPow[1][p][0]=GPow[0][p][0]=1;
        for (int i=1;i<(1<<p);i++){
            GPow[1][p][i]=mul(GPow[1][p][i-1],buf1);
            GPow[0][p][i]=mul(GPow[0][p][i-1],buf0);
        }
    }
}
void NTT(vec &a,int len,int inv){
    a.resize(len);
    for (int i=0;i<len;i++)
        if (i<rev[i]) swap(a[i],a[rev[i]]);
    for (int l=2,cnt=1;l<=len;l<<=1,cnt++){
        int m=l>>1;
        for (int i=0;i<len;i+=l){
            int *buf=GPow[inv][cnt];
            for (int j=0;j<m;j++,buf++) {
                int x=a[i+j],y=1ll*(*buf)*a[i+j+m]%Mod;
                a[i+j]=add(x,y),a[i+j+m]=dec(x,y);
            }
        }
    }
    if (inv!=1){
        ll inv=getinv(len);
        for (int i=0;i<len;i++) a[i]=mul(a[i],inv);
    }
}
void mult(vec &a,vec &b){
	int n=(int)a.size()+(int)b.size()-1;
    int bit=0; while ((1<<bit)<n) bit++;
    int len=1<<bit;
    for (int i=0;i<len;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    NTT(a,len,1); NTT(b,len,1);
    a.resize(len);
    for (int i=0;i<len;i++) a[i]=mul(a[i],b[i]);
    NTT(a,len,0);
}

vec tmp3;
int val[210000];
inline int C(int x,int y){
	if (x<y) return 0;
	return mul(fac[x],mul(invfac[y],invfac[x-y]));
}
vec solve(int l,int r){
	if (l==r){
		vec tmp;
		tmp.resize(1); tmp[0]=val[l];
		return tmp;
	}
	int mid=(l+r)>>1;
	vec tmp1=solve(l,mid),tmp2=solve(mid+1,r);
	int len=mid-l+1,t=k*len;
	tmp3.clear(); tmp3.resize(t+len+1);
	for (int i=0;i<=len;i++)
		if ((len-i)&1) tmp3[i+t]=dec(0,C(len,i));
		else tmp3[i+t]=C(len,i);
	mult(tmp3,tmp2);
	tmp3.resize(max(tmp3.size(),tmp1.size()));
	for (int i=0;i<(int)tmp1.size();i++) tmp3[i]=add(tmp3[i],tmp1[i]);
	return tmp3;
}
vec v1,v2;
void getans(int n){
	if (n<k) return;
	int i,s;
	for (i=0,s=0;s+i<=n;i++,s+=k) val[i+1]=C(n-s,i);
	v1=solve(1,i);
	for (i=0,s=k;s+i<=n;i++,s+=k) val[i+1]=C(n-s,i);
	v2=solve(1,i);
	v1.resize(max(v1.size(),v2.size()+k));
	for (int i=0;i<(int)v2.size();i++) v1[i+k]=dec(v1[i+k],v2[i]);
	while ((int)v1.size()>1&&!v1.back()) v1.pop_back();
	poly[++cnt]=v1;
}
int main(){
	m=read(); k=read(); initG();
	fac[0]=1; for (int i=1;i<=m+1;i++) fac[i]=mul(fac[i-1],i);
	inv[1]=1; for (int i=2;i<=m+1;i++) inv[i]=mul((Mod-Mod/i),inv[Mod%i]);
    for (int i=1;i<=m;i++) Ans[i]=add(Ans[i-1],mul(m,inv[i]));
	invfac[0]=1; for (int i=1;i<=m+1;i++) invfac[i]=mul(invfac[i-1],inv[i]);
	for (int i=1;i<=m;i++) a[i]=read();
	sort(a+1,a+m+1);
	for (int i=1,j=0;i<=m;i=j+1){
		j=i;
		while (j<m&&a[j+1]==a[j]+1) j++;
		getans(j-i+1);
	}
	for (int i=1;i<=cnt;i++) que.push(pii((int)poly[i].size(),i));
	while (que.size()>1){
		int x=que.top().second; que.pop();
		int y=que.top().second; que.pop();
		mult(poly[x],poly[y]);
		que.push(pii((int)poly[x].size(),x));
	}
	int x=que.top().second,ans=0;
	for (int i=1;i<(int)poly[x].size()&&i<=m;i++) ans=add(ans,mul(Ans[i],dec(0,poly[x][i])));
	printf("%d\n",ans);
	return 0;
}

Day2T3

  • 现在只会 \(10pts\)
  • 就是直接枚举 \(b_1\),然后算出 \(x\) 代入后面的方程,看 \(b_i\) 符不符合条件,符合就输出结束。
#include<cstdio>
using namespace std;
typedef long long ll;
int T,m,err;
ll p,a[2100],c[2100];
inline ll add(ll x,ll y){return x+y>=p?x+y-p:x+y;}
inline ll dec(ll x,ll y){return x-y<0?x-y+p:x-y;}
ll mul(ll x,ll a){
    ll res=0;
    while (a){
        if (a&1) res=add(res,x);
        x=add(x,x); a>>=1;
    }
    return res;
}
ll qpow(ll x,ll a){
    ll res=1;
    while (a){
        if (a&1) res=mul(res,x);
        x=mul(x,x); a>>=1;
    }
    return res;
}
int main(){
    scanf("%d",&T);
    while (T--){
        scanf("%d%lld%d",&m,&p,&err); err=(err+1)/2;
        for (int i=1;i<=m;i++) scanf("%lld%lld",&a[i],&c[i]);
        ll inva=qpow(a[1],p-2),x,y;
        for (ll t=c[1]-err;t<=c[1]+err;t++){
            x=t;
            if (x<0) x+=p;
            if (x>=p) x-=p;
            x=mul(x,inva);
            bool flag=true;
            for (int i=2;i<=m;i++){
                y=dec(mul(a[i],x),c[i]);
                if (y>=p) y-=p;
                if (y>err&&y<p-err){
                    flag=false;
                    break;
                }
            }
            if (flag){
                printf("%lld\n",x);
                break;
            }
        }
    }
    return 0;
} 
posted @ 2020-07-15 21:38  hydd  阅读(244)  评论(0编辑  收藏  举报