A

手玩一下样例,我们从大到小确定每个数:将确定的数挪到序列的最前面,设确定了 $x$ 个数,那么 $x \times x$ 的方格的数都没用了,并且剩下的是一个 L 形方格。剩下的 L 形方格内对角线一定是最大值,所以我们只需要每次取最大值,将这个数和已经确定的数的 $\gcd$ 删掉即可。注意要删两次。

B

可以猜测一下选择方案一定是前面可能有某些增加值的地方,后面值就一直不变了。所以我们先一直跑 dp,跑到快要 T 的时候就相当于要从剩下的块中选出一些数字。由于我们可以将这个块添加到任意位置,所以选最大出现次数的数字即可。

可以证明其实只需要跑 $n$ 块即可。。因为你最坏情况是每一块增加一次。这一块不增加后面完全没有必要增加。

C

一个经典结论是设长度为 $s$,设 $g = \gcd(s,n)$,那么新的序列的第 $i$ 位会对应原序列的 $i,i+g,i+2g,\ldots i+kg$ 位。所以我们复制一遍序列,枚举 $g|n$,将下标按照 $\bmod g$ 分类,一个点合法当前仅当它是和它一组中的最大值,一个方案合法当且仅当区间内所有点合法,倒着扫一遍维护一个指针,差分维护前缀加,求出所有长度的区间的方案数,判断 $\gcd$ 即可解决。

注意这里需要预处理一下 $\gcd(i,n)$,要不然会 T。。时间还是很紧的。

D

设 $v_p(x)$ 表示 $x$ 的唯一分解中 $p$ 上面的指数。

首先我们有 $v_p(n!) = \sum_{i \geq 1} \lfloor \frac{n}{p^i} \rfloor$,并且还有 $v_p(ab) = v_p(a)+v_p(b)$,如果考虑将 $n$ 分解成 $p$ 进制下的数,那么 $v_p(n!)$ 的每一位贡献都是将比这一位低的位都去掉的数的和。

那么我们知道 $v_p(\binom n m) = v_p(n!)-v_p(m!)-v_p((n-m)!)$,我们考虑 $m+(n-m) = n$ 这个加法运算,如果在第 $i$ 位它进位了,那么在前 $i$ 位贡献不变,在第 $i+1$ 位答案会多 $1$。所以 $v_p(\binom n m)$ 就是 $n-m$ 和 $m$ 做 $p$ 进制下加法的进位次数。

这个好像叫做库默尔定理?

然后我们可以考虑设计一个数位dp:我们在第 $i$ 位需要知道它后面那一位是否进位才可以确定限制等因素,设 $f_{i,j,0/1,0/1}$ 从高到低考虑前 $i$ 位,进了 $j$ 次位,当前的和是否卡上界,下一位是否进位,转移细节可以看代码(我注释写的很详细了)。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 3500+5;
const int ha = 1e9 + 7;
const int inv2 = (ha+1)>>1;
int p,_,n;
char str[MAXN];
int b[MAXN],a[MAXN];
int f[2][MAXN][2][2],now;
// 考虑前i位 当前进位了j次 现在是否卡上界 下一位是否进位

inline void add(int &x,int y){
    x += y;if(x >= ha) x -= ha;
}

inline int calc(int n){ // sum of [x <= b,y <= n,x+y <= n]
    if(n < 0) return 0;
    if(n == 0) return 1;
    return (1ll*(n+1)*(n+1)%ha+ha-1ll*n*(n+1)%ha*inv2%ha)%ha;
}

inline int calc2(int n){ // sum of [x <= p-1,y <= p-1,x+y <= n]
    // p <= n+1-i => i <= n+1-p
    int res = 0;
    if(n+1-p >= 0) add(res,1ll*(n+1-p+1)%ha*p%ha);
    // p > n+1-i => i >= n+2-p
    if(n+2-p <= p-1){
        // p-1 +p-2 + ....
        int len = (p-1)-(n+2-p)+1;
        add(res,1ll*(p-1)*p%ha*inv2%ha);
        len = (p-1)-len;
        if(len > 0) add(res,ha-1ll*(len+1)*len%ha*inv2%ha);
    }
    return res;
}

int main(){
    scanf("%d%d",&p,&_);// 进制转换
    scanf("%s",str+1);int len = strlen(str+1);
    FOR(i,1,len) a[i] = str[i]-'0';
    std::reverse(a+1,a+len+1);
    while(len){
        int t = 0;
        ROF(i,len,1) t = (10ll*t+a[i]) % p;
        b[++n] = t;
        a[1] -= t;
        FOR(i,1,len){
            if(a[i] < 0){
                int tt = (-a[i])/10;
                if(a[i]+10*tt < 0) ++tt;
                a[i] += 10*tt;
                a[i+1] -= tt;
            }
        }
        while(len && !a[len]) --len;
        LL r = 0; // up to O(p^2)
        ROF(i,len,1){
            r = 10*r+a[i];
            a[i] = r/p;
            r %= p;
        }
        while(len && !a[len]) --len;
    }
    std::reverse(b+1,b+n+1);
    f[now=0][0][1][0] = 1;
    // int tt = 0;FOR(x,0,p-1) FOR(y,0,p-1) tt += (x+y <= 3);
    // DEBUG(tt);DEBUG(calc2(3));
    // DEBUG(calc2(3)-calc2(2));
    // exit(0);
    FOR(i,1,n){
        // 打破限制 <=> (x+y)%p < b[i]
        // 有进位  <=>  x+y>=p
        // 无进位 <=> x+y <= p-1
        // 卡上界 <=> (x+y)%p = b[i]
        // +1 <=> 这一位的值 +1=
        // 这一位有限制 上一位必然有限制
        // (x+1)^2-x*(x+1)/2
        CLR(f[now^1],0);
        FOR(j,0,i){// 设两个数分别为x,y
            int t = 0;
            int c1 = calc(p-1);
            int c2 = calc(p-2);
            int c3 = calc(b[i]-1);
            int c4 = (calc2(p+b[i]-1)+ha-calc2(p-1))%ha;

            add(f[now^1][j][0][0],1ll*f[now][j][0][0]*(c1/*不进位*/)%ha);
            add(f[now^1][j][0][0],1ll*f[now][j][0][1]*(c2/*有进位*/)%ha);
            add(f[now^1][j][0][0],1ll*f[now][j][1][0]*(c3/*打破限制 不进位*/)%ha);
            add(f[now^1][j][0][0],1ll*f[now][j][1][1]*(c4/*打破限制 有进位*/)%ha);
            
            c1 = calc(p-2);
            c2 = calc(p-2);add(c2,p);
            c3 = calc(b[i]-2);
            c4 = (calc2(std::min(2*p-2,b[i]+p-2))+ha-calc2(p-2))%ha;
            
            add(f[now^1][j+1][0][1],1ll*f[now][j][0][0]*(c1/*不进位 +1*/)%ha);
            add(f[now^1][j+1][0][1],1ll*f[now][j][0][1]*(c2/*有进位 +1*/)%ha);
            add(f[now^1][j+1][0][1],1ll*f[now][j][1][0]*(c3/*打破限制 不进位 +1*/)%ha);
            add(f[now^1][j+1][0][1],1ll*f[now][j][1][1]*(c4/*打破限制 有进位 +1*/)%ha);
        
            c3 = b[i]+1;
            c4 = std::min(b[i]+p,p-1)-std::max(1+b[i],0)+1;
            c4 = std::max(c4,0);
            // add(f[now^1][j][1][0],f[now][j][0][0]*(/*不存在*/));
            // add(f[now^1][j][1][0],f[now][j][0][1]*(/*不存在*/));
            add(f[now^1][j][1][0],1ll*f[now][j][1][0]*(c3/*卡上界 不进位*/)%ha);
            add(f[now^1][j][1][0],1ll*f[now][j][1][1]*(c4/*卡上界 有进位*/)%ha);
            
            c3 = b[i]-1+1;
            c4 = std::min(b[i]+p-1,p-1)-std::max(1-p+b[i]+p-1,0)+1;
            // add(f[now^1][j+1][1][1],f[now][j][0][0]*(/*不存在*/));
            // add(f[now^1][j+1][1][1],f[now][j][0][1]*(/*不存在*/));
            add(f[now^1][j+1][1][1],1ll*f[now][j][1][0]*(c3/*卡上界 不进位 +1*/)%ha);
            add(f[now^1][j+1][1][1],1ll*f[now][j][1][1]*(c4/*卡上界 有进位 +1*/)%ha);
        }
        now ^= 1;
    }
    int ans = 0;
    FOR(i,_,n) add(ans,f[now][i][0][0]),add(ans,f[now][i][1][0]);
    printf("%d\n",ans);
    return 0;
}

E

首先表达式问题肯定不能做,要建表达式树。

建数每次扫出一个完整的括号就在这个地方划分就好了(详情请看代码)。

发现只有 $4$ 个变量也就是 $16$ 种取值,我们相当于限制了在多个变量取值情况的总取值,所以我们考虑将所有可能取值的总取值都压缩起来:设 $f_{i,S}$ 表示 $i$ 子树内,变量取值为 $1$ 的集合 $T \in S$ 的时候总取值都为 $1$,其余都为 $0$ 的方案数。换言之 $S$ 是字母取值集合的集合(所以大小 $2^{16}$)。

在底层的时候如果是确定的点就枚举所有的 $v \in T$,将这些 $T$ 组成的集合 $S$ 的 $f_{v,S} = 1$。如果不确定的话就把所有可能枚举一遍。这里要预处理所有类型的点而不是每次暴力要不然时间可能不太行。

然后合并的时候相当于是一个集合并卷积或者集合交卷积,FWT即可。

这种将所有可行的取值的状态压起来的状压还是要多想。。好好复习一下。

#include <bits/stdc++.h>

#define fi first
#define se second
#define db double
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl

const int MAXN = 500+5;
const int MAXM = 16;
const int ha = 1e9 + 7;

inline void add(int &x,int y){
    x += y;if(x >= ha) x -= ha;
}

int ch[MAXN][2],tot;
char a[MAXN],str[MAXN];
int rt;

inline int work(int l,int r){
    if(l > r) return 0;
    if(l == r){
        a[++tot] = str[l];
        return tot;
    }
    int sm = 0,ps = 0;
    FOR(i,l,r){
        if(str[i] == '(') ++sm;
        if(str[i] == ')') --sm;
        if(sm == 0){
            ps = i;break;
        }
    }
    if(ps == r) return work(l+1,r-1);
    int x = ++tot;
    a[x] = str[ps+1];
    ch[x][0] = work(l+1,ps-1);
    ch[x][1] = work(ps+3,r-1);
    return x;
}

int f[MAXN][(1<<16)+3];
int g[9][(1<<16)+3];
// 子树v内, 四个字母每种可能的取值 T 对应的表达式取值集合是S,方案数

inline int ctoi(char c){
    if(c >= 'A' && c <= 'D') return c-'A';
    if(c >= 'a' && c <= 'd') return c-'a';
    return -1;
}

int N = (1<<16);

inline void OR(int f[]){for(int mid = 1;mid < N;mid <<= 1) for(int i = 0;i < N;i += (mid<<1)) FOR(j,0,mid-1) add(f[i+mid+j],f[i+j]);}
inline void iOR(int f[]){for(int mid = N>>1;mid;mid >>= 1) for(int i = 0;i < N;i += (mid<<1)) FOR(j,0,mid-1) add(f[i+mid+j],ha-f[i+j]);}
inline void AND(int f[]){for(int mid = 1;mid < N;mid <<= 1) for(int i = 0;i < N;i += (mid<<1)) FOR(j,0,mid-1) add(f[i+j],f[i+mid+j]);}
inline void iAND(int f[]){for(int mid = N>>1;mid;mid >>= 1) for(int i = 0;i < N;i += (mid<<1)) FOR(j,0,mid-1) add(f[i+j],ha-f[i+mid+j]);}
inline void COR(int A[],int B[],int C[]){OR(A);OR(B);FOR(i,0,N-1) C[i] = 1ll*A[i]*B[i]%ha;iOR(C);iOR(A);iOR(B);}
inline void CAND(int A[],int B[],int C[]){AND(A);AND(B);FOR(i,0,N-1) C[i] = 1ll*A[i]*B[i]%ha;iAND(C);iAND(A);iAND(B);}

int tmp[(1<<16)+5];

inline void dfs(int v){
    if(!ch[v][0] && !ch[v][1]){
        int id = ctoi(a[v])+(4*(a[v] >= 'a' && a[v] <= 'd'));
        if(id == -1) id = 8;
        FOR(S,0,(1<<16)-1) f[v][S] = g[id][S];
        return;
    }
    dfs(ch[v][0]);dfs(ch[v][1]);
    if(a[v] == '&') return CAND(f[ch[v][0]],f[ch[v][1]],f[v]);
    if(a[v] == '|') return COR(f[ch[v][0]],f[ch[v][1]],f[v]);
    CAND(f[ch[v][0]],f[ch[v][1]],tmp);
    COR(f[ch[v][0]],f[ch[v][1]],f[v]);
    FOR(S,0,N-1) add(f[v][S],tmp[S]);
}

int main(){
    FOR(i,0,3){
        int t = 0;
        FOR(S,0,(1<<4)-1) t |= (((S>>i)&1)<<S);
        ++g[i][t];++g[i+4][t^((1<<16)-1)];
        ++g[8][t];++g[8][t^((1<<16)-1)];
    }
    scanf("%s",str+1);int len = strlen(str+1);
    rt = work(1,len);
    dfs(rt);
    int n;scanf("%d",&n);
    int t0=0,t1=0;
    FOR(i,1,n){
        int a,b,c,d,e;scanf("%d%d%d%d%d",&a,&b,&c,&d,&e);
        int t = a|(b<<1)|(c<<2)|(d<<3);
        if(e) t1 |= (1<<t);
        else t0 |= (1<<t);
    }
    int ans = 0;
    FOR(S,0,(1<<16)-1) if(((S&t1) == t1) && ((S&t0) == 0)) add(ans,f[rt][S]);
    printf("%d\n",ans);
    return 0;
}
Last modification:September 27th, 2020 at 10:04 pm
如果觉得我的文章对你有用,请随意赞赏