Skip to content

20240108 A 组模拟赛 题解

前言

fun fact:这场比赛有至少三个人在 T1 对拍超过一万组的情况下仍挂了分,其中两个甚至从 100pts 挂到 0pts(不瞒你说,这两个人是我和 KK)。

此外,XD 作为唯一一个赛时想出 T3 做法,且和另外一位重庆老哥并列第一 95pts 的人,在赛后大量批话。具体请移步2024 批话合集

唉,140->31。

T1

简单树形 DP。

设 $dp_i$ 表示从 $i$ 开始,涂完 $i$ 子树需要的最小时间。显然构造方案可以把所有儿子倒序排列后,每次涂前 $w_u$ 个,转移是简单的。

考虑如何换根,难点在于删掉将要换到的那个位置。容易发现每次删一个数会使后面全部往前平移一个。那么排序后倒序考虑删每个点,如果这个点恰好是某一块的开头才有可能使贡献改变,这个可以 multiset 维护。

复杂度 $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--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
using pii=pair<int,int>;
#define fi first
#define se second
#define mkp make_pair
#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=5e5+5,inf=0x3f3f3f3f;
int n,w[N],dp[N],pd[N];
vector<pii> vec[N];
vector<int> e[N];
void dfs(int x,int fa){
    dp[x]=0;
    for(auto i:e[x]){
        if(i==fa) continue;
        dfs(i,x);
        vec[x].push_back(mkp(dp[i],i));
    }
    sort(vec[x].begin(),vec[x].end(),greater<pii>());
    int sz=vec[x].size()-1;
    forup(i,0,sz){
        if(!(i%w[x])){
            dp[x]=max(dp[x],vec[x][i].fi+i/w[x]+1);
        }
    }
}
void dfs2(int x,int fa){
    multiset<int> ss;
    sort(vec[x].begin(),vec[x].end(),greater<pii>());
    int sz=vec[x].size()-1;
    forup(i,0,sz){
        if(!(i%w[x])){
            ss.insert(vec[x][i].fi+i/w[x]+1);
        }
    }
    ss.insert(0); // 这里是防止 RE
    pd[x]=*prev(ss.end());
    fordown(i,sz,0){
        if(!(i%w[x])){
            ss.erase(ss.find(vec[x][i].fi+i/w[x]+1));
            if(i<sz) ss.insert(vec[x][i+1].fi+i/w[x]+1);
        }
        if(vec[x][i].se==fa||vec[x][i].se==x) continue;
        vec[vec[x][i].se].push_back(mkp(*prev(ss.end()),x));
        dfs2(vec[x][i].se,x);
    }
}
signed main(){
    n=read();
    forup(i,1,n){
        w[i]=read();
    }
    forup(i,1,n-1){
        int u=read(),v=read();
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1,0);
    dfs2(1,0);
    forup(i,1,n){
        printf("%d\n",pd[i]);
    }
}

T2

感觉很智慧啊。

首先如果 $m=1$ 就是个裸的 Nim 游戏。但是 Nim 游戏是把每一堆分开的,而这个问题每一维都和其他维相关(因为有黑洞),考虑把四维放在一起的做法。

以下简称先手必胜为“必胜”,后手为“必败”。

首先按有向图博弈的思路,容易得到一个 $O(n^5)$ DP,具体来说,如果一个点下一步能走到的所有点都是必胜那么这个点必败,否则(指有至少一个点必败)这个点必胜。

注意到只需要考虑四个方向在下一个黑洞之前有没有必败点,又由于 DP 是从小到大枚举的,所以可以对每一维开一个三维的数组 $V_{a,b,c}$ 表示其余三维分别是 $a,b,c$,这一维到上一个黑洞前有没有必败点,每次遇到黑洞把对应四个点清零即可。

但是这个很难再压了,考虑继续优化。

注意到对最后一维记录的这个东西非常多余,因为你会连续地枚举这一维。容易发现,在每个黑洞之后找到第一个必败点,后面的都是必胜的(因为这一维对应的 $V$ 都是 $1$ 了)。而容易发现这个可以用 bitset 来维护。

具体来说,$V$ 只维护前三维,然后每次把对应的三个 bitset 并起来取反。这样每次只需要一个 _Find_next 就能找到第一个必败点,然后一直到下一个黑洞都是必胜的。具体求答案可以用总的减去必败的和黑洞。

注意每次更新三个 $V$,另外黑洞可以用指针维护。

时间复杂度 $O(\frac{n^4}{w}+m\log m)$,空间复杂度 $O(n^3+m)$,前面这个 $n^3$ 是 bitset 类型的所以不会炸。

参考代码
#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--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
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=205,inf=0x3f3f3f3f;
int n,m,cnt;
struct Node{
    int a,b,c,d;
    bool operator <(const Node &r)const{
        if(a!=r.a) return a<r.a;
        if(b!=r.b) return b<r.b;
        if(c!=r.c) return c<r.c;
        return d<r.d;
    }
}sv[100005];
bitset<N> b1[N][N],b2[N][N],b3[N][N],bs;
signed main(){
    n=read();m=read();
    forup(i,1,m){
        int a=read(),b=read(),c=read(),d=read();
        sv[i]=Node{a,b,c,d};
    }
    sort(sv+1,sv+m+1);
    int ll=1;
    forup(a,1,n){
        forup(b,1,n){
            forup(c,1,n){
                bs=~(b1[b][c]|b2[a][c]|b3[a][b]);
                int j=bs._Find_next(0);
                #define ava (ll<=m&&sv[ll].a==a&&sv[ll].b==b&&sv[ll].c==c)
                if(j<(ava?sv[ll].d:n+1)){
                    b1[b][c].set(j,1);
                    b2[a][c].set(j,1);
                    b3[a][b].set(j,1);
                    ++cnt;
                }
                while(ava){
                    int i=sv[ll].d;
                    ++ll;
                    b1[b][c].set(i,0);
                    b2[a][c].set(i,0);
                    b3[a][b].set(i,0);
                    int j=bs._Find_next(i);
                    if(j<(ava?sv[ll].d:n+1)){
                        b1[b][c].set(j,1);
                        b2[a][c].set(j,1);
                        b3[a][b].set(j,1);
                        ++cnt;
                    }
                }
            }
        }
    }
    printf("%d\n",n*n*n*n-m-cnt);
}

T3

怪题。

容易想到质因数完全相同的集合可以随意互换。

感觉这句话有歧义啊,若一个数的质因数分解为 $x=\prod p_i^{k_i}$,设 $f(x)=\prod p_i$,那么对于任意一对 $i,j$ 使得 $f(i)=f(j)$,$i,j$ 显然可以互相交换(因为任意一个数若和 $i$ 互质当且仅当和 $j$ 互质),设这样的集合为 $S$。

但是容易发现限制比这个更弱,比如在 $n=5$ 时 $1,3,5$ 是可以随意交换的,但显然这三者的 $f$ 不相同。

注意到若两个质数 $p_1,p_2$,满足 $\left\lfloor\frac{n}{p_1}\right\rfloor=\left\lfloor\frac{n}{p_2}\right\rfloor$,那么将这两个数的倍数集合完全互换也是合法的(对于交,容易发现完全不管它是没关系的)。因为我们只关注互质关系,所以具体包含哪个质因子其实就不重要了。

注意到若 $p_1\ne p_2$,又有 $\left\lfloor\frac{n}{p_1}\right\rfloor=\left\lfloor\frac{n}{p_2}\right\rfloor$,可以推出 $p_1,p_2>\sqrt{n}$,那么每个数只有一个这样的质因子。

这样就可以直接维护了。最终答案就是每个 $S$ 内没被用过的点数的阶乘之积乘上 $p_1\to p_2$ 的映射关系数量即可。

用欧拉筛复杂度可以做到 $O(n)$,注意特判 $1$。

参考代码
#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--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
char buf[1<<21],*p1,*p2;
#define gc (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
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=3e7+5,mod=1e9+7;
int n,a[N];
int vv[N],mx[N],mul[N];
vector<int> pri;
int vis1[N],vis2[N];
int cnt1[N],cnt2[N];
int fact[N];
void init(){
    mx[1]=1;mul[1]=1;
    ++cnt1[1];++cnt2[1];
    forup(i,2,n){
        if(!vv[i]){
            mx[i]=i;mul[i]=1;
            pri.push_back(i);
            ++cnt2[n/i];
        }
        ++cnt1[mul[i]*mx[i]];
        for(auto j:pri){
            if(i*j>n) break;
            vv[i*j]=1;
            if(i%j==0){
                mx[i*j]=mx[i];
                mul[i*j]=mul[i];
                break;
            }else{
                mx[i*j]=mx[i];
                mul[i*j]=mul[i]*j;
            }
        }
    }
    fact[0]=1;
    forup(i,1,n){
        fact[i]=1ll*fact[i-1]*i%mod;
    }
}
bool chk(int a,int b){
    int pa=(a==1?1:n/mx[a]),pb=(b==1?1:n/mx[b]);
    if(mul[a]!=mul[b]||pa!=pb) return false;
    if(vis1[mx[a]]){
        if(vis1[mx[a]]!=mx[b]){
            return false;
        }
    }else if(vis2[mx[b]]){
        return false;
    }
    return true;
}
signed main(){
    n=read();
    init();
    forup(i,1,n){
        a[i]=read();
        if(a[i]){
            if(!chk(i,a[i])){
                puts("0");
                return 0;
            }
            int pp=i==1?1:n/mx[i];
            if(!vis1[mx[i]]){
                vis2[mx[a[i]]]=1;
                vis1[mx[i]]=mx[a[i]];
                --cnt2[pp];
            }
            --cnt1[mx[a[i]]*mul[a[i]]];
        }
    }
    int ans=1;
    forup(i,1,n){
        ans=1ll*ans*fact[cnt1[i]]%mod;
        ans=1ll*ans*fact[cnt2[i]]%mod;
    }
    printf("%d\n",ans);
}

Comments