csp-s模拟11

csp-s模拟11

\(T1\) T2203. 玩水 (water) \(100pts\)

  • 定义一个点 \((i,j)\) 是分点当且仅当 \(s_{i,j+1}=s_{i+1,j}\) ,而一个点 \((i,j)\) 是合点当且仅当 \((i-1,j-1)\) 是分点。

  • 先考虑若只有两个人时,只要存在一个分点就一定有解。

  • 扩展到三个人时,若存在一个合点可以通过向右或向下走到达另外一个分点或存在两个相邻的分点就一定有解,于是就转化为了二维偏序问题,因 \(n,m \le 1000\) 故二维前缀和维护即可。

    点击查看代码
    int sum[1010][1010],vis[1010][1010];
    char c[1010][1010];
    int main()
    {
    	freopen("water.in","r",stdin);
    	freopen("water.out","w",stdout);
    	int t,n,m,flag,i,j,k;
    	cin>>t;
    	for(k=1;k<=t;k++)
    	{
    		flag=0;
    		cin>>n>>m;
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=m;j++)
    			{
    				cin>>c[i][j];
    			}
    		}
    		for(i=1;i<=n;i++)
    		{
    			for(j=1;j<=m;j++)
    			{
    				vis[i][j]=(i+1<=n&&j+1<=m&&c[i+1][j]==c[i][j+1]);
    				sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+vis[i][j];
    				flag|=(vis[i][j]==1&&(vis[i-1][j]==1||vis[i][j-1]==1||sum[i-1][j-1]>=1));
    			}
    		}
    		cout<<flag<<endl;
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T2\) T2212. AVL 树 \(20pts\)

  • 部分分

    • \(20pts\) :爆搜。
    点击查看代码
    int ch[500010][2],fa[500010],vis[500010],h[500010];
    vector<int>ans,tmp;
    bool cmp(vector<int>a,vector<int>b)
    {
    	if(a.size()<b.size())
    	{
    		return false;
    	}
    	for(int i=0;i<a.size();i++)
    	{
    		if(a[i]>b[i])
    		{
    			return false;
    		}
    		if(a[i]<b[i])
    		{
    			return true;
    		}
    	}
    	return false;
    }
    void print(int x)
    {
    	if(x==0||vis[x]==0)
    	{
    		return;
    	}
    	h[x]=1;
    	print(ch[x][0]);
    	tmp.push_back(x);
    	print(ch[x][1]);
    	h[x]+=max(h[ch[x][0]],h[ch[x][1]]);
    }
    void dfs(int pos,int n,int k,int rt)
    {
    	if(pos==n+1)
    	{
    		if(k==0)
    		{
    			int flag=1;
    			for(int i=1;i<=n;i++)
    			{
    				if(vis[i]==1)
    				{
    					flag&=vis[fa[i]];
    				}
    			}
    			if(flag==1)
    			{
    				tmp.clear();
    				memset(h,0,sizeof(h));
    				print(rt); 
    				for(int i=1;i<=n;i++)
    				{
    					if(vis[i]==1)
    					{
    						flag&=(abs(h[ch[i][0]]-h[ch[i][1]])<=1);
    					}
    				}
    				if(flag==1&&cmp(ans,tmp)==false)
    				{
    					ans=tmp;
    				}
    			}
    		}
    	}
    	else
    	{
    		if(k>=1)
    		{
    			vis[pos]=1;
    			dfs(pos+1,n,k-1,rt);
    		}
    		if(pos!=rt)
    		{
    			vis[pos]=0;
    			dfs(pos+1,n,k,rt);
    		}
    	}
    }
    int main()
    {
    	freopen("avl.in","r",stdin);
    	freopen("avl.out","w",stdout);
    	int n,k,rt=0,i;
    	cin>>n>>k;
    	for(i=1;i<=n;i++)
    	{
    		cin>>fa[i];
    		if(fa[i]==-1)
    		{
    			fa[i]=0;
    			rt=i;
    		}
    		else
    		{
    			ch[fa[i]][fa[i]<i]=i;
    		}
    	}
    	vis[0]=1;
    	dfs(1,n,k,rt);
    	memset(vis,0,sizeof(vis));
    	for(i=0;i<ans.size();i++)
    	{
    		vis[ans[i]]=1;
    	}
    	for(i=1;i<=n;i++)
    	{
    		cout<<vis[i];
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 正解

    • 左根右的中序遍历在删除整棵子树的情况下,贪心策略为尽可能多地选择左子树的点,优先递归左子树。
    • 考虑如何判断当前节点是否可以保留。回溯它的祖先节点直至根,对于该节点是左儿子的情况计算出保留这个节点至少需要右子树中有多少个节点。若剩下的 \(k\) 足够,那么就可以保留。
      • 预处理 \(f_{i}\) 表示高度为 \(i\) 的 AVL 树所包含的最少节点数量,递推式为 \(f_{i}=\begin{cases} 1 & i=1 \\ 2 & i=2 \\ f_{i-1}+f_{i-2}+1 & i>2 \end{cases}\)
    • 同时还需要保证删完后的树是一棵平衡树,故左儿子不能选太多的点。设 \(h_{x}\) 表示以 \(x\) 为根的子树内的最大深度, \(used_{x}\) 表示以 \(x\) 为根的子树内的已经选择过的最大深度, \(lim_{x}\) 表示如果要保留 \(x\) 至少需要的深度。
    点击查看代码
    int ch[500010][2],fa[500010],f[500010],ans[500010],h[500010],dep[500010],lim[500010],used[500010];
    #define lson(rt) (ch[rt][0])
    #define rson(rt) (ch[rt][1])
    void dfs(int x)
    {
    	if(x==0)
    	{
    		return;
    	}
    	h[x]=dep[x]=dep[fa[x]]+1;
    	dfs(lson(x));
    	dfs(rson(x));
    	h[x]=max(h[x],max(h[lson(x)],h[rson(x)]));
    }
    void pushup(int x,int &k)
    {
    	used[x]=max(used[x],dep[x]);
    	for(int rt=x;rt!=0;rt=fa[rt])
    	{
    		k-=(ans[rt]==0);//中途往上更新没有选中的点
    		ans[rt]=1;
    		used[fa[rt]]=max(used[fa[rt]],dep[x]);
    		if(lson(fa[rt])==rt&&rson(fa[rt])!=0)
    		{
    			lim[rson(fa[rt])]=max(lim[rson(fa[rt])],used[fa[rt]]-1);//符合 AVL 树高度的限制
    		}
    	}
    }
    bool check(int x,int k)
    {
    	int sum=0;
    	for(int rt=x;rt!=0;rt=fa[rt])
    	{
    		sum+=(ans[rt]==0);
    		if(lson(fa[rt])==rt)
    		{
    			sum+=f[max({used[fa[rt]]-1,dep[x]-1,lim[rson(fa[rt])]})-dep[fa[rt]]];//计算额外需要多少个点
    		}
    	}
    	return sum<=k;
    }
    void solve(int x,int &k)
    {
    	if(x==0)
    	{
    		return;
    	}
    	if(check(x,k)==true)
    	{
    		pushup(x,k);
    	}
    	if(lson(x)!=0&&rson(x)!=0)
    	{
    		if(h[lson(x)]>=lim[x])
    		{
    			lim[lson(x)]=max(lim[lson(x)],lim[x]);//左子树已经够了,直接更新左子树
    			lim[rson(x)]=max(lim[rson(x)],lim[x]-1);
    		}
    		else
    		{
    			lim[lson(x)]=max(lim[lson(x)],lim[x]-1);//左子树不够
    			lim[rson(x)]=max(lim[rson(x)],lim[x]);
    		}
    	}
    	else
    	{
    		if(lson(x))
    		{
    			lim[lson(x)]=max(lim[lson(x)],lim[x]);
    		}
    		if(rson(x))
    		{
    			lim[rson(x)]=max(lim[rson(x)],lim[x]);
    		}
    	}
    	solve(lson(x),k);
    	solve(rson(x),k);
    }
    int main()
    {
    	freopen("avl.in","r",stdin);
    	freopen("avl.out","w",stdout);
    	int n,k,rt=0,i;
    	cin>>n>>k;
    	f[1]=1;
    	for(i=2;i<=30;i++)
    	{
    		f[i]=f[i-1]+f[i-2]+1;
    	}
    	for(i=1;i<=n;i++)
    	{
    		cin>>fa[i];
    		if(fa[i]==-1)
    		{
    			fa[i]=0;
    			rt=i;
    		}
    		else
    		{
    			ch[fa[i]][fa[i]<i]=i;
    		}
    	}
    	dfs(rt);
    	solve(rt,k);
    	for(i=1;i<=n;i++)
    	{
    		cout<<ans[i];
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T3\) T2211. 暴雨 \(20pts\)

  • 部分分

    • \(20pts\)
      • \(h'_{i}\) 表示最终第 \(i\) 块土地的高度,有 \(\sum\limits_{i=1}^{n}\min(\max\limits_{j=1}^{i} \{ h'_{j} \},\max\limits_{j=i}^{n} \{ h'_{j} \})-h'_{i}\) 即为所求。
      • 爆搜即可。
    点击查看代码
    const ll p=1000000007;
    ll h[25010],tmp[25010],lmax[25010],rmax[25010],ans=0;
    void dfs(ll pos,ll n,ll k)
    {
    	if(pos==n+1)
    	{
    		if(k==0)
    		{
    			ll sum=0;
    			lmax[0]=rmax[n+1]=0;
    			for(ll i=1;i<=n;i++)
    			{
    				lmax[i]=max(lmax[i-1],tmp[i]);
    			}
    			for(ll i=n;i>=1;i--)
    			{
    				rmax[i]=max(rmax[i+1],tmp[i]);
    			}
    			for(ll i=1;i<=n;i++)
    			{
    				sum^=((min(lmax[i],rmax[i])-tmp[i])&1);
    			}
    			if(sum==0)
    			{
    				ans=(ans+1)%p;
    			}
    		}		
    	}
    	else
    	{
    		tmp[pos]=h[pos];
    		dfs(pos+1,n,k);
    		if(k>=1)
    		{
    			tmp[pos]=0;
    			dfs(pos+1,n,k-1);
    		}
    	}
    }
    int main()
    {
    	freopen("rain.in","r",stdin);
    	freopen("rain.out","w",stdout);
    	ll n,k,i;
    	cin>>n>>k;
    	for(i=1;i<=n;i++)
    	{
    		cin>>h[i];
    	}
    	dfs(1,n,k);
    	cout<<ans<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 正解

    • \(f_{i,j,k,0/1}\) 表示前 \(i\) 块土地中最大值为 \(j\) 并要求在后面存在一个高度至少为 \(j\) 的土地,且已经铲平了 \(k\) 块得到的积水体积为偶数/奇数的方案数。
      • 后缀状态设计同理。
    • 因为至多会去掉 \(k\) 块土地,所以最大值最多会更改 \(k+1\) 遍。故时间复杂度为 \(O(nk^{2})\)
    • 使用 mapset 预处理每一段(包括前缀和后缀)前 \(k+1\) 大的数。转移的过程中或提前离散化即可。
    • 最后枚举最大值所在列,钦定左边都小于它,右边都小于等于它,防止重复计算多个最大值的情况。
    点击查看代码
    const ll p=1000000007;
    struct cmp
    {
    	bool operator () (int a,int b) const 
    	{
    		return a>b;
    	}
    };
    int h[25010],hh[25010],f[2][25010][30][30][2];
    vector<int>pre[25010],suf[25010];
    set<int,cmp>s;
    set<int,cmp>::iterator it;
    void add(int x,int m,vector<int> &num)
    {
    	s.insert(x);
    	if(s.size()>m+1)
    	{
    		s.erase(--s.end());
    	}
    	for(it=s.begin();it!=s.end();it++)
    	{
    		num.push_back(*it);
    	}
    }
    void init(int n,int m,int pd,vector<int>num[],int h[])
    {
    	int pos=0,flag;
    	s.clear();
    	for(int i=0;i<=n;i++)
    	{
    		add(h[i],m,num[i]);
    	}
    	f[pd][0][0][0][0]=1;
    	for(int i=1;i<=n;i++)
    	{
    		pos=0;
    		flag=1;
    		for(int j=0;j<num[i].size();j++)
    		{
    			pos=(num[i][j]==h[i])?j:pos;
    		}
    		for(int j=0;j<num[i-1].size();j++)
    		{
    			flag=(num[i-1][j]==h[i])?0:flag;//没有 h[i] 这一位,离散化后的位置加一
    		}
    		for(int j=0;j<num[i-1].size();j++)
    		{
    			for(int k=0;k<=m;k++)
    			{
    				if(h[i]>num[i-1][j])//保证了 pos!=0
    				{
    					for(int v=0;v<=1;v++)
    					{
    						f[pd][i][pos][k][v]=(f[pd][i][pos][k][v]+f[pd][i-1][j][k][v])%p;
    						f[pd][i][j+flag][k+1][(num[i-1][j]+v)&1]=(f[pd][i][j+flag][k+1][(num[i-1][j]+v)&1]+f[pd][i-1][j][k][v])%p;
    					}
    				}
    				else
    				{
    					for(int v=0;v<=1;v++)
    					{
    						f[pd][i][j][k][(num[i-1][j]-h[i]+v)&1]=(f[pd][i][j][k][(num[i-1][j]-h[i]+v)&1]+f[pd][i-1][j][k][v])%p;
    						f[pd][i][j][k+1][(num[i-1][j]+v)&1]=(f[pd][i][j][k+1][(num[i-1][j]+v)&1]+f[pd][i-1][j][k][v])%p;
    					}
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	freopen("rain.in","r",stdin);
    	freopen("rain.out","w",stdout);
    	int n,m,ans=0,l,r,i,j,k,v;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		cin>>h[i];
    		hh[i]=h[i];
    	}
    	reverse(hh+1,hh+1+n);
    	init(n,m,0,pre,h);
    	init(n,m,1,suf,hh);
    	for(i=1;i<=n;i++)
    	{
    		for(k=0;k<=m;k++)
    		{
    			for(v=0;v<=1;v++)
    			{
    				l=r=0;
    				for(j=0;j<pre[i-1].size();j++)
    				{
    					l=(l+(pre[i-1][j]<h[i])*f[0][i-1][j][k][v])%p;
    				}
    				for(j=0;j<suf[n-(i+1)+1].size();j++)
    				{
    					r=(r+(suf[n-(i+1)+1][j]<=h[i])*f[1][n-(i+1)+1][j][m-k][v])%p;
    				}
    				ans=(ans+1ll*l*r%p)%p;
    			}
    		}
    	}
    	cout<<ans<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T4\) T2197. 置换 \(0pts\)

\(T5\) T2198. 传统题 \(10pts\)

  • 部分分

    • \(10pts\) :爆搜。

      点击查看代码
      ll a[300010],ans=0;
      void dfs(ll pos,ll n,ll m,ll p)
      {
      	if(pos==n+1)
      	{
      		ll maxx=0,len=0;
      		for(ll i=1;i<=n;i++)
      		{
      			len=(a[i]==a[i-1])?len+1:1;
      			maxx=max(maxx,len);
      		}
      		ans=(ans+maxx)%p;
      	}
      	else
      	{
      		for(ll i=1;i<=m;i++)
      		{
      			a[pos]=i;
      			dfs(pos+1,n,m,p);
      		}
      	}
      }
      int main()
      {
      	freopen("sequence.in","r",stdin);
      	freopen("sequence.out","w",stdout);
      	ll n,m,p;
      	cin>>n>>m>>p;
      	dfs(1,n,m,p);
      	cout<<ans<<endl;
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
      
    • \(50pts\)

  • 正解

    • \(F(p)\) 表示使 \(p\) 成立的方案数, \(len\) 为序列最长连续相同子段长度。那么 \(\sum\limits_{i=1}^{n}F(len=i) \times i=\sum\limits_{i=1}^{n}F(len \ge i)\) 即为所求。
    • 正难则反有 \(\begin{aligned} &\sum\limits_{i=1}^{n}F(len \ge i) \\ &=\sum\limits_{i=1}^{n}m^{n}-F(len<i) \\ &=nm^{n}-\sum\limits_{i=1}^{n-1}F(len \le i) \end{aligned}\) ,现在开始考虑怎么计算 \(\sum\limits_{i=0}^{n-1}F(len \le i)\)
    • 枚举分成了 \(j\) 个非空段 \(\sum\limits_{i=0}^{n-1}\sum\limits_{j=1}^{n}m(m-1)^{j-1}d_{i,j}\) ,其中 \(d_{i,j}\) 是系数。
      • \(d_{i,j}\) 表示 \(\sum\limits_{k=1}^{j}x_{k}=n(\forall k \in [1,j],x_{k} \le i)\) 的正整数解的方案数。
    • 求解 \(d_{i,j}\) 的式子非常经典,在 冲刺CSP联训模拟2 T2 P295. 工地难题 已经写过(本题中要求非空)。二项式反演后有 \(d_{i,j}=\sum\limits_{k=0}^{j}(-1)^{k}\dbinom{k}{0}\dbinom{j}{k}\dbinom{n-ik-1}{j-1}=\sum\limits_{k=0}^{j}(-1)^{k}\dbinom{j}{k}\dbinom{n-ik-1}{j-1}\)
    • 此时的除 \(nm^{n}\) 外的式子为 \(\sum\limits_{i=0}^{n-1}\sum\limits_{j=1}^{n}m(m-1)^{j-1}\sum\limits_{k=0}^{j}(-1)^{k}\dbinom{j}{k}\dbinom{n-ik-1}{j-1}\)
    • 先将 \(m\) 和枚举 \(k\) 提前,并加上 \((i+1)k \le n\) 的条件(钦定有 \(k\) 个超重),有 \(m\sum\limits_{i=0}^{n-1}\sum\limits_{k=0}^{\left\lfloor \frac{n}{i+1} \right\rfloor}(-1)^{k}\sum\limits_{j=k}^{n}(m-1)^{j-1}\dbinom{j}{k}\dbinom{n-ik-1}{j-1}\)
    • 再将 \(\dbinom{n-ik-1}{j-1}=\dfrac{j}{n-ik}\dbinom{n-ik}{j}\) 拆开,有 \(m\sum\limits_{i=0}^{n-1}\sum\limits_{k=0}^{\left\lfloor \frac{n}{i+1} \right\rfloor}(-1)^{k}\frac{1}{n-ik}\sum\limits_{j=k}^{n}j(m-1)^{j-1}\dbinom{n-ik}{j}\dbinom{j}{k}\)
    • 枚举 \(i,k\) 通过调和计数已经可以 \(O(n \log n)\) 处理了,瓶颈在枚举后面的 \(j\)
    • 后面的式子已经比较难化简了,考虑组合意义。
      • 式子分为 \(j(m-1)^{j-1}\)\(\dbinom{n-ik}{j}\dbinom{j}{k}\) 两部分。
      • 先从 \(n-ik\) 个元素中选出 \(j\) 个元素;再在这 \(j\) 个元素中选出 \(k\) 个元素备用,同时选出一个元素 \(x\) ,并对剩下的 \(j-1\) 个元素用 \(m-1\) 种颜色染色。
      • \(\dbinom{n-ik}{j}\dbinom{j}{k}\) 改写作 \(\dbinom{n-ik}{k}\dbinom{n-ik-k}{j-k}\) ,考虑先在这 \(n-ik\) 个元素中选出 \(k\) 个元素备用,再对 \(x\) 是否在这 \(k\) 个元素中进行分讨。
      • \(x\) 在这 \(k(k \ge 1)\) 个元素中,后面就要求在 \(n-ik-k\) 个元素中选出 \(j-k\) 个元素并进行染色,而 \(j-k\) 在此时就不再那么重要了,可以直接当做枚举子集进行染色(多一种元素表示不属于枚举出的子集)。此时方案数为 \(\dbinom{k}{1}(m-1)^{k-1}m^{n-ik-k}=k(m-1)^{k-1}m^{n-ik-k}\)
      • \(x\) 不在这 \(k\) 个元素中,就先在 \(n-ik-k(n-ik-k \ge 1)\) 中选出一个元素然后对剩余元素进行染色。此时方案数为 \(\dbinom{n-ik-k}{1}(m-1)^{k}m^{n-ik-k-1}=(n-ik-k)(m-1)^{k}m^{n-ik-k-1}\)
    • 最终,有 \(nm^{n}-m\sum\limits_{i=0}^{n-1}\sum\limits_{k=0}^{\left\lfloor \frac{n}{i+1} \right\rfloor}(-1)^{k}\dbinom{n-ik}{k}\frac{k(m-1)^{k-1}m^{n-ik-k}+(n-ik-k)(m-1)^{k}m^{n-ik-k-1}}{n-ik}\) 即为所求。
    • 注意 \((m-1)^{k-1}\)\(m^{n-ik-k-1}\) 的处理。
    点击查看代码
    ll inv[300010],jc[300010],jc_inv[300010],mi[300010][2];
    ll qpow(ll a,ll b,ll p)
    {
    	ll ans=1;
    	while(b)
    	{
    		if(b&1)
    		{
    			ans=ans*a%p;
    		}
    		b>>=1;
    		a=a*a%p;
    	}
    	return ans;
    }
    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;
    }
    int main()
    {
    	freopen("sequence.in","r",stdin);
    	freopen("sequence.out","w",stdout);
    	ll n,m,p,ans=0,tmp,tmp1,tmp2,i,k;
    	cin>>n>>m>>p;
    	jc[0]=jc_inv[0]=mi[0][0]=mi[0][1]=1;
    	for(i=1;i<=n;i++)
    	{
    		inv[i]=qpow(i,p-2,p);
    		jc[i]=jc[i-1]*i%p;
    		jc_inv[i]=jc_inv[i-1]*inv[i]%p;
    		mi[i][0]=mi[i-1][0]*(m-1)%p;
    		mi[i][1]=mi[i-1][1]*m%p;
    	}
    	for(i=0;i<=n-1;i++)
    	{
    		for(k=0;k<=n/(i+1);k++)
    		{
    			tmp1=(k*mi[max(k-1,0ll)][0]%p)*mi[n-i*k-k][1]%p;
    			tmp2=((n-i*k-k)*mi[k][0]%p)*mi[max(n-i*k-k-1,0ll)][1];
    			tmp=(((tmp1+tmp2)%p)*inv[n-i*k]%p)*C(n-i*k,k,p)%p;
    			if(k%2==0)
    			{
    				ans=(ans+tmp)%p;
    			}
    			else
    			{
    				ans=(ans-tmp+p)%p;
    			}
    		}
    	}
    	cout<<(n*mi[n][1]%p-ans*m%p+p)%p<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

总结

  • 貌似题目出难了,基本上是全程罚坐。
  • \(T2\) 第一遍读题时没注意到仍要求是一棵平衡树。

后记

  • 来自比赛公告的温馨提示。

  • \(T1,T2,T3,T4\) 被分在了 \(4\) 个 PDF 里,且样例也分成了 \(4\) 个 zip ,需要单独下载。但除 \(T5\) 外题解合起来放在了一个 \(PDF\) 里。

  • \(T4\) 之前被出在了 初三奥赛模拟测试3 里,所以 \(huge\)\(T5\) 放了进来,让我们最后再写“我们做过的且码量较大的” \(T4\)

posted @ 2024-10-14 18:02  hzoi_Shadow  阅读(59)  评论(3编辑  收藏  举报
扩大
缩小