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);
}