Preface
和保安拉扯了半天才进EZ,然后就被招呼着打模拟赛了
说实话OI赛制以后可能再难遇到了所以还是有点怀念的说
结果T3自信写完过了大样例无比自信摸鱼去了,然后炸到40pts了……
本来以为HHHOJ上的号都废了结果还能拿出来给别人涨Rating,真是太化蜡了
由于外面上不了HHHOJ所以我就简化一下题面放上去了
A. 「NOIP2022模拟赛一 By yzxoi A」质数检验
Pro
- 设\(f(x)\)表示大于\(x\)的第一个质数
- \(t\)组询问,给定\(x\),判断\(\lfloor\frac{f(x)+f(f(x))}{2}\rfloor\)是否为质数
- \(t\le 10^5,x\le 10^{18}\)
Sol
一眼猜测当且仅当\(x=1\)时\(g(x)=2\)为YES
,否则为NO
证明:若\(x>1\),显然\(\frac{f(x)+f(f(x))}{2}\)为整数,且\(f(x)<\frac{f(x)+f(f(x))}2< f(f(x))\)
假设\(\frac{f(x)+f(f(x))}{2}\)为质数,则\(f(f(x))\le \frac{f(x)+f(f(x))}2\),矛盾,故\(g(x)\)为合数
#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int t; long long x;
int main()
{
for (scanf("%d",&t);t;--t)
scanf("%lld",&x),puts(x==1?"YES":"NO");
return 0;
}
B. 「NOIP2022模拟赛一 By yzxoi B」数据结构
Pro
- 有长度为\(n\)的序列\(a\),操作系数\(E\),进行\(m\)个操作:
1 l r x
:对\([l,r]\)内所有满足\(a_i\le E\)的数加上\(x\)2 l r
:查询\(\max_\limits{l\le i\le r} a_i[a_i\le E]\)
- \(n,m\le 10^6,0\le x,a_i,E\le 10^6\)
Sol
不难发现每个数只会被删除一次,删除之后就不再参与统计了
因此我们可以直接暴力把超过\(E\)的数直接变成\(-\infty\),由于每次修改最多访问\(O(\log n)\)个节点,因此复杂度还是对的
#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
#define int long long
using namespace std;
const int N=1000005;
int n,m,lim,a[N],opt,l,r,x;
class Segment_Tree
{
private:
struct segment
{
int mx,tag;
}node[N<<2];
#define M(x) node[x].mx
#define T(x) node[x].tag
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void pushup(CI now)
{
M(now)=max(M(now<<1),M(now<<1|1));
}
inline void remove(CI now,CI l,CI r)
{
if (l==r) return (void)(M(now)=-1e18); int mid=l+r>>1;
if (T(now)) M(now<<1)+=T(now),M(now<<1|1)+=T(now),T(now<<1)+=T(now),T(now<<1|1)+=T(now),T(now)=0;
if (M(now<<1)>lim) remove(LS); if (M(now<<1|1)>lim) remove(RS); pushup(now);
}
inline void apply(CI now,CI l,CI r,CI x)
{
T(now)+=x; if ((M(now)+=x)<=lim) return; remove(now,l,r);
}
inline void pushdown(CI now,CI l,CI r)
{
int mid=l+r>>1; if (T(now)) apply(LS,T(now)),apply(RS,T(now)),T(now)=0;
}
public:
inline void build(TN)
{
if (l==r) return (void)(M(now)=a[l]<=lim?a[l]:-1e18);
int mid=l+r>>1; build(LS); build(RS); pushup(now);
}
inline void modify(CI beg,CI end,CI x,TN)
{
if (beg<=l&&r<=end) return apply(now,l,r,x); int mid=l+r>>1; pushdown(now,l,r);
if (beg<=mid) modify(beg,end,x,LS); if (end>mid) modify(beg,end,x,RS); pushup(now);
}
inline int query(CI beg,CI end,TN)
{
if (beg<=l&&r<=end) return M(now); int mid=l+r>>1,ret=-1e18; pushdown(now,l,r);
if (beg<=mid) ret=max(ret,query(beg,end,LS)); if (end>mid) ret=max(ret,query(beg,end,RS)); return ret;
}
#undef M
#undef T
#undef TN
#undef LS
#undef RS
}T;
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%lld%lld%lld",&n,&m,&lim),i=1;i<=n;++i)
scanf("%lld",&a[i]); for (T.build(),i=1;i<=m;++i)
{
if (scanf("%lld%lld%lld",&opt,&l,&r),opt==2) printf("%lld\n",max(0LL,T.query(l,r)));
else scanf("%lld",&x),T.modify(l,r,x);
}
return 0;
}
C. 「NOIP2022模拟赛一 By yzxoi C」括号树
Pro
- 给一个\(n\)个节点的树,每个点的点权为
(
或)
,问所有合法括号序列路径中的最大嵌套数 - 定义合法括号序列路径\(S\)的最大嵌套数为\(\max_\limits{1\le i\le |S|} \sum_{j=1}^{i} (s_j='('?1:-1)\)
- \(n\le 40000\)
Sol
妈的调了半天发现是一个以前犯过的nt错误,和我的码风有很大的关系,而且极其隐蔽
大致地,就是在写点分治的过程中,把重心rt
开成了全局变量,然后在点分治中写的是:
inline void solve(const int& now)
在内部调用递归solve(rt)
之后直接爆炸……
然后导致根本没有把所有的点都做一遍,因此惨烈地G了
回到题目,这道题很经典,因为很早之前做过树上括号序列计数的问题,而且这种静态树上路径统计,肯定是点分治没跑了
考虑如何在分治中心统计答案,容易想到我们可以先保证子树内部分合法,然后遗留下若干个括号和另外的子树匹配
显然可以开一个桶来表示之前的子树中在遗留\(x\)个括号的情况下,前缀最大值是多少
维护的过程显然可以开一个栈来维护,而且要注意维护向下和向下的链时需要遗留的括号是不同的
最后注意要正反跑两边,因为我的写法是先算往下的再算往上的
具体实现由些细节,可以看代码,复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
#define pb push_back
using namespace std;
const int N=40005;
int n,x,a[N],ans,sz[N],bkt[N],mx,rt,stk[N],top,pfx[N],mpfx[N];
char ch; bool vis[N]; vector <int> v[N];
#define to v[now][i]
inline void getsz(CI now=1,CI fa=0)
{
sz[now]=1; for (RI i=0;i<v[now].size();++i) if (!vis[to]&&to!=fa) getsz(to,now),sz[now]+=sz[to];
}
inline void getrt(CI tot,CI now=1,CI fa=0)
{
int mn=tot-sz[now]; for (RI i=0;i<v[now].size();++i) if (!vis[to]&&to!=fa)
mn=max(mn,sz[to]),getrt(tot,to,now); if (mn<mx) mx=mn,rt=now;
}
inline void DFS1(CI now,CI fa)
{
pfx[now]=pfx[fa]+a[now]; mpfx[now]=max(mpfx[fa],pfx[now]);
bool ispop=0; if (stk[top]==1&&a[now]==-1) --top,ispop=1; else stk[++top]=a[now];
if (~bkt[top]&&(!top||stk[top]==-1)) ans=max(ans,max(bkt[top],mpfx[now])+top);
for (RI i=0;i<v[now].size();++i) if (!vis[to]&&to!=fa) DFS1(to,now);
if (ispop) stk[++top]=1; else --top;
}
inline void DFS2(CI now,CI fa)
{
pfx[now]=pfx[fa]+a[now]; mpfx[now]=min(mpfx[fa],pfx[now]);
bool ispop=0; if (stk[top]==-1&&a[now]==1) --top,ispop=1; else stk[++top]=a[now];
if (!top||stk[top]==1) bkt[top]=max(bkt[top],-mpfx[now]);
for (RI i=0;i<v[now].size();++i) if (!vis[to]&&to!=fa) DFS2(to,now);
if (ispop) stk[++top]=-1; else --top;
}
inline void solve(int now)
{
RI i; for (i=vis[now]=1,getsz(now),bkt[0]=0;i<=sz[now];++i) bkt[i]=-1;
for (i=0;i<v[now].size();++i) if (!vis[to])
{
top=0; stk[++top]=mpfx[now]=pfx[now]=a[now]; DFS1(to,now);
top=0; mpfx[now]=pfx[now]=0; DFS2(to,now); ans=max(ans,bkt[0]);
}
for (i=1,bkt[0]=0;i<=sz[now];++i) bkt[i]=-1;
for (i=v[now].size()-1;~i;--i) if (!vis[to])
{
top=0; stk[++top]=mpfx[now]=pfx[now]=a[now]; DFS1(to,now);
top=0; mpfx[now]=pfx[now]=0; DFS2(to,now); ans=max(ans,bkt[0]);
}
for (i=0;i<v[now].size();++i) if (!vis[to])
mx=sz[to]+1,getrt(sz[to],to,now),solve(rt);
}
#undef to
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i; for (scanf("%d",&n),i=2;i<=n;++i) scanf("%d",&x),v[i].pb(x),v[x].pb(i);
for (i=1;i<=n;++i)
{
ch=getchar(); while (ch!='('&&ch!=')') ch=getchar();
a[i]=ch=='('?1:-1;
}
return getsz(),mx=n+1,getrt(n),solve(rt),printf("%d",ans),0;
}
D. 「NOIP2022模拟赛一 By yzxoi D」三元组
Pro
- 给定一个集合\(A\),其中的元素为\(a\)
- 定义一种得分方式为存在无序三元组\((i,j,k)\),满足下列条件:
- \(i\ne j,j\ne k,i\ne k\)
- \(i,j,k\in A\)
- \(i|j,i|k\)
- 当该三元组满足条件时,你可以将\(k\)从\(A\)中删除,并将\(k\)加入初始为空的删除序列\(S\)的末尾,并获得一分
- 问最终达到最高分数的不同删除序列\(S\)数量
- \(|A|,a\le 60\)
Sol
刚开始题目看错了,以为是把\(i,j,k\)全删了,大呼SB题
结果写完发现不对劲,然后就没时间了qwq……
首先转化模型,若\(x|y\)我们就连一条\(x\to y\)的边,这样可以得到一个弱联通DAG
不难发现对于每个联通块,答案可以单独计算,并且入度为\(0\)的点是不能删除的
因此我们可以想到从这里入手,把删点转化为加点
但是如果只有入度为\(0\)的点也是不能加入别的数的,因此我们还需要至少一个其他的数\(y\),使得有边\(x\to y\),才能加入一个点\(z\)(有边\(x\to z\))
因此我们可以称当存在\(x\to y\)的边时,\(x\)就被激活了,可以用来加入别的点
因此我们考虑状压入度为\(0\)的点的激活状态,然后就可以DP了
因为对于每个联通块,入度为\(0\),并且有出边的点一定只能是\(1\sim 30\),并且选了一个点入度为\(0\),它的倍数就不能选
所以最多形成了\(1\sim 30\)的最长反链,入度为\(0\)的个数一定\(\le 15\),所以可以状压
设\(f_{i,S}\)表示加入了\(i\)个点,被激活的状态为\(S\)的方案数,转移的时候考虑先处理出\(t_S\)表示被激活的状态为\(S\)时能加入的点的个数
转移的话分不改变激活状态和改变激活状态两种,注意为了不记重状态不变时的系数为\(t_S-i\)
最后合并不同的联通块时还有一个细节就是两个序列是可以交叉的,因此要乘上一个组合系数
综上,设\(n=\frac{|A|}{4}\),复杂度为\(O(2^n\times n^2)\)
#include<cstdio>
#include<iostream>
#include<cstring>
#define RI int
#define CI const int&
#define pb push_back
using namespace std;
const int N=65,S=(1<<15)|5,mod=1e9+7;
int n,a[N],C[N][N],in[N],f[N][S],clk[N],tot,st[N],cnt,c[N],t[S],ans=1,len; bool vis[N];
inline void inc(int& x,CI y)
{
if ((x+=y)>=mod) x-=mod;
}
inline int sum(CI x,CI y)
{
return x+y>=mod?x+y-mod:x+y;
}
inline void DFS(CI now,CI tim)
{
if (clk[now]) return; ++tot; clk[now]=tim;
for (RI i=1;i<=n;++i) if (a[i]%a[now]==0||a[now]%a[i]==0) DFS(i,tim);
}
int main()
{
RI i,j,k,s; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
for (C[0][0]=i=1;i<=n;++i) for (C[i][0]=j=1;j<=n;++j)
C[i][j]=sum(C[i-1][j],C[i-1][j-1]);
for (i=1;i<=n;++i) for (j=1;j<=n;++j) if (a[i]!=a[j]&&a[i]%a[j]==0) ++in[i];
for (k=1;k<=n;++k) if (!clk[k])
{
tot=cnt=0; DFS(k,k); memset(f,0,sizeof(f)); memset(t,0,sizeof(t)); memset(vis,0,sizeof(vis));
for (i=1;i<=n;++i) if (clk[i]==k&&!in[i]) st[++cnt]=a[i],vis[i]=1;
if (tot-cnt<=1) continue; for (i=1;i<=n;++i)
if (clk[i]==k&&!vis[i])
for (j=1;j<=cnt;++j) if (a[i]%st[j]==0) c[i]|=(1<<j-1);
int m=(1<<cnt)-1; for (i=0;i<=m;++i) for (j=1;j<=n;++j)
if (clk[j]==k&&!vis[j]&&((i&c[j])==c[j])) ++t[i];
for (i=1;i<=n;++i) if (clk[i]==k&&!vis[i]) ++f[1][c[i]];
for (i=1;i<=tot-cnt-1;++i) for (s=0;s<=m;++s) if (f[i][s])
{
if (t[s]>i) inc(f[i+1][s],1LL*f[i][s]*(t[s]-i)%mod);
for (j=1;j<=n;++j) if (clk[j]==k&&!vis[j]&&((s|c[j])!=s)&&(s&c[j]))
inc(f[i+1][s|c[j]],f[i][s]);
}
int ret=0; for (i=0;i<=m;++i) inc(ret,f[tot-cnt][i]);
len+=tot-cnt-1; ans=1LL*ans*ret%mod*C[len][tot-cnt-1]%mod;
}
return printf("%d",ans),0;
}
Postscript
现在好菜的说,希望以后别犯nt错误了……