Skip to content

20230902 B 组模拟赛 题解

前言

我是超级大傻逼。

压缩包密码是今年的通用密码,大小写敏感

前排提示:这篇题解里面的语言可能会有点抽象,因为我爆大零,现在很烦躁。

T1

很有趣又好玩还有意思还很好而且很适合放在 T1 并且思路清晰条理清楚的诈骗题。

首先可以自己手动模拟一下小数据,发现无论如何 Alice 都能获胜。

然后就可以猜结论了:所有情况均为 Alice 获胜。

恭喜你,获得一百分。

考虑证明结论(或者构造方案)。

首先,假设数列不动,是一个指针在动(其实是等价的,但这样更好理解)。

如果全是 $0$,Alice 直接就获胜了,所以我们只考虑有 $1$ 的情况。

那么容易发现,假如数列中有且仅有一段连续的 $1$,那么在这一段 $1$ 之前 Bob 不得不翻转一个 $0$,不然 Alice 直接就赢了。

那么 Alice 可以每次只留第一段 $1$,把后面的全变成 $0$,这样下一轮 Bob 就不得不在这一段 $1$ 之前放一个 $1$。

容易发现假如把数列看成一个二进制数,那么每两轮这个数必定增加一定值。最终到达全 $1$ 状态。

但全 $1$ 状态 Alice 就可以一波全翻转直接获胜了。

所以所有 $2^n$ 种情况均为 Alice 获胜。

参考代码
#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=1e5+5,inf=0x3f3f3f3f,mod=998244353;
int n;
int ksm(int a,int b){
    int c=1;
    while(b){
        if(b&1) c=1ll*a*c%mod;
        a=1ll*a*a%mod;
        b>>=1;
    }
    return c;
}
signed main(){
    n=read();
    printf("%d\n",ksm(2,n));
}

T2

考虑建图。

具体来说,把每个数当成一个结点,然后把每张扑克牌当成一条边,每条边表示“在每条边的两个端点里选其中一个”,那么容易发现,建出来的图是若干个连通块,而一个连通块假如不是树,那么必然每个点都能选(因为边数大于等于点数,而每条边都有一个名额),反之,如果是树,那么整个连通块中有一个点是选不了的。容易发现这个点取树中的最大值或最小值是必定不劣的。

那么把每棵树的最大最小值对 $(x,y)$ 单独存起来,就成了一个区间覆盖问题,假如询问区间 $[l,r]$ 包含了任何一对 $(x,y)$(即 $l\le x < y \le r$),那么答案就是 No,反之就是 Yes。

做法有很多,再次略过了,最优能做到线性。

参考代码(非最优)
#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;
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=1e5+5,inf=0x3f3f3f3f;
int n,m,q;
vector<int> e[N];
int vis[N];
vector<pii> tr;
struct Node{
    int l,r,pos;
    bool operator <(const Node &p)const{return l<p.l;}
}que[N];
bool ans[N];
multiset<int> sr;
pii dfs(int x,int fa){
    vis[x]=1;
    pii res=mkp(x,x);
    for(auto i:e[x]){
        if(i==fa) continue;
        if(vis[i]){
            res=mkp(-1,-1);
            continue;
        }
        pii cc=dfs(i,x);
        if(res.fi!=-1){
            res.fi=min(res.fi,cc.fi);
            res.se=max(res.se,cc.se);
        }
    }
    return res;
}
signed main(){
    n=read();m=read();
    forup(i,1,m){
        int a=read(),b=read();
        e[a].push_back(b);
        e[b].push_back(a);
    }
    forup(i,1,n){
        if(!vis[i]){
            pii res=dfs(i,0);
//          forup(i,1,n){
//              printf("%d ",vis[i]);
//          }
//          puts("");
//          printf("%d %d\n",res.fi,res.se);
            if(res.fi!=-1){
                tr.push_back(res);
                sr.insert(res.se);
            }
        }
    }
    q=read();
    forup(i,1,q){
        que[i].l=read();que[i].r=read();
        que[i].pos=i;
    }
    sort(tr.begin(),tr.end());
    sort(que+1,que+q+1);
    int l=0;
    forup(i,1,q){
        while(l<(int)tr.size()&&tr[l].fi<que[i].l){
            sr.erase(sr.find(tr[l].se));
            ++l;
        }
        if(sr.empty()||*sr.begin()>que[i].r){
            ans[que[i].pos]=true;
        }else{
            ans[que[i].pos]=false;
        }
    }
    forup(i,1,q){
        puts(ans[i]?"Yes":"No");
    }
}

T3

四元环计数变种。

首先考虑类似于 Meet In Middle 的做法,现枚举一个点 $u$,然后找所有由 $u$ 出发的长度为 $2$ 的路径,然后假如两条路径终点相同就统计答案,因为每个环会被统计四次所以最后除以 $4$,复杂度上界大概是 $O(n^2)$。

考虑优化,发现每个环会被统计四次很不好,那么我们将点按边权从大到小排序,然后统计一个点删一个点,这样就不会重复了。假如你把这份代码交上去,恭喜你,通过此题。

乂?刚刚不是只优化了常数吗,为什么能 $n$ 方过十万。

因为其实这个上界过于松了,考虑算一个更紧的上界,用类似整除分块的思路。

具体来说,设点 $i$ 的度数为 $d_i$,考虑分 $d_i>\sqrt{m}$ 和 $d_i \le \sqrt{m}$ 考虑。

首先每个点计算次数就是 $d_i^2$(这个显然吧?考虑每个点作为路径中间点会被 $d_i$ 个起点访问,然后访问 $d_i$ 个终点)

对于 $d_i\le\sqrt{m}$,显然有 $\sum d_i\le 2m$(因为只有 $m$ 条边,一条边提供 $2$ 的度数),那么 $\sum d_i^2\le\left(\sum d_i\right)\max\begin{Bmatrix}d_i\end{Bmatrix}\le 2m\sqrt{m}$,就是 $O(m\sqrt{m})$ 级别的。

然后对于 $d_i>\sqrt{m}$,由于同样有 $\sum d_i\le 2m$,所以这样的点至多有 $\sqrt{m}$ 个,然后考虑一条路径 $a\to b \to c$ 中,假如这样的店作为 $b$,那么 $c$ 的度数显然也大于 $\sqrt{m}$,所以也属于这样的点,至多有 $\sqrt{m}$ 个,然后边 $b\to c$ 至多有 $2m$ 条,对应 $\sqrt{m}$ 个点 $c$,复杂度也是 $O(m\sqrt{m})$。

参考代码
#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=1e5+5,mod=1e9+7;
int n,m,a[N],ans,vis[N],rh[N],ct[N];
vector<int> e[N];
int seq[N];
bool cmp(int a,int b){
    return e[a].size()>e[b].size();
}
signed main(){
    n=read();m=read();
    forup(i,1,n){
        a[i]=read();
        seq[i]=i;
    }
    forup(i,1,m){
        int u=read(),v=read();
        e[u].push_back(v);
        e[v].push_back(u);
    }
    sort(seq+1,seq+n+1,cmp);
    forup(i,1,n){
        int u=seq[i];
        vis[u]=1;
        for(auto x:e[u]){
            if(vis[x]) continue;
            for(auto y:e[x]){
                if(vis[y]) continue;
                if(ct[y]!=0){
                    (ans+=(rh[y]+1ll*a[x]*ct[y]%mod)%mod)%=mod;
                    (ans+=1ll*(a[u]+a[y])*ct[y]%mod)%=mod;
                }
                (rh[y]+=a[x])%=mod;ct[y]++;
            }
        }
        for(auto x:e[u]){
            if(vis[x]) continue;
            for(auto y:e[x]){
                if(vis[y]) continue;
                rh[y]=ct[y]=0;
            }
        }
    }
    printf("%d\n",ans);
}

Comments