Skip to content

20230923 B 组 发怒 题解

前言

密码是通用密码,大小写敏感

前三题比较水,虽然场上只写了 T1,T2 推出来了那个看起来很好维护的式子,以为自己能维护,结果要用一个叫高维前缀和的我不会的算法,硬刚 T2 3.5h,最终得分 100+0+0+0。

所以前三题我只写简略题解,第四题比较有意思。

前三题简略题解:

T1

删点不好维护,倒着加点,并查集维护,复杂度 $O((n+m)\log n)$。

T2

打表发现 $f^k_{0,0}=\bigoplus_{(i|j)\subseteq k}a_{i,j}$(其中 $|$ 表示按位或,$\bigoplus$ 表示按位异或,$\subseteq $ 表示二进制下 $1$ 的集合的从属关系),直接高维前缀和维护。

T3

考虑分成有空集和没空集两种情况,前一种贪心,后一种把式子化一下然后贪心。

T4

首先这道题长得很像树形 DP,那先考虑直接在树上做。

这里有个技巧,树上维护连通块可以考虑从上往下 DP(这么说可能有点抽象,详见状态设计)。

那么我们很容易设出一个状态,设 $dp_{rt,i,j}$ 表示考虑以 $rt$ 为根,考虑 dfs 序前 $i$ 个点,乘积为 $j$ 的方案数,转移枚举之前的乘积即可,具体转移方程略。

容易发现,为了不重不漏,我们只要对每个结点独立求出它子树内的答案即可,那么显然可以把第一维压掉,所以设 $dp_{i,j}$,意义和上文相同。

这样复杂度上界是 $O(n^2m)$ 的(考虑一条链),那么考虑优化。

首先这个 $m$ 应该先优化掉,因为第二维只和乘法有关,那么我们可以考虑除法下取整的一个性质,即 $\left\lfloor\frac{\left\lfloor\frac{x}{n}\right\rfloor}{m}\right\rfloor=\left\lfloor\frac{x}{nm}\right\rfloor$,那么我们可以把第二维改为维护 $\left\lfloor\frac{m}{j}\right\rfloor$,这样第二维的取值就只有 $\sqrt{m}$ 种了。

然后是前面的 $n^2$,容易发现这个数其实和树的形态有关,那么我们可以考虑使用点分治优化成 $O(n\log n)$ 的。

参考代码
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(int i=(s);i<=(e);i++)
#define fordown(i,s,e) for(int i=(s);i>=(e);i--)
using namespace std;
#define gc getchar()
inline int read(){
    int x=0,f=1;char c;
    while(!isdigit(c=gc)) if(c=='-') f=-1;
    while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
    return x*f;
}
#undef gc
const int N=2005,mod=1e9+7;
int n,m,a[N],vis[N],ans,tt;
vector<int> e[N];
struct svsqrt{// (1)!
    int a[N];
    void clear(){
        mem(a,0);
    }
    int& operator [](const int &r){
        if(r<=tt) return a[r];
        else return a[tt+m/r];
    }
    int calc(){
        int res=0;
        forup(i,0,N-1){
            (res+=a[i])%=mod;
        }
        return res;
    }
};
int als,sz[N],mx[N],wq;
void getrt(int x,int fa){
    sz[x]=1;mx[x]=0;
    for(auto i:e[x]){
        if(vis[i]||i==fa) continue;
        getrt(i,x);
        sz[x]+=sz[i];
        mx[x]=max(mx[x],sz[i]);
    }
    mx[x]=max(mx[x],als-sz[x]);
    if(!wq||mx[x]<mx[wq]) wq=x;
}
vector<int> ss;
void dfs(int x,int fa,svsqrt& dp){
    sz[x]=1;
    svsqrt nw;nw.clear();
    for(auto i:ss){
        if(i<a[x]) continue;
        if(!dp[i]) continue;
        (nw[i/a[x]]+=dp[i])%=mod;
    }
    for(auto i:e[x]){
        if(i==fa||vis[i]) continue;
        dfs(i,x,nw);
        sz[x]+=sz[i];
    }
    for(auto i:ss){
        if(!nw[i]) continue;
        (dp[i]+=nw[i])%=mod;
    }
}
void dfz(int x){
    svsqrt dp;
    dp.clear();
    dp[m]=1;
    dfs(x,0,dp);
    (ans+=dp.calc()-1)%=mod;
    vis[x]=1;
    for(auto i:e[x]){
        if(vis[i]) continue;
        als=sz[i];
        wq=0;
        getrt(i,0);
        dfz(wq);
    }
}
signed main(){
    n=read();m=read();
    fordown(i,m,1){
        if(m/i!=m/(i+1)) ss.push_back(m/i);
    }
    forup(i,1,n){
        a[i]=read();
    }
    forup(i,1,n-1){
        int u=read(),v=read();
        e[u].push_back(v);
        e[v].push_back(u);
    }
    tt=sqrt(m)+5;
    wq=0;
    als=n;
    getrt(1,0);
    dfz(wq);
    printf("%d\n",ans);
}
  1. 非常方便的写法,除了处理除法下取整以外改一改还能处理负数下标

Comments