【多校训练】2021HDU多校8

【前言】
没有第七场,因为是本校出题
最后rk66,校3/9

1001. X-liked Counting

【题意】

[ l , r ] [l,r] [l,r]有多少个数字,满足它的某个前缀或后缀能被 x x x整除。

l , r ≤ 1 0 18 , x ≤ 500 l,r\leq 10^{18},x\leq 500 l,r1018,x500

【思路】

直接计算前缀或后缀中至少有一个满足条件的总数不太方便,考虑补集,计算没有一个前缀和后缀满足条件的再用总数减去。

由于前缀和后缀之间有重合,不能直接在头尾两端转移。观察到 x x x很小,不妨先枚举整个数对 x x x取模的值,再从前后任选一个方向转移,并记录当前前后缀对 x x x取模的结果,这样前缀和后缀模 x x x的结果都可以表示出来,主要注意没有前导零的细节。

算总状态数很多,但实际上有用状态很少,剪枝就可以过了。

【参考代码】

#include<bits/stdc++.h>
using namespace std;
int T;
long long l,r;
int x;
long long dp[30][1010][2][2];
int a[30];
int p10[30];
long long p9[30];
long long solve(long long n)
{ 
    for(int i=1;i<=19;i++)a[i]=n%10,n/=10;
    long long res=1;
    for(int i=19;i>=1;i--)
    {
        int cnt=0;
        for(int j=0;j<a[i];j++)cnt+=(j!=7);
        res+=p9[i-1]*cnt;
        if(a[i]==7)break;
    }
    for(int i=1;i<=19;i++)
    {
        if(a[i]==7)
        {
            res--;
            break;
        }
    }
    for(int t=1;t<x;t++)
    {
        for(int i=0;i<=19;i++)for(int j=0;j<x;j++)dp[i][j][0][0]=dp[i][j][0][1]=dp[i][j][1][0]=dp[i][j][1][1]=0;
        dp[19][0][1][1]=1;
        for(int i=19;i>=1;i--)
        {
            for(int j=0;j<x;j++)
            {
                for(int k=0;k<2;k++)
                {
                    for(int l=0;l<2;l++)
                    {
                        if(!dp[i][j][k][l])continue;
                        int mx=k?a[i]:9;
                        for(int d=0;d<=mx;d++)
                        {
                            if(d==7)continue;
                            int nj=(j*10+d)%x;
                            if(d || !l)if(!nj || (i>1 && nj*p10[i-1]%x==t))continue;
                            dp[i-1][nj][k&(d==mx)][l&(!d)]+=dp[i][j][k][l];
                        }
                    }
                }
            }
        }
        res-=dp[0][t][0][0]+dp[0][t][1][0];
    }
    return res;
}
int main()
{
    scanf("%d",&T);
    while(T--)
    {
        scanf("%lld%lld%d",&l,&r,&x);
        p10[0]=1%x;p9[0]=1;
        for(int i=1;i<=19;i++)p10[i]=p10[i-1]*10%x,p9[i]=9LL*p9[i-1];
        printf("%lld\n",solve(r)-solve(l-1));
    }
    return 0;
}

1002. Buying Snacks

【题意】

n n n种物品,每个物品有大小两类,大的2元,小的1元。现在要买其中的一些,但是每种物品只能买至多一件。同时,可以将相邻两个编号的物品一起买,使得它们两个总价减去1元。问 ∀ k ∈ [ 1 , m ] \forall k\in [1,m] k[1,m]花费 k k k元买的物品有多少种方案。答案对998244353取模。

n ≤ 1 0 9 , m ≤ 20000 n\leq 10^9,m\leq 20000 n109,m20000

【思路】

f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个物品,一共用了 j j j块的方案数,有如下的转移方程:
f [ i ] [ j ] = f [ i − 1 ] [ j ] + f [ i − 1 ] [ j − 1 ] + f [ i − 1 ] [ j − 2 ] + f [ i − 2 ] [ j − 1 ] + 2 f [ i − 2 ] [ j − 2 ] + f [ i − 2 ] [ j − 3 ] f[i][j]=f[i-1][j]+f[i-1][j-1]+f[i-1][j-2]+f[i-2][j-1]+2f[i-2][j-2]+f[i-2][j-3] f[i][j]=f[i1][j]+f[i1][j1]+f[i1][j2]+f[i2][j1]+2f[i2][j2]+f[i2][j3]
意义显然

g i g_i gi表示 f i f_i fi的生成函数,那么:
g i = ( x 2 + x + 1 ) g i − 1 + ( x 3 + 2 x 2 + x ) g i − 2 g_i=(x^2+x+1)g_{i-1}+(x^3+2x^2+x)g_{i-2} gi=(x2+x+1)gi1+(x3+2x2+x)gi2
众所周知,常系数线性齐次递推可以用矩阵乘法加速,而这个东西同样可以用矩阵乘法做。

复杂度 O ( 8 ⋅ m log ⁡ m log ⁡ n ) O(8\cdot m\log m\log n) O(8mlogmlogn),初始值 g 0 = 1 , g 1 = x 2 + x + 1 g_0=1,g_1=x^2+x+1 g0=1,g1=x2+x+1

这个复杂度有点卡,所以需要比较优秀的NTT板子,或者在快速幂的时候不进行idft。

然后众所周知,常系数线性齐次递推还有一种BM+CH的做法,这个常数会小很多。

然后其实别的队推导后(打表后)发现答案是 ∑ ( − 1 ) i ( 2 n − 2 i k − i ) \sum (-1)^i \binom{2n-2i}{k-i} (1)i(ki2n2i),所以 O ( m 2 ) O(m^2) O(m2)就能过。

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret=0;bool f=0;char c=getchar();
    while(!isdigit(c)){if(c==EOF)return -1;if(c=='-')f=1;c=getchar();}
    while(isdigit(c))ret=(ret<<3)+(ret<<1)+(c^48),c=getchar();
    return f?-ret:ret;
}
const int maxn=7e4+5,mod=998244353;
ll inv[maxn],fac[maxn],ifac[maxn];
inline ll add(ll x,ll y){x+=y;return x%mod;}
inline ll mul(ll x,ll y){return (ll)x*y%mod;}
inline ll ksm(ll x,ll k){ll ans=1;for(;k;k>>=1,x=x*x%mod)if(k&1)(ans*=x)%=mod;return ans;}
inline void init_inv(int n){inv[1]=1;rep(i,2,n) inv[i]=mul(mod-mod/i,inv[mod%i]);}
inline void init_fac(int n){fac[0]=ifac[0]=1;
    rep(i,1,n) fac[i]=mul(fac[i-1],i),ifac[i]=mul(ifac[i-1],inv[i]);
}
struct FT{
    int n,nn; ll w[2][maxn],rev[maxn],tmp;
    inline int Init(int _n){
        for(n=1;n<=_n;n<<=1); if(n==nn) return n;nn=n;
        ll w0=ksm(3,(mod-1)/n);
        w[0][0]=w[1][0]=1;
        rep(i,1,n-1) w[0][i]=w[1][n-i]=mul(w[0][i-1],w0);
        rep(i,0,n-1) rev[i]=(rev[i>>1]>>1)|((i&1)*(n>>1));
        return n;
    }
    void FFT(ll a[],int op){
        rep(i,0,n-1) if(i<rev[i]) swap(a[i],a[rev[i]]);
        for(int i=1;i<n;i<<=1){
            for(int j=0,t=n/(i<<1);j<n;j+=(i<<1)){
                for(int k=j,l=0;k<j+i;k++,l+=t){
                    ll x=a[k],y=mul(w[op][l],a[k+i]);
                    a[k]=add(x,y);a[k+i]=add(x-y,mod);
                }
            }
        }
        if(op) {tmp=inv[n];rep(i,0,n) a[i]=mul(a[i],tmp);}
    }
}ft;
int n,m,len;
ll c[maxn],d[maxn],tp[maxn];
void print(ll *x){cout<<"(";rep(i,0,m-1) cout<<x[i]<<",)"[i==m-1];}
void mul(ll *a,ll *b,ll *c){
    rep(i,0,m-1) c[i]=a[i],d[i]=b[i];
    rep(i,m,len) c[i]=d[i]=0;

    ft.FFT(c,0);ft.FFT(d,0);
    rep(i,0,len-1) c[i]=mul(c[i],d[i]);
    ft.FFT(c,1);
    
    rep(i,m,len) c[i]=0;
}
ll res[2][2][maxn],ans[2][2][maxn],tmp[2][2][maxn];
ll v[2][maxn],X[maxn],Y[maxn];
void KSM(int p){
    rep(i,0,1)rep(j,0,1){
        rep(k,0,len) ans[i][j][k]=0;
        if(i==j) ans[i][j][0]=1;
    }
    for(;p;p>>=1){
        if(p&1){
            rep(i,0,1)rep(j,0,1){
                rep(k,0,len) tmp[i][j][k]=0;
                rep(k,0,1){
                    mul(res[i][k],ans[k][j],tp);
                    rep(t,0,m-1) tmp[i][j][t]=add(tmp[i][j][t],tp[t]);
                }
            }
            rep(i,0,1)rep(j,0,1)rep(t,0,m-1) ans[i][j][t]=tmp[i][j][t];
        }
        rep(i,0,1)rep(j,0,1){
            rep(k,0,len) tmp[i][j][k]=0;
            rep(k,0,1){
                mul(res[i][k],res[k][j],tp);
                rep(t,0,m-1) tmp[i][j][t]=add(tmp[i][j][t],tp[t]);
            }
        }
        rep(i,0,1)rep(j,0,1)rep(t,0,m-1) res[i][j][t]=tmp[i][j][t];
    }
}
int main(){
    freopen("data.in","r",stdin);
    freopen("my.out","w",stdout);

    init_inv(maxn-5);init_fac(maxn-5);
    dwn(_,yh(),1){
        n=yh(),m=yh()+1;
        len=ft.Init(2*m-1);
        rep(i,0,1)rep(j,0,1){
            rep(t,0,len) ans[i][j][t]=res[i][j][t]=0;
        }
        rep(i,0,1)rep(t,0,len) v[i][t]=0;
        res[0][0][0]=1;res[0][0][1]=1;res[0][0][2]=1;
        res[0][1][1]=1;res[0][1][2]=2;res[0][1][3]=1;
        res[1][0][0]=1;

        v[0][0]=1;v[0][1]=1;v[0][2]=1;
        v[1][0]=1;

        KSM(n-1);
        mul(ans[0][0],v[0],X);mul(ans[0][1],v[1],Y);
        rep(i,1,m-1){
            printf("%lld ",add(X[i],Y[i]));
        }
        putchar(10);
    }
    cerr<<clock();
    return 0;
}
1003. Ink on paper

【题目】

n n n片墨水,初始位置在 ( x i , y i ) (x_i,y_i) (xi,yi),初始半径可以看作0,每秒半径增加0.5,问多久这些墨水连成一片。

n ≤ 5000 n\leq 5000 n5000

【思路】

显然就是完全图的MST,用没有堆优化的Prim即可。

复杂度 O ( n 2 ) O(n^2) O(n2)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;

const ll inf=(ll)3e18;
const int N=5005;
int n;
int vis[N],a[N],b[N];
ll dis[N][N],nowdis[N];

int read()
{
    int ret=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
    while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
    return f?ret:-ret;
}

/*struct node
{
    int v;ll w;
    node(int v=0,ll w=0):v(v),w(w){}
    bool operator < (const node&rhs)const
    {
        return w>rhs.w;
    }
};
priority_queue<node>q;*/

ll prim()
{
    //int cnt=0;
    ll ans=0;
   // while(!q.empty()) q.pop();
    for(int i=1;i<=n;++i) vis[i]=0;

    vis[1]=1;
    for(int i=2;i<=n;++i) 
        nowdis[i]=dis[1][i];
    for(int i=1;i<n;++i)
    {
        ll now=inf;int id=0;
        for(int j=1;j<=n;++j) if(!vis[j])        
        {
            //printf("%d %lld %lld\n",j,now,nowdis[j]);
            if(now>nowdis[j]) now=nowdis[j],id=j;
            //printf("%d %lld\n",j,nowdis[j]);
        }
        vis[id]=1;
        //printf("!!%d\n",id);
        for(int j=1;j<=n;++j) if(!vis[j])
            nowdis[j]=min(nowdis[j],dis[id][j]);
        ans=max(ans,now);
    }

   /* q.push(node(1,0));
    while(!q.empty() && cnt<=n)
    {
        node x=q.top();q.pop();
        if(vis[x.v]) continue;
        vis[x.v]=1;ans=max(ans,x.w);
        ++cnt;
        for(int i=1;i<=n;++i) if(!vis[i])
            q.push(node(i,dis[x.v][i]));
    }*/
    return ans;
}

ll sqr(ll x){return x*x;}


int main() 
{ 
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d",&n);
        for(int i=1;i<=n;++i)
            scanf("%d%d",&a[i],&b[i]);
        for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j) 
        {
            ll w=sqr(a[i]-a[j])+sqr(b[i]-b[j]);
            dis[i][j]=dis[j][i]=w;
            //e[i].pb(node(j,w));e[j].pb(node(i,w));
        }
        printf("%lld\n",prim());
    }
    return 0; 
}
1004. Counting Stars

【题意】

一个长度为 n n n的序列 a i a_i ai,有三种操作:

  • ∀ i ∈ [ l , r ] \forall i\in[l,r] i[l,r]每个数字减去 a i & ( − a i ) a_i\&(-a_i) ai&(ai)

  • ∀ i ∈ [ l , r ] \forall i \in [l,r] i[l,r],每个数字加上一个 2 popcount ( a i ) 2^{\text{popcount}(a_i)} 2popcount(ai) popcount ( a i ) \text{popcount}(a_i) popcount(ai)表示 a i a_i ai二进制最高位的1是哪一位。

  • 询问区间数字和

n , q ≤ 1 0 5 , a i ≤ 1 0 9 n,q\leq 10^5,a_i\leq 10^9 n,q105,ai109

【思路】

第一个操作相当于去掉最低位的1,第二个操作相当于最高位的1往更高位移动。

用线段树来维护,那么显然对于一个数字我们最多进行 log ⁡ \log log次第一个操作,这部分就是经典的势能分析,直接暴力递归修改。第二个操作相当于给区间加2的最高位的次方和,我们可以通过将最高位和低位分别维护,这个操作可以看成是区间乘2。

复杂度 O ( n log ⁡ A + q log ⁡ n ) O(n\log A+q\log n) O(nlogA+qlogn),反正是一个 log ⁡ \log log

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int ret;scanf("%d",&ret);return ret;
}
const int maxn=1e5+5,mod=998244353;
struct num{int x; int high;}a[maxn];
int sum[maxn<<2];
int lz[maxn<<2],high[maxn<<2];
ll pw[maxn];
#define ls (v<<1)
#define rs (v<<1|1)
#define mid ((l+r)>>1)
void push_up(int v){
    sum[v]=(sum[ls]+sum[rs])%mod;
    high[v]=(high[ls]+high[rs])%mod;
}
void opt(int v,int tms){
    
    sum[v]=(sum[v]+(ll)high[v]*(pw[tms]-1))%mod;
    high[v]=((ll)high[v]*pw[tms]%mod)%mod;

    lz[v]+=tms;
}
void push_down(int v){
    if(lz[v]){
        opt(ls,lz[v]);opt(rs,lz[v]);lz[v]=0;
    }
}
int al,ar;
int ans;
void add(int v,int l,int r){
    if(al<=l&&ar>=r) return opt(v,1);
    push_down(v);
    if(al<=mid) add(ls,l,mid); if(ar>mid) add(rs,mid+1,r);
    push_up(v);
}
void qry(int v,int l,int r){

    if(al<=l&&ar>=r) return ans=(ans+sum[v])%mod,void();
    push_down(v);
    if(al<=mid) qry(ls,l,mid); if(ar>mid) qry(rs,mid+1,r);
}
void clear(int v,int l,int r){
    sum[v]=high[v]=0;if(l==r)return;
    push_down(v);
    clear(ls,l,mid);clear(rs,mid+1,r);
}
void dlt(int v,int l,int r){
    if(high[v]==0) return;
    if(al<=l&&ar>=r&&high[v]==sum[v]) return clear(v,l,r);
    if(l==r){
        if(a[l].x) sum[v]=(sum[v]-(a[l].x&-a[l].x)+mod)%mod,a[l].x-=a[l].x&(-a[l].x);
        else sum[v]=high[v]=a[l].high=0;
        return;
    }
    push_down(v);
    if(al<=mid) dlt(ls,l,mid); if(ar>mid) dlt(rs,mid+1,r);
    push_up(v);
}

void build(int v,int l,int r){
    sum[v]=high[v]=lz[v]=0;
    if(l==r){
        int y=yh(); dwn(i,30,0)if((y>>i)&1){
            a[l].x=(y^(1<<i));
            a[l].high=(1<<i);
            sum[v]=y;  high[v]=(1<<i)%mod;
            break;
        }
        return;
    }
    build(ls,l,mid);build(rs,mid+1,r);
    push_up(v);
}
int n;

int main(){
// #ifdef van
//     freopen("my.in","r",stdin);
// #endif
    pw[0]=1;rep(i,1,maxn-1) pw[i]=(pw[i-1]*2ll)%mod;
    dwn(_,yh(),1){
        n=yh();
        build(1,1,n);
        dwn(m,yh(),1){
            int op=yh(); al=yh(),ar=yh();
            if(op==2)dlt(1,1,n);
            else if(op==3)add(1,1,n);
            else{
                ans=0; qry(1,1,n);
                printf("%d\n",ans);
            }
            // rep(i,1,n){
            //     ans=0; al=ar=i; qry(1,1,n);cout<<ans<<" \n"[i==n];
            // }
        }
    }
    return 0;
}
1005. Separated Number

【题意】

一个长度为 n n n的数字,问把它分成至多 k k k个连续段的所有情况的每段数字和之和。

n , k ≤ 1 0 6 n,k\leq 10^6 n,k106

【思路】

一个很显然的暴力是枚举一个段,然后考虑这个段贡献多少次计算权值。

然后可以发现这个段 [ l , r ] [l,r] [l,r]贡献多少次,事实上是考虑在这个段的左右选择多少个右端点,其中 l − 1 l-1 l1 n n n一定要选,那么这个显然是枚举选择的个数后用组合数计算。然后又可以发现除了开头和结尾的段,长度相等的串贡献次数是相同,于是考虑一个长度怎么计算贡献。

先不管开头结尾串,这个很容易算。

设当前考虑的是 l e n len len,这样前后一共有 r e s = n − l e n − 2 res=n-len-2 res=nlen2个位置可以任选(注意 l − 1 l-1 l1 n n n一定要选),那么总的次数就是:
∑ i = 0 min ⁡ ( k − 3 , r e s ) ( r e s i ) \sum_{i=0}^{\min(k-3,res)}\binom{res}{i} i=0min(k3,res)(ires)
这个组合数在 r e s ≤ k − 3 res\leq k-3 resk3就是杨辉三角的一层,答案是 2 r e s 2^{res} 2res

而当 r e s > k − 3 res>k-3 res>k3,实际上是杨辉三角某层的前缀,这个东西是可以从 2 r e s 2^{res} 2res开始递推的:设当前层权值为 n o w now now,那么下一层就是 2 × n o w − ( r e s K − 3 ) 2\times now-\binom{res}{K-3} 2×now(K3res)(即考虑每个数对下一层贡献两次,然后第 K − 2 K-2 K2个数是贡献1次)

开头和结尾的串贡献类似,这里不复述了。

最后一个问题是处理出每个长度的串权值和,这个东西就求出前缀数字和,然后从 s u m [ 1 ] sum[1] sum[1]开始简单递推就行。

复杂度 O ( n ) O(n) O(n)

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;

const int N=1e6+10,mod=998244353;

namespace Math
{
    int fac[N],ifac[N],inv[N],bas[N],pw[N];
    int upm(int x){return x>=mod?x-mod:(x<0?x+mod:x);}
    void up(int &x,int y){x=upm(x+y);}
    int mul(int x,int y){return 1ll*x*y%mod;}
    int qpow(int x,int y)
    {
        int res=1;
        for(;y;y>>=1,x=mul(x,x)) if(y&1) res=mul(res,x);
        return res;
    }
    int C(int x,int y){if(y>x)return 0;return mul(fac[x],mul(ifac[x-y],ifac[y]));}
    void initsth()
    {
        fac[0]=1;bas[0]=1;pw[0]=1;
        for(int i=1;i<N;++i)
        {
            fac[i]=mul(fac[i-1],i);
            bas[i]=mul(bas[i-1],10);
            pw[i]=mul(pw[i-1],2);
        }
        ifac[N-1]=qpow(fac[N-1],mod-2);
        for(int i=N-2;~i;--i)ifac[i]=mul(ifac[i+1],i+1);
        inv[0]=1;for(int i=1;i<N;++i) inv[i]=mul(ifac[i],fac[i-1]);
    }
}
using namespace Math;

int n,K;
int pre[N],suf[N];
int sum[N],ss[N];
char s[N];

int main() 
{ 
    initsth();
    int T;scanf("%d",&T);
    while(T--)
    {
        scanf("%d%s",&K,s+1);n=strlen(s+1);

        for(int i=0;i<=n;++i) sum[i]=ss[i]=pre[i]=suf[i]=0;

        for(int i=1;i<=n;++i) sum[1]+=s[i]-'0',ss[i]=ss[i-1]+s[i]-'0';
        
        for(int i=1,now=0;i<=n;++i)
        {
            now=(mul(now,10)+(s[i]-'0'))%mod;
            pre[i]=now;
        }
        for(int i=n,now=0;i>=1;--i)
        {
            now=upm(now+mul(bas[n-i],s[i]-'0'));
            suf[n-i+1]=now;
        }
        for(int len=2;len<=n;++len)
        {
            sum[len]=upm(sum[len-1]+mul(bas[len-1],ss[n-len+1]));
            up(sum[len],mod-pre[len-1]);
        }
        for(int i=1;i<n;++i) up(sum[i],mod-pre[i]),up(sum[i],mod-suf[i]);
        up(sum[n],pre[n]);
        //for(int i=1;i<=n;++i) printf("%d %d\n",i,sum[i]);
        

        int ans=0;
        K=min(n,K);
        if(K>=3)
        {
            int now=pw[K-3];
            for(int len=n;len>=1;--len)
            {
                int res=n-len-2;
                if(res<0) continue;
                if(res<=K-3) up(ans,mul(sum[len],pw[res]));
                else
                {
                    now=mul(now,2);up(now,mod-C(res-1,K-3));
                    up(ans,mul(now,sum[len]));
                }
            }

            now=pw[K-2];
            for(int i=n;i>=1;--i)
            {
                int res=n-i-1;
                if(res<0) continue;
                if(res<=K-2) 
                {
                    up(ans,mul(pre[i],pw[res]));
                    up(ans,mul(suf[i],pw[res]));
                }
                else
                {
                    now=mul(now,2);up(now,mod-C(res-1,K-2));
                    up(ans,mul(pre[i],now));
                    up(ans,mul(suf[i],now));
                }
            }
        }
        else if(K==2)
        {
            for(int i=2;i<=n;++i)
            {
                up(ans,pre[i]);up(ans,suf[i]);
            }
        }
        up(ans,pre[n]);
        printf("%d\n",ans);
    }
    return 0; 
}
1006. GCD Game

【题意】

一个长度为 n n n的序列 a i a_i ai,两个人博弈,每次可以任选一个 a i a_i ai,再任选一个数字 1 ≤ x < a i 1\leq x<a_i 1x<ai,使 a i = gcd ⁡ ( x , a i ) a_i=\gcd(x,a_i) ai=gcd(x,ai),不能操作的人输。问谁赢。

n ≤ 1 0 6 , a i ≤ 1 0 7 n\leq 10^6,a_i\leq 10^7 n106,ai107

【思路】

这个东西和 N i m Nim Nim游戏是本质相同的,一个数的所有素数的次数和等价于一堆石头个数。

复杂度 O ( n log ⁡ n + A ) O(n\log n+A) O(nlogn+A)

【参考代码】

#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a),i##ss=(b);i<=i##ss;i++)
#define dwn(i,a,b) for(int i=(a),i##ss=(b);i>=i##ss;i--)
#define deb(x) cerr<<(#x)<<":"<<(x)<<'\n'
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
#define hvie '\n'
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
int yh(){
    int x;scanf("%d",&x);return x;
}
const int maxn=3e7+555;
int prm[maxn/10],minp[maxn],tot=0;
bool vis[maxn];
int n,a[maxn/10];
void euler(int n){
    vis[1]=1;
    for(int i=2;i<=n;i++){
        if(!vis[i]) prm[++tot]=i,minp[i]=i;
        for(int j=1;j<=tot&&prm[j]*i<=n;j++){
            vis[i*prm[j]]=1;
            minp[i*prm[j]]=min(minp[i],prm[j]);
            if(i%prm[j]==0) break;
        }
    }
}
int fen(int x){
    int ans=0;while(x>1) ans++,x/=minp[x];
    return ans;
}
int main(){
    euler(1e7);
    dwn(_,yh(),1){
        ll ans=0;
        n=yh();rep(i,1,n){
            int x=yh();
            ans^=fen(x);
        }
        puts(ans?"Alice":"Bob");
    }
    return 0;
}
1007. A Simple Problem

【题意】

一个长度为 n n n的全0序列 a i a_i ai,一个正数 k k k,有 q q q个操作:

  • 区间加某个数
  • 求区间所有长度为 k k k的子区间最大值的最小值

k , n ≤ 5 × 1 0 8 , q ≤ 1 0 5 k,n\leq 5\times10^8,q\leq 10^5 k,n5×108,q105

【思路】

用线段树维护区间加,区间 max ⁡ \max max
考虑统计在两段之间的答案,前一段的后缀 max ⁡ \max max和后一段的前缀 max ⁡ \max max都是单调的。
可以在序列上二分,每次选择一段正好为 k k k的区间,如果前一段的 max ⁡ \max max更大,最优的区间不会在这段区
间的左边,否则不会在这段区间的右边。
直接用这种方法合并答案,即可得到的做法 O ( q log ⁡ 3 n ) O(q\log^3 n) O(qlog3n)
如果能在线段树上二分,就可以做到 O ( q log ⁡ 2 n ) O(q\log^2 n) O(qlog2n)

正解:
对于所有的询问, k k k是定值,将序列按 k k k分段,超过 n n n的部分补至 k k k的倍数。
连续的 k k k个数要么在一段内,要么在两段之间。
对于每一段建一棵线段树,每一段的线段树形态完全一样,二分可以在线段树上做,单次二分的复杂度 O ( log ⁡ n ) O(\log n) O(logn)
为。
每次修改只需要对端点处的 O ( 1 ) O(1) O(1)段区间重新计算答案。
另外建一棵线段树维护 n k \frac n k kn段的答案,需要支持区间加,单点修改,区间查询 min ⁡ \min min
对于修改,中间段做区间加,两端重新计算。
对于询问,中间段查询 min ⁡ \min min,两端不足一个段的部分用二分计算答案

复杂度 O ( q log ⁡ n ) O(q\log n) O(qlogn)

【参考代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
const int N = 111111;
const int M = N*60;
const int inf = 1e9+N;
int n,k,q,d,tot,rt,ls[M],rs[M];
int t[M],w[M],s[M],e[M];
void ch(int u,int x){
	w[u]+=x;
	t[u]+=x;
	s[u]+=x;
	e[u]+=x;
}
void modify2(int&u,int L,int R,int X,int l,int r){
	if(!u)
		u=++tot;
	if(L<=l&&r<=R){
		ch(u,X);
		return;
	}
	int h=l+r>>1;
	if(L<=h)
		modify2(ls[u],L,R,X,l,h);
	if(h<R)
		modify2(rs[u],L,R,X,h+1,r);
	t[u]=max(t[ls[u]],t[rs[u]])+w[u];
}
int solve(int u,int l,int r,int cl=0,int cr=k-1){
	int h=l+r>>1,fl;
	int u1=ls[u],l1=l,r1=h,u2=rs[u],l2=h+1,r2=r;
	int w1=w[u1],w2=w[u2],s=inf,o;
	while(l1!=r1){
		u1=rs[u1];
		w1+=w[u1];
		l1=(l1+r1>>1)+1;
	}
	while(l2!=r2){
		u2=ls[u2];
		w2+=w[u2];
		r2=l2+r2>>1;
	}
	l=0,r=k-1;
	o=-inf;
	while(l!=r){
		h=l+r>>1;
		if(cl<=h+1&&h+1<=cr)
			s=min(s,max(o,max(w1+t[rs[u1]],w2+t[ls[u2]])));
		if(cr<h+1)
			fl=0;
		else{
			if(h+1<cl)
				fl=1;
			else
				fl=w1+t[rs[u1]]>=w2+t[ls[u2]];
		}
		if(fl){
			o=max(o,w2+t[ls[u2]]);
			l=h+1;
			u1=rs[u1];
			u2=rs[u2];
		}
		else{
			o=max(o,w1+t[rs[u1]]);
			r=h;
			u1=ls[u1];
			u2=ls[u2];
		}
		w1+=w[u1];
		w2+=w[u2];
	}
	return s;
}
void modify1(int&u,int L,int R,int X,int l=0,int r=d){
	if(!u)
		u=++tot;
	if(L<=l*k&&r*k+k-1<=R){
		ch(u,X);
		return;
	}
	if(l==r){
		modify2(u,L,R,X,l*k,r*k+k-1);
		s[u]=t[u];
		return;
	}
	int h=l+r>>1;
	if(L<=h*k+k-1)
		modify1(ls[u],L,R,X,l,h);
	if((h+1)*k<=R)
		modify1(rs[u],L,R,X,h+1,r);
	t[u]=max(t[ls[u]],t[rs[u]])+w[u];
	if(L<=h*k&&(h+1)*k+k-1<=R)
		e[u]+=X;
	else{
		if(L<=(h+1)*k+k-1||h*k<=R)
			e[u]=solve(u,l,r)+w[u];
	}
	s[u]=min(min(s[ls[u]],s[rs[u]])+w[u],e[u]);
}
int query(int u,int L,int R,int l=0,int r=d){
	if(R<l*k+k-1||r*k<L)
		return inf;
	if(!u)
		return 0;
	if(L<=l*k&&r*k+k-1<=R)
		return s[u];
	if(l==r)
		return inf;
	int h=l+r>>1,o;
	if(R<=h*k+k-1)
		return query(ls[u],L,R,l,h)+w[u];
	if((h+1)*k<=L)
		return query(rs[u],L,R,h+1,r)+w[u];
	if(h*k<L||R<(h+1)*k+k-1)
		o=solve(u,l,r,max(0,L-h*k),min(k-1,R-(h+1)*k+1))+w[u];
	else
		o=e[u];
	return min(o,min(query(ls[u],L,R,l,h),query(rs[u],L,R,h+1,r))+w[u]);
}
int main()
{
	//freopen("input2.txt","r",stdin);
	//freopen("std.out","w",stdout);
	int T,i,l,r,x;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d",&n,&k,&q);
		d=(n-1)/k;
		n=(d+1)*k;
		while(q--){
			scanf("%d%d%d",&i,&l,&r);
			l--,r--;
			if(i==1){
				scanf("%d",&x);
				modify1(rt,l,r,x);
			}
			else{
				printf("%d\n",query(rt,l,r));
			}
		}
		tot=0,rt=0;
		memset(ls,0,sizeof(ls));
		memset(rs,0,sizeof(rs));
		memset(t,0,sizeof(t));
		memset(w,0,sizeof(w));
		memset(s,0,sizeof(s));
		memset(e,0,sizeof(e));
	}
	return 0;
}

1008. Squard Card

【题意】

一个平面内有两个圆 A , B A,B A,B,往平面上随机丢一个长度为 a a a的正方形卡片,如果卡片绕卡片中心旋转任意度数后能完全在一个圆内,则得分。

问在 A A A种得分的情况下在 B B B中也得分的概率

多组数据。

【思路】

考虑得分的面积,那么概率就是 交 的 面 积 A 的 面 积 \frac {交的面积}{A的面积} A,然后这个得分的面积事实上就是一个圆,大概是正方形的一条边作为圆的弦为边界情况,那么可行的范围半径是 r 2 − a 2 4 − a 2 \sqrt{r^2-\frac {a^2} 4}-\frac a 2 r24a2 2a

求圆交就行。

【参考代码】

#include<bits/stdc++.h>
using namespace std;
int t; 
int r1,r2,xa,ya,xb,yb,a;
const double pi=acos(-1.0);
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d%d%d%d%d%d",&r1,&xa,&ya,&r2,&xb,&yb,&a);
        if(sqrt(2.0)*r2<a)
        {
            puts("0.000000");
            continue;
        }
        double ra=sqrt(r1*r1-a*a/4.0)-a/2.0,rb=sqrt(r2*r2-a*a/4.0)-a/2.0;
        double d=sqrt((double)(xa-xb)*(xa-xb)+(ya-yb)*(ya-yb));
        if(ra+rb<=d)
        {
            puts("0.000000");
            continue;
        }
        if(ra>=rb+d)
        {
            printf("%.6f\n",rb*rb/ra/ra);
            continue;
        }
        if(rb>=ra+d)
        {
            puts("1.000000");
            continue;
        }
        double ca=(ra*ra+d*d-rb*rb)/ra/d/2.0,cb=(rb*rb+d*d-ra*ra)/rb/d/2.0;
        double s=acos(ca)*ra*ra+acos(cb)*rb*rb-ra*ra*ca*sqrt(1.0-ca*ca)-rb*rb*cb*sqrt(1.0-cb*cb);
        printf("%.6f\n",s/pi/ra/ra);
    }
    return 0;
}

1009. Singing Superstar

【题意】

给定一个串 S S S,再给 q q q个询问串 t i t_i ti,问 t i t_i ti S S S中不重重叠地出现了多少次。

∣ S ∣ , q ≤ 1 0 5 , ∣ t i ∣ ≤ 30 |S|,q\leq 10^5,|t_i|\leq 30 S,q105,ti30

【思路】

这里有一个关键是 t i t_i ti很小,所以一个做法是将 S S S所有长度不超过30的串都用哈希求出答案即可。

复杂度 O ( 30 ⋅ ∣ S ∣ + ∑ ∣ t i ∣ ) O(30\cdot |S|+\sum |t_i|) O(30S+ti),可能有个map的log

当然一个更“准确”的做法是,所有询问串建AC自动机,然后用 S S S在自动机上匹配,每次跑到一个节点更新这个节点代表的字符串的答案即可。

复杂度 O ( 串 长 和 ) O(串长和) O()

【参考代码】

hash

#include<bits/stdc++.h>
#define pb push_back
#define mkp make_pair
#define fi first
#define se second
using namespace std;

typedef double db;
typedef long long ll;
typedef pair<int,int> pii;
typedef pair<int,ll> pil;
typedef pair<ll,int> pli;

const int bas1=29,bas2=33,mod1=1e9+9,mod2=1e9+7;
const int N=1e5+10;

int sum1[N],sum2[N],bas11[N],bas22[N];
char s[N];
map<pii,int>mp1,mp2;


pii gethash1(int l,int r)
{
    int s1=0,s2=0;
    s1=((sum1[r]-1ll*sum1[l-1]*bas11[r-l+1]%mod1)+mod1)%mod1;
    s2=((sum2[r]-1ll*sum2[l-1]*bas22[r-l+1]%mod2)+mod2)%mod2;
    //printf("%d %d %d %d\n",l,r,s1)
    return mkp(s1,s2);
}

pii gethash(char *t,int l,int r)
{
    int s1=0,s2=0;
    for(int i=l;i<=r;++i)
    {
        s1=(1ll*s1*bas1+t[i]-'a'+1)%mod1;
        s2=(1ll*s2*bas2+t[i]-'a'+1)%mod2;
    }
    return mkp(s1,s2);
}

int main() 
{ 
    //freopen("data.in","r",stdin);
    //freopen("my.out","w",stdout);
    int T;scanf("%d",&T);
    bas11[0]=bas22[0]=1;
    for(int i=1;i<N;++i) 
    {
        bas11[i]=1ll*bas11[i-1]*bas1%mod1;
        bas22[i]=1ll*bas22[i-1]*bas2%mod2;
    }
    while(T--)
    {
        int n,m;
        scanf("%s",s+1);n=strlen(s+1);
        mp1.clear();mp2.clear();
        for(int i=1;i<=n;++i) 
        {
            sum1[i]=(1ll*sum1[i-1]*bas1+s[i]-'a'+1)%mod1;
            sum2[i]=(1ll*sum2[i-1]*bas2+s[i]-'a'+1)%mod2;
        }
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=30;++j)
            {
                if(i-j+1<1) break;
                pii t=gethash1(i-j+1,i);
                //printf("%d %d %d %d\n",i-j+1,i,t.fi,t.se);
                if(mp1[t]>=i-j+1) continue;
                else mp1[t]=i,mp2[t]++;
                
            }
        }
        scanf("%d",&m);
        while(m--)
        {
            scanf("%s",s+1);n=strlen(s+1);
            pii t=gethash(s,1,n);
            if(!mp2.count(t)) puts("0");
            else printf("%d\n",mp2[t]);
        }
    }
    return 0; 
}

AC自动机

#include<bits/stdc++.h>
using namespace std;
const int maxn = 6e5 + 5;
const int maxm = 1e5 + 4;
char tmp[35];
int n, ans[maxm], last[maxm], flag[maxm], pos[maxm];
char s[maxm];
namespace AC {
int tr[maxn][27], tot;
int fail[maxn], cnt[maxn], dep[maxn];
void init () {
    tot = 0;
    for (int i = 0 ; i < maxn ; i++) {
        for (int j = 0 ; j < 26 ; j++) tr[i][j] = 0;
        fail[i] = cnt[i] = dep[i] = 0;
    }
}
int add (char *s) {
    int u = 0;
    for (int i = 1 ; s[i] ; i++) {
        if (!tr[u][s[i] - 'a']) tr[u][s[i] - 'a'] = ++tot;
        u = tr[u][s[i] - 'a'];
        dep[u] = i;
    }
    return u;
}
queue<int>q;
void build () {
    while (q.size()) q.pop();
    for (int i = 0 ; i < 26 ; i++) if (tr[0][i]) q.push(tr[0][i]);
    while (q.size()) {
        int u = q.front(); q.pop();
        for (int i = 0 ; i < 26 ; i++) {
            if (tr[u][i]) {
                fail[tr[u][i]] = tr[fail[u]][i];
                q.push(tr[u][i]);
            } else {
                tr[u][i] = tr[fail[u]][i];
            }
        }
    }
}
void ask (char *t) {
    int u = 0;
    memset(last, -1, sizeof last);
    for (int i = 1 ; t[i] ; i++) {
        u = tr[u][t[i] - 'a']; // 转移
        for (int j = u ; j ; j = fail[j]) {
            if (last[j] == -1 || i - last[j] >= dep[j]) {
                cnt[j]++;
                last[j] = i;
            }
        }
    }
    return ;
}
}
int main() {
    int T = 0, m;
    scanf("%d", &T);
    while (T--) {
        scanf("%s%d", s + 1, &m);
        memset(ans, 0, sizeof ans);
        AC::init();
        for (int i = 1 ; i <= m; i++) {
            scanf("%s", tmp + 1);
            pos[i] = AC::add(tmp);
        }
        AC::build();
        AC::ask(s);
        for (int i = 1 ; i <= m ; i++) printf("%d\n", AC::cnt[pos[i]]);
    }
    return 0;
}
1010. Yinyang

【题意】

一个 n × m n\times m n×m的网格,要给它染黑白,有些位置已经染了。要求最后所有黑色联通,白色也联通,且任意 2 × 2 2\times 2 2×2矩阵四个颜色非同色。

n × m ≤ 100 n\times m\leq 100 n×m100

【思路】

所有黑色和白色分别要连通,那么构成环的只可能是网格最外面一圈。

下面不妨设 n ≥ m n\geq m nm,考虑DP

DP时需要记录前 m m m个格子的颜色和连通性,这样的状态不超过20000个,转移时枚举下一位置的颜色,不允许形成环,特判最后两个格子即可。

【参考代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
const int N = 55;
const int M = 15;
const int MO = 998244353;
void ad(int&x,int y){
	x+=y;
	if(x>=MO)
		x-=MO;
}
int mul(int x,int y){
	return (LL)x*y%MO;
}
int n,m,a[N][N];
int b[M],f[M];
LL geth(){
	int i;
	LL h=0;
	for(i=1;i<=m;i++)
		h=h*2+b[i];
	for(i=1;i<=m;i++)
		h=h*16+f[i];
	if(f[f[f[f[m]-1]-1]-1]>1)
		return -1;
	return h;
}
void getbf(LL h){
	int i;
	for(i=m;i>=1;i--)
		f[i]=h&15,h>>=4;
	for(i=m;i>=1;i--)
		b[i]=h&1,h>>=1;
}
map<LL,int> mp;
vector<pair<LL,int> > v[2];
void dfs(int k){
	if(k==m+1){
		LL h=geth();
		if(h>=0)
		    v[0].push_back(make_pair(h,1));
		return;
	}
	int i;
	for(i=0;i<2;i++){
		if(a[1][k]>=0&&a[1][k]!=i)
			continue;
		b[k]=i;
		if(k==1||b[k]!=b[k-1])
			f[k]=k;
		else
			f[k]=f[k-1];
		dfs(k+1);
	}
}
LL nx(LL h,int t,int e){
	int i,x,y;
	getbf(h);
	if(b[t]==e){
		if(t==1)
			return h;
		if(f[t]==f[t-1])
			return -1;
		if(b[t]==b[t-1]){
			x=min(f[t],f[t-1]);
			y=max(f[t],f[t-1]);
			for(i=1;i<=m;i++)
				if(f[i]==y)
					f[i]=x;
		}
		return geth();
	}
	x=0;
	for(i=1;i<=m;i++)
		if(i!=t&&f[i]==f[t]){
			x=i;
			break;
		}
	if(!x)
		return -1;
	y=f[t];
	for(i=1;i<=m;i++)
		if(f[i]==y)
			f[i]=x;
	b[t]=e;
	f[t]=t;
	if(t!=1&&b[t]==b[t-1])
		f[t]=f[t-1];
	return geth();
}
void go(int i,int a){
	int j;
	LL x,y;
	for(j=0;j<v[0].size();j++){
		x=v[0][j].first;
		if(a!=1){
			y=nx(x,i,0);
			if(y>=0)
				v[1].push_back(make_pair(y,v[0][j].second));
		}
		if(a!=0){
			y=nx(x,i,1);
			if(y>=0)
				v[1].push_back(make_pair(y,v[0][j].second));
		}
	}
	v[0].clear();
	sort(v[1].begin(),v[1].end());
	x=-1;
	j=-1;
	for(i=0;i<v[1].size();i++){
		if(v[1][i].first==x)
			ad(v[0][j].second,v[1][i].second);
		else{
			v[0].push_back(v[1][i]);
			x=v[1][i].first;
			j++;
		}
	}
	v[1].clear();
}
int cal(LL h,int a1,int a2){
	int i,o=0;
	getbf(h);
	if(b[m-2]==b[m-1]&&b[m-1]==b[m])
		return 0;
	if(b[m-1]==b[m]){
		for(i=1;i<m-2;i++){
			if(b[i]==b[m]){
				if(f[i]!=f[m])
					return 0;
			}
			else{
				if(f[i]!=f[m-2])
					return 0;
			}
		}
		if(a1!=b[m]&&a2!=b[m])
			o++;
		if(a1!=b[m]&&a2!=b[m-2])
			o++;
		return o;
	}
	if(b[m-2]==b[m-1]){
		if(f[m-2]==f[m-1]){
			for(i=1;i<m-2;i++){
				if(b[i]==b[m]){
					if(f[i]!=f[m])
						return 0;
				}
				else{
					if(f[i]!=f[m-2])
						return 0;
				}
			}
			if(a1!=b[m-2]&&a2!=b[m-2])
				o++;
			return o;
		}
		for(i=1;i<m-2;i++){
			if(b[i]==b[m]){
				if(f[i]!=f[m])
					return 0;
			}
			else{
				if(f[i]!=f[m-2]&&f[i]!=f[m-1])
					return 0;
			}
		}
		if(a1!=b[m]&&a2!=b[m])
			o++;
		if(a1!=b[m]&&a2!=b[m-2])
			o++;
		return o;
	}
	if(f[m-2]==f[m]){
		for(i=1;i<m-2;i++){
			if(b[i]==b[m]){
				if(f[i]!=f[m])
					return 0;
			}
			else{
				if(f[i]!=f[m-1])
					return 0;
			}
		}
		if(a1!=b[m-1]&&a2!=b[m-1])
			o++;
		if(a1!=b[m]&&a2!=b[m-1])
			o++;
		if(a1!=b[m]&&a2!=b[m])
			o++;
		return o;
	}
	for(i=1;i<m-2;i++){
		if(b[i]==b[m]){
			if(f[i]!=f[m]&&f[i]!=f[m-2])
				return 0;
		}
		else{
			if(f[i]!=f[m-1])
				return 0;
		}
	}
	if(a1!=b[m-1]&&a2!=b[m-1])
		o++;
	return o;
}
int main()
{
	//freopen("input.txt","r",stdin);
	//freopen("std2.out","w",stdout);
	int T,i,j,s;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		for(i=1;i<=n;i++){
			for(j=1;j<=m;j++){
				scanf("%d",a[i]+j);
			}
		}
		if(n<m){
			for(i=1;i<=m;i++)
				for(j=i+1;j<=m;j++)
					swap(a[i][j],a[j][i]);
			swap(n,m);
		}
		v[0].clear();
		dfs(1);
		for(i=2;i<n;i++){
			for(j=1;j<=m;j++){
				go(j,a[i][j]);
			}
		}
		for(j=1;j<=m-2;j++)
			go(j,a[n][j]);
		s=0;
		for(i=0;i<v[0].size();i++)
			ad(s,mul(cal(v[0][i].first,a[n][m-1],a[n][m]),v[0][i].second));
		printf("%d\n",s);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值