Codeforces Round 323 (Div. 1) (CF582)

A. GCD Table

首先有个结论,\(\gcd(x,y)\le \min(x,y)\)
那么给出的数组中最大的数一定属于原来的 \(a\)。把它排除掉后,次大的数同理。因此,我们每次找到剩下最大的数加入 \(a\) 数组,并把 \(a\) 数组中已经形成的 \(\gcd\) 依次删除,不断重复这个过程即可构造出答案。

Code
const int N=2.5e5+5;
int n,b[N],a[N],flag[N];
map<int,int> pos;
int main()
{
    n=read();
    for(int i=1;i<=n*n;i++) b[i]=read();
    sort(b+1,b+n*n+1),reverse(b+1,b+n*n+1);
    for(int i=n*n;i;i--) pos[b[i]]=i; 
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=n*n;j++) if(!flag[j]) {a[i]=b[j];break;}
        flag[pos[a[i]]]=1,pos[a[i]]++;
        for(int j=1;j<i;j++) 
        {
            int x=__gcd(a[i],a[j]);
            flag[pos[x]]=1,pos[x]++;flag[pos[x]]=1,pos[x]++;
        }
    }
    for(int i=1;i<=n;i++) printf("%d ",a[i]);printf("\n");
    return 0;
}

B. Once Again...

一个不太动脑子的做法是定义广义矩乘,然后矩阵快速幂转移 dp。
当然题这么做就没意思了,所以来写写另外一种做法。

观察到,如果 \(T\) 很大,一定会在某一个区间中一直重复选同一个数。那么答案可以分成两部分:

  • 正常的最长不降子序列
  • 一直重复的部分

对于前者,我们把序列每 \(n\) 个划成一段。显然不可能存在某一段一个都不选,因为这样不如把这段丢到第二种情况。
那么因为钦定了每段都要选,只考虑前 \(n\) 段一定包含了最优解。
而剩下的 \(T-n\) 段,我们希望重复的数尽量多。所以选择出现次数最多的数,一直重复选它就可以了。

Code
const int N=1e4+5;
int n,T,a[N];
int cnt[N],mx,ans,f[N];
int main()
{
    n=read(),T=read();
    for(int i=1;i<=n;i++) a[i]=read();
    int len=min(n,T)*n;
    for(int i=n+1;i<=len;i++) a[i]=a[i-n];
    for(int i=1;i<=len;i++)
        for(int j=0;j<i;j++) if(a[j]<=a[i]) f[i]=max(f[i],f[j]+1),ans=max(ans,f[i]);
    for(int i=1;i<=n;i++) cnt[a[i]]++,mx=max(mx,cnt[a[i]]);
    printf("%d\n",ans+max(T-n,0)*mx);
    return 0;
}

C. Superior Periodic Subarrays

题目中的限制等价于 \(a_i\ge a_{(ks+i)\bmod n}\)
推推式子,发现这可以整理成 \(a_i\ge a_{x\gcd(s,n)+i}\)

也就是说,我们枚举 \(\gcd(s,n)\),并把 \(a\)\(\bmod \, \gcd(s,n)\) 的值分成 \(d\) 组。那么每个组能选的 \(a_i\) 只有这个组内的最大值。
处理出所有可以选择的数,令 \(f_i\) 表示以 \(i\) 结尾最多连续选几个。答案就是可以选的区间数。
然后一个很奇怪的优化是,判断 \(\gcd(s,n)=d\) 比判断 \(\gcd(\frac{s}{d},\frac{n}{d})=1\) 要慢,写成前面那种会被卡常。感觉很离谱。

Code
#define int long long
const int N=1e6+5,mod=998244353;
int n,a[N];
int ans,b[N],f[N],mx[N],cnt[N];
signed main()
{
    n=read();
    for(int i=1;i<=n;i++) a[i]=read(),a[i+n]=a[i];
    for(int d=1;d<n;d++)
    {
        if(n%d) continue;
        for(int k=0;k<d;k++)
        {
            mx[k]=0;
            for(int i=k;i<(n<<1);i+=d) mx[k]=max(mx[k],a[i]);
            for(int i=k;i<(n<<1);i+=d) b[i]=(a[i]==mx[k]);
        }
        f[0]=b[0];
        for(int i=1;i<(n<<1);i++) f[i]=min(n-1,b[i]?f[i-1]+1:0);
        cnt[0]=0;
        for(int i=1;i<(n/d);i++) cnt[i]=cnt[i-1]+(__gcd(i,n/d)==1);
        for(int i=n;i<(n<<1);i++) ans+=cnt[f[i]/d];
    }
    printf("%lld\n",ans);
    return 0;
}

D. Number of Binominal Coefficients

根据库默尔定理,有:\(\binom{n+m}{m}\) 中,质数 \(p\) 的幂次等于 \(n,m\)\(p\) 进制下相加的进位次数。
那么题意转化为计算有多少对 \((n,m)\)\(n+m\le A\),它们在 \(p\) 进制下相加的进位次数 \(\ge \alpha\)
考虑数位 dp:设 \(f_{i,0/1,0/1,j}\) 表示前 \(i\) 位,是否进位,是否卡上界,进位了 \(j\) 次的方案数。
这东西会炸空间,要开滚动数组。

Code
#define int long long
const int N=4005,mod=1e9+7;
int p,c,a[N],b[N];
int f[2][2][N],g[2][2][N];
char s[N];
int n;
il void add(int &x,int y) {x=(x+y)%mod;}
signed main()
{
    p=read(),c=read(); scanf("%s",s+1),n=strlen(s+1);
    for(int i=1;i<=n;i++) a[i]=s[i]-'0';
    reverse(a+1,a+n+1);
    for(int i=n;i;i--)
    {
        for(int j=1;j<N-1;j++) b[j]*=10;
        b[1]+=a[i];
        for(int j=1;j<N-1;j++) b[j+1]+=b[j]/p,b[j]%=p;
    }
    int n=N-1;
    while(!b[n]&&n) n--;
    if(!n) {printf("0\n");return 0;}
    // cout<<"??"<<n<<endl;
    for(int i=1;i<=n;i++) a[i]=b[i];
    // cout<<endl;
    f[1][0][0]=1;
    int a0=(p*(p+1)/2)%mod;
    int a1=((p-1)*p/2)%mod;
    for(int i=n;i;i--)
    {
        int a2=a[i]*(a[i]+1)/2%mod,a3=a[i]*(a[i]-1)/2%mod;
        int a4=a[i]*(2*p-a[i]-1)/2%mod,a5=a[i]*(2*p-a[i]+1)/2%mod;
        memset(g,0,sizeof(g));
        for(int j=0;j<=n-i;j++)
        {
            add(g[0][0][j],f[0][0][j]*a0+f[1][0][j]*a2);
            add(g[0][1][j],f[0][0][j]*a1+f[1][0][j]*a3);
            add(g[1][0][j],f[1][0][j]*(a[i]+1));
            add(g[1][1][j],f[1][0][j]*a[i]);
            add(g[0][0][j+1],f[0][1][j]*a1+f[1][1][j]*a4);
            add(g[0][1][j+1],f[0][1][j]*a0+f[1][1][j]*a5);
            add(g[1][0][j+1],f[1][1][j]*(p-a[i]-1));
            add(g[1][1][j+1],f[1][1][j]*(p-a[i]));
        }
        swap(f,g);
    }
    int res=0;
    for(int i=c;i<=n;i++)
    {
        add(res,f[0][0][i]+f[1][0][i]);
    }
    printf("%lld\n",res);
    return 0;
}

E. Boolean Function

先建出表达式树。
考虑 dp,设 \(f_{i,j}\) 表示合并到点 \(i\),子树内每个变量的计算结果状压后为 \(j\) 的方案数。
那么转移方程就是:

\[f_{u,i}=\sum_{j\oplus k=i} f_{l,j}\times f_{r,k} \]

直接转移是不可行的,但是这个式子貌似长得和位运算卷积一模一样。套一个 FMT 优化就能过。

Code
typedef long long ll;
const int N=505,M=(1<<16)+5,mod=1e9+7;
il void add(int &x,ll y) {x=(x+y%mod)%mod;if(x<0) x=(x+mod)%mod;}
int n,m,S,limit;
char s[N];
int f[N<<1][M],g[M];
struct node
{
    int f[4];
}a[25];
void Or(int *a,int tp)
{
    for(int len=1;len<limit;len<<=1)
        for(int i=0;i<limit;i+=(len<<1))
            for(int j=0;j<len;j++) add(a[i+j+len],a[i+j]*tp);
}
void And(int *a,int tp)
{
    for(int len=1;len<limit;len<<=1)
        for(int i=0;i<limit;i+=(len<<1))
            for(int j=0;j<len;j++) add(a[i+j],a[i+j+len]*tp);
}
int tot;
int L[N],R[N];
#define ls L[now]
#define rs R[now]
void solve(int &now,int l,int r)
{
    if(!now) now=++tot;
    if(l>r) return;
    if(l==r)
    {
        if(s[l]!='?') 
        {
            int ss=0;
            for(int i=0;i<m;i++)
            {
                int now=0;
                if(s[l]>='A'&&s[l]<='D') now=a[i].f[s[l]-'A'];
                else now=a[i].f[s[l]-'a']^1;
                ss|=(1<<i)*now;
            }
            f[now][ss]++;
        }
        else 
        {
            for(int c=0;c<4;c++)
            {
                int s1=0,s2=0;
                for(int i=0;i<m;i++)
                {
                    int now=a[i].f[c];
                    s1|=(1<<i)*now,s2|=(1<<i)*(now^1);
                }
                f[now][s1]++,f[now][s2]++;
            }
        }
        return;
    }
    int sum=0,mid=0;
    for(int i=l;i<=r;i++) 
        if(s[i]=='(') sum++;
        else if(s[i]==')') sum--;
        else if(!sum) mid=i;
    solve(ls,l+1,mid-2),solve(rs,mid+2,r-1);
    memset(g,0,sizeof(g));
    if(s[mid]!='|') 
    {
        And(f[ls],1),And(f[rs],1);
        for(int i=0;i<limit;i++) add(g[i],1ll*f[ls][i]*f[rs][i]);
        And(g,-1),And(f[ls],-1),And(f[rs],-1);
        for(int i=0;i<limit;i++) add(f[now][i],g[i]);
    }
    memset(g,0,sizeof(g));
    if(s[mid]!='&') 
    {
        Or(f[ls],1),Or(f[rs],1);
        for(int i=0;i<limit;i++) add(g[i],1ll*f[ls][i]*f[rs][i]);
        Or(g,-1),Or(f[ls],-1),Or(f[rs],-1);
        for(int i=0;i<limit;i++) add(f[now][i],g[i]);
    }
}
signed main()
{
    scanf("%s",s+1),n=strlen(s+1);
    m=read();
    for(int i=0;i<m;i++)
    {
        for(int j=0;j<4;j++) a[i].f[j]=read();
        S|=(1<<i)*read();
    }
    limit=1; while(limit<(1<<m)) limit<<=1;
    int rt=0;
    solve(rt,1,n);
    printf("%d\n",f[rt][S]);
    return 0;
}
posted @ 2023-11-02 11:52  樱雪喵  阅读(281)  评论(0编辑  收藏  举报