NOIP2022模拟赛一 By yzxoi 8.15

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错误了……

posted @ 2022-08-15 16:25  空気力学の詩  阅读(103)  评论(0编辑  收藏  举报