多校A层冲刺NOIP2024模拟赛24

多校A层冲刺NOIP2024模拟赛24

\(T1\) A. 选取字符串 \(100pts/100pts\)

  • 考虑建出失配树,然后等价于询问 \(\sum\limits_{S \subseteq \{ 0,1,2, \dots ,n \},|S|=k}dep_{\operatorname{LCA}\{ S \}}^{2}\)

  • 不妨从 \(\operatorname{LCA}\) 的角度考虑,统计 \(x\) 能作为多少个 \(|S|\)\(\operatorname{LCA}\) 。但是这也不可做,考虑 \(x\) 对答案的贡献。

  • luogu P5305 [GXOI/GZOI2019] 旧词 ,将单个点深度平方的贡献 \(dep_{x}^{2}\) 差分成路径上所有点深度的贡献 \(\sum\limits_{i \in (0 \to x)}(dep_{i}^{2}-dep_{fa_{i}}^{2})=\sum\limits_{i \in (0 \to x)}(2dep_{i}-1)\) ,再乘以 \(\dbinom{siz_{x}}{k}\) 即可。

  • \(\sum\limits_{i=0}^{n}(2dep_{i}-1)\dbinom{siz_{i}}{k}\) 即为所求。

    点击查看代码
    const ll p=998244353;
    ll siz[1000010],dep[1000010],nxt[1000010],jc[1000010],inv[1000010],jc_inv[1000010],ans=0;
    char s[1000010];
    vector<ll>e[1000010];
    void add(ll u,ll v)
    {
    	e[u].push_back(v);
    }
    ll C(ll n,ll m,ll p)
    {
    	return (n>=m&&n>=0&&m>=0)?(jc[n]*jc_inv[n-m])%p*jc_inv[m]%p:0;
    }
    void dfs(ll x,ll fa,ll k)
    {
    	siz[x]=1;
    	dep[x]=dep[fa]+1;
    	for(ll i=0;i<e[x].size();i++)
    	{
    		dfs(e[x][i],x,k);
    		siz[x]+=siz[e[x][i]];
    	}
    	ans=(ans+(2*dep[x]%p-1+p)%p*C(siz[x],k,p)%p)%p;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("string.in","r",stdin);
    	freopen("string.out","w",stdout);
    #endif
    	ll n,k,i,j;
    	scanf("%lld%s",&k,s+1);
    	n=strlen(s+1);
    	for(i=2,nxt[1]=j=0;i<=n;i++)
    	{
    		while(j>=1&&s[i]!=s[j+1])
    		{
    			j=nxt[j];
    		}
    		j+=(s[i]==s[j+1]);
    		nxt[i]=j;
    	}
    	inv[1]=jc[0]=jc_inv[0]=jc[1]=jc_inv[1]=1;
    	for(i=2;i<=n+1;i++)
    	{
    		inv[i]=(p-p/i)*inv[p%i]%p;
    		jc[i]=jc[i-1]*i%p;
    		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
    	}
    	for(i=1;i<=n;i++)
    	{
    		add(nxt[i],i);
    	}
    	dfs(0,n+1,k);
    	printf("%lld\n",ans);
    	return 0;
    }
    

\(T2\) B. 取石子 \(20pts/20pts\)

  • 部分分

    • \(20pts\) :暴力建博弈树。
    点击查看代码
    int a[50010];
    vector<pair<int,int> >ans;
    bool dfs(int last,int n)
    {
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=min(a[i],last);j++)
    		{
    			a[i]-=j;
    			bool tmp=dfs(j,n);	
    			a[i]+=j;
    			if(tmp==false)
    			{
    				return true;
    			}
    		}
    	}
    	return false;
    }
    bool work(int k,int n)
    {
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=min(a[i],k);j++)
    		{
    			a[i]-=j;
    			bool tmp=dfs(j,n);	
    			a[i]+=j;
    			if(tmp==false)
    			{
    				ans.push_back(make_pair(i,j));
    			}
    		}
    	}
    	return ans.size();
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("nim.in","r",stdin);
    	freopen("nim.out","w",stdout);
    #endif
    	int n,k,i;
    	scanf("%d%d",&n,&k);
    	for(i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    	}
    	sort(a+1,a+1+n);
    	if(work(k,n)==true)
    	{
    		printf("1\n");
    		sort(ans.begin(),ans.end());
    		for(i=0;i<ans.size();i++)
    		{
    			printf("%d %d\n",ans[i].first,ans[i].second);
    		}
    	}
    	else
    	{
    		printf("0\n");
    	}
    	return 0;
    }
    
  • 正解

    • 容易得到的博弈策略:
      • \(\sum\limits_{i=1}^{n}a_{i}\) 是奇数时,先手必胜,每次只取 \(1\) 个即可。
      • 否则,先手最优一定取偶数个且后面每个人能取偶数个就只能取偶数个,可以递归至 \(k \gets \left\lfloor \frac{k}{2} \right\rfloor,a_{i} \gets \left\lfloor \frac{a_{i}}{2} \right\rfloor\)
    • 解得先手必胜当且仅当存在 \(t \in [0,\left\lfloor \log_{2}k \right\rfloor]\) 使得 \((\sum\limits_{i=1}^{n}\left\lfloor \frac{a_{i}}{2^{t}} \right\rfloor) \equiv 1 \pmod{2}\) ,即 \((\bigoplus\limits_{i=1}^{n}a_{i}) \not\equiv {0} \pmod{2^{t}}\) ,可进一步规约至 \((\bigoplus\limits_{i=1}^{n}a_{i}) \not\equiv {0} \pmod{2^{\left\lfloor \log_{2}k \right\rfloor}}\)
    • 先手第一步必胜的策略一定是取 \((2x+1) \times \nu_{2}(\bigoplus\limits_{i=1}^{n}a_{i})=(2x+1) \times \operatorname{lowbit}(\bigoplus\limits_{i=1}^{n}a_{i})\) 个石子。枚举第一次取的堆 \(i \in [1,n]\) ,同时枚举先手能取的二进制各位(能取的一定是将 \(a_{i}\) 取出部分石子后与其他石子的异或和二进制表示下 \(1\) 的位置),当 \(>a_{i} \lor >k\) 时及时 break
    点击查看代码
    int a[50010];
    int lowbit(int x)
    {
    	return (x&(-x));
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("nim.in","r",stdin);
    	freopen("nim.out","w",stdout);
    #endif
    	int n,k,sum=0,tmp,ans,i,j;
    	scanf("%d%d",&n,&k);
    	for(i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    		sum^=a[i];
    	}
    	if(sum==0||lowbit(sum)>k)
    	{
    		printf("0\n");
    	}
    	else
    	{
    		printf("1\n");
    		for(i=1;i<=n;i++)
    		{
    			ans=0;
    			tmp=sum;
    			for(j=0;j<=30;j++)
    			{
    				if((tmp>>j)&1)
    				{
    					tmp^=(a[i]-ans);
    					ans+=(1<<j);
    					if(ans>a[i]||ans>k)
    					{
    						break;
    					}
    					tmp^=(a[i]-ans);
    					printf("%d %d\n",i,ans);
    				}
    			}
    		}
    	}
    	return 0;
    }
    

\(T3\) C. 均衡区间 \(25pts/25pts\)

  • 部分分

    • 测试点 \(1 \sim 2,4 \sim 6\):模拟。

      点击查看代码
      int a[1000010],ans[1000010];
      void work(int n)
      {
      	memset(ans,0,sizeof(ans));
      	for(int i=1;i<=n;i++)
      	{
      		int minn=0x7f7f7f7f,maxx=0;
      		for(int j=i;j<=n;j++)
      		{
      			minn=min(minn,a[j]);
      			maxx=max(maxx,a[j]);
      			if(minn!=min(a[i],a[j])&&maxx!=max(a[i],a[j]))
      			{
      				ans[i]++;
      			}
      		}
      	}
      }
      int main()
      {
      #define Isaac
      #ifdef Isaac
      	freopen("interval.in","r",stdin);
      	freopen("interval.out","w",stdout);
      #endif
      	int n,id,i;
      	scanf("%d%d",&n,&id);
      	for(i=1;i<=n;i++)
      	{
      		scanf("%d",&a[i]);
      	}
      	work(n);
      	for(i=1;i<=n;i++)
      	{
      		printf("%d ",ans[i]);
      	}
      	printf("\n");
      	reverse(a+1,a+1+n);
      	work(n);
      	reverse(ans+1,ans+1+n);
      	for(i=1;i<=n;i++)
      	{
      		printf("%d ",ans[i]);
      	}
      	printf("\n");
      	return 0;
      }
      
    • 测试点 \(3\) : 不横跨 \(i\) 时端点处一定同时为最值,横跨 \(i\) 时端点处一定有至少一个取到最小值,故输出 \(0\)

  • 正解

    • 以求解左端点为例。
    • \(xl_{i},nl_{i},xr_{i},nr_{i}\) 分别表示 \(i\) 左侧第一个比它大的数,左侧第一个比它小的数,右侧第一个比它大的数,右侧第一个比它小的数,单调栈预处理即可。
    • \(i\) 不为最值时,右端点 \(j\) 需满足 \((i,j]\) 中出现了 \(>a_{i}\)\(<a_{i}\) 的数,即 \(j \ge \max(xr_{i},nr_{i})\) ;但又需要保证 \(a_{j}\) 不为最值,类似地有 \(i \le \min(xl_{j},nl_{j})\)
    • 因空间略卡,使用扫描线加树状数组维护二维数点即可。
    点击查看代码
    struct BIT
    {
    	int c[1000010];
    	int lowbit(int x)
    	{
    		return (x&(-x));
    	}
    	void clear()
    	{
    		memset(c,0,sizeof(c));
    	}
    	void add(int n,int x,int val)
    	{
    		for(int i=x;i<=n;i+=lowbit(i))
    		{
    			c[i]+=val;
    		}
    	}
    	int getsum(int x)
    	{
    		int ans=0;
    		for(int i=x;i>=1;i-=lowbit(i))
    		{
    			ans+=c[i];
    		}
    		return ans;
    	}
    }T;
    struct node
    {
    	int pos,x,val,id;
    }q[2000010];
    int a[1000010],L[1000010],R[1000010],ans[1000010],cnt;
    stack<int>s1,s2;
    bool cmp(node a,node b)
    {
    	return a.pos<b.pos;
    }
    void add(int pos,int x,int val,int id)
    {
    	cnt++;
    	q[cnt].pos=pos;
    	q[cnt].x=x;
    	q[cnt].val=val;
    	q[cnt].id=id;
    }
    void work(int n)
    {
    	cnt=0;
    	memset(q,0,sizeof(q));
    	memset(ans,0,sizeof(ans));
    	while(s1.empty()==0)
    	{
    		s1.pop();
    	}
    	while(s2.empty()==0)
    	{
    		s2.pop();
    	}
    	for(int i=1;i<=n;i++)
    	{
    		while(s1.empty()==0&&a[s1.top()]<=a[i])
    		{
    			s1.pop();
    		}
    		while(s2.empty()==0&&a[s2.top()]>=a[i])
    		{
    			s2.pop();
    		}
    		L[i]=min((s1.empty()==0)?s1.top():0,(s2.empty()==0)?s2.top():0);
    		s1.push(i);
    		s2.push(i);
    	}
    	while(s1.empty()==0)
    	{
    		s1.pop();
    	}
    	while(s2.empty()==0)
    	{
    		s2.pop();
    	}
    	for(int i=n;i>=1;i--)
    	{
    		while(s1.empty()==0&&a[s1.top()]<=a[i])
    		{
    			s1.pop();
    		}
    		while(s2.empty()==0&&a[s2.top()]>=a[i])
    		{
    			s2.pop();
    		}
    		R[i]=max((s1.empty()==0)?s1.top():n+1,(s2.empty()==0)?s2.top():n+1);
    		s1.push(i);
    		s2.push(i);
    		if(R[i]<=n)
    		{
    			add(R[i]-1,i,-1,i);
    			add(n,i,1,i);
    		}
    	}
    	sort(q+1,q+1+cnt,cmp);
    	for(int i=1,j=1;i<=cnt;i++)
    	{
    		for(;j<=q[i].pos;j++)
    		{
    			if(L[j]>=1)
    			{
    				T.add(n,L[j],1);
    			}
    		}
    		ans[q[i].id]+=q[i].val*(T.getsum(n)-T.getsum(q[i].x-1));
    	}
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("interval.in","r",stdin);
    	freopen("interval.out","w",stdout);
    #endif
    	int n,id,i;
    	scanf("%d%d",&n,&id);
    	for(i=1;i<=n;i++)
    	{
    		scanf("%d",&a[i]);
    	}
    	work(n);
    	for(i=1;i<=n;i++)
    	{
    		printf("%d ",ans[i]);
    	}
    	printf("\n");
    	reverse(a+1,a+1+n);
    	work(n);
    	reverse(ans+1,ans+1+n);
    	for(i=1;i<=n;i++)
    	{
    		printf("%d ",ans[i]);
    	}
    	printf("\n");
    	return 0;
    }
    

\(T4\) D. 禁止套娃 \(10pts/10pts\)

  • 部分分

    • \(10pts\) :爆搜求本质不同子序列个数。

      点击查看代码
      const ll p=1000000007;
      int a[5010],ans=0;
      vector<int>state,tmp;
      map<vector<int>,bool>s1,s2;
      void dfs2(int pos)
      {
          if(pos==state.size())
          {
              if(s2.find(tmp)==s2.end())
              {
                  s2[tmp]=1;
                  ans=(ans+1)%p;
              }
          }
          else
          {
              dfs2(pos+1);
              tmp.push_back(state[pos]);
              dfs2(pos+1);
              tmp.pop_back();
          }
      }
      void dfs(int pos,int n)
      {
          if(pos==n+1)
          {
              if(s1.find(state)==s1.end())
              {
                  s2.clear();
                  dfs2(0);
                  s1[state]=1;
              }
          }
          else
          {
              dfs(pos+1,n);
              state.push_back(a[pos]);
              dfs(pos+1,n);
              state.pop_back();
          }
      }
      int main()
      {
      #define Isaac
      #ifdef Isaac
          freopen("nest.in","r",stdin);
          freopen("nest.out","w",stdout);
      #endif
          int n,i;
          cin>>n;
          for(i=1;i<=n;i++)	
          {
              cin>>a[i];
          }
          dfs(1,n);
          cout<<ans<<endl;
          return 0;
      }
      

  • 正解

    • 一个元素的贡献会来自从属关系的内外两层,考虑枚举内层的选择情况,记录选择的两个相邻内层点间的外层方案数量。
    • 内外层的限制条件均为设相邻两个数分别为 \(x,y\) ,则 \(a_{x+1 \sim y-1}\) 中不存在 \(=a_{y}\) 的值。
    • \(f_{i}\) 表示处理到 \(i\) 时内外层末尾均选择 \(i\) 的方案数,考虑从 \(f_{j}(j \in [0,i-1])\) 转移时,考虑 \(a_{j+1 \sim i-1}\) 如何选择外层,设选择的下标集合为 \(S\) (不妨钦定内部元素单调递增),则 \(S\) 需满足以下限制条件。
      • \(S\) 中相邻两个数 \(x,y\) 满足 \(a_{x+1 \sim y-1}\) 中不存在 \(=a_{y}\) 的数。
      • \(a_{\max\{ S \}+1 \sim i-1}\) 中不存在 \(=a_{i}\) 的数。
      • \(\forall x \in S,a_{x} \ne a_{i}\) .
    • 限制条件 \(1,3\) 是容易进行状态设计并转移的,但限制条件 \(2\) 难以处理,考虑计算出总方案数再减去不合法方案数。具体地,设 \(g_{i,j}\) 表示在 \([i+1,j-1]\) 中选取外层的合法方案数,则有 \(f_{i}=\sum\limits_{j=0}^{i-1}(g_{j,i}-g_{j,pre_{i}})f_{j}\)
    • 边界为 \(f_{0}=1\) ,最后钦定一个必选点 \(n+1\) 用于统计答案即可。
    • 难点来到了怎么处理 \(\{ g \}\) ,仍考虑前缀和优化。不妨从右往左回退处理,沿途设 \(h_{j}\) 表示 \([j+1,i-1]\) 的合法本质不同子序列数量,转移同 \(30pts\) 中第一种写法。
    点击查看代码
    const int p=1000000007;
    int a[5010],f[5010],g[5010][5010],h[5010],last[5010],pre[5010],suf[5010];
    int main()
    {
    #define Isaac
    #ifdef Isaac
    	freopen("nest.in","r",stdin);
    	freopen("nest.out","w",stdout);
    #endif
    	int n,sum,i,j;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		pre[i]=last[a[i]];
    		last[a[i]]=i;
    	}
    	fill(last+1,last+1+n,n+1);
    	for(i=n;i>=1;i--)
    	{
    		suf[i]=last[a[i]];
    		last[a[i]]=i;
    	}
    	f[0]=1;
    	for(i=1;i<=n+1;i++)
    	{
    		sum=1;
    		for(j=i-1;j>=0;j--)
    		{
    			g[j][i]=sum;
    			if(a[i]!=a[j])
    			{
    				h[j]=sum;
    				sum=(2*sum%p-h[suf[j]]+p)%p;//对应原 30pts 中的减去 pre 的贡献
    			}
    		}
    		for(j=0;j<=i-1;j++)
    		{
    			f[i]=(f[i]+1ll*(g[j][i]-g[j][pre[i]]+p)%p*f[j]%p)%p;
    		}
    	}
    	cout<<f[n+1]<<endl;
    	return 0;
    }
    

总结

  • \(T3\) 性质场上没推出来。

后记

  • 下发的题面 \(PDF\) 和原题解 \(PDF\)\(\LaTeX\) 炸了。
  • \(T1\) 题面 \(i,j\) 写反了。
  • \(T3\) 题解 \(i \le L_{j}\) 打成了 \(i \ge L_{j}\)
posted @ 2024-11-19 19:35  hzoi_Shadow  阅读(47)  评论(3编辑  收藏  举报
扩大
缩小