暑假集训CSP提高模拟18

暑假集训CSP提高模拟18

组题人: @worldvanquisher | @joke3579

\(T1\) P227. Mortis \(0pts\)

  • 原题: [ABC302G] Sort from 1 to 4 | luogu P1459 [USACO2.1] 三值的排序 Sorting a Three-Valued Sequence

  • 部分分

    • \(0pts\) :输出逆序对个数。
  • 正解

    • \(\{ a \}\) 排序后的序列为 \(\{ b \}\)
    • 然后大力分讨。
      • \(a_{i}=b_{i}\) 说明不需要进行交换。
      • 否则,若存在 \(i \ne j\) 使得 \(a_{i}=b_{j},a_{j}=b_{i}\) 交换两者即可,对答案产生 \(1\) 的贡献。
      • 再否则,若存在 \(i \ne j \ne k\) 使得 \(a_{i}=b_{j},a_{j}=b_{k},a_{k}=b_{i}\) ,顺序交换三者即可,对答案产生 \(2\) 的贡献。
      • 再否则,剩下的数一定形如 \(a_{i}=b_{j},a_{j}=b_{k},a_{k}=b_{h},a_{h}=b_{i}(i \ne j \ne k \ne h)\) ,顺序交换四者即可,对答案产生 \(3\) 的贡献。
    • 具体地,我们记录 \(c_{i,j}\) 表示 \(i\) 最终位置上 \(j\) 的个数。不对情况 \(1\) 进行统计,仅枚举情况 \(2,3\) ,剩下的就是情况 \(4\) 的贡献。
    点击查看代码
    int a[200010],b[200010],c[5][5];
    int main()
    {
    	int n,ans=0,sum=0,i,j,k;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		b[i]=a[i];
    	}
    	sort(b+1,b+1+n);
    	for(i=1;i<=n;i++)
    	{
    		if(b[i]!=a[i])
    		{
    			sum++;
    			c[b[i]][a[i]]++;
    		}
    	}
    	for(i=1;i<=4;i++)
    	{
    		for(j=1;j<=4;j++)
    		{
    			if(i!=j)
    			{
    				while(c[i][j]>=1&&c[j][i]>=1)
    				{
    					ans++;
    					sum-=2;
    					c[i][j]--;
    					c[j][i]--;
    				}
    			}
    		}
    	}
    	for(i=1;i<=4;i++)
    	{
    		for(j=1;j<=4;j++)
    		{
    			for(k=1;k<=4;k++)
    			{
    				if(i!=j&&i!=k&&j!=k)
    				{
    					while(c[i][j]>=1&&c[j][k]>=1&&c[k][i]>=1)
    					{
    						ans+=2;
    						sum-=3;
    						c[i][j]--;
    						c[j][k]--;
    						c[k][i]--;
    					}
    				}
    			}
    		}
    	}
    	cout<<ans+sum/4*3<<endl;
    	return 0;
    }
    

\(T2\) P228. 生活在hzoi上 \(0pts\)

  • 原题: luogu P5206 [WC2019] 数树 问题 \(1\)
  • 部分分
    • \(5pts\) :每个节点仅能给 \(1\) ,生成树的方案数为 \(n^{n-2}\)
  • 正解
    • 逆天题面, \(miaomiao\) 说改不动就不用改了。上次遇到子集反演的时候 \(miaomiao\) 就直接毙了。
    • 挂一下官方题解。

    题面锅了,谢罪。

    首先我们先考虑一个暴力。枚举第二棵树的形态求方案。

    先给他转化一下,容易发现只在一棵树里出现的边是没用的,那么考虑两棵树内都存在的边。而根据定义,如果只保留两棵树内都存在的边,那同一联通块内的节点给的数是一样的。

    那么设 \(E_1\) 为第一棵树的边集,\(E_2\) 为第二棵树的边集,总方案数就是 \(y^{n-|E_1\cap E_2|}\)

    然后考虑正解。现在答案是这个东西:

    \[\sum_{E_2}y^{n-|E_1\cap E_2|} \]

    有个憨批式子叫子集反演:

    \[f(S)=\sum_{T\subseteq S}\sum_{P\subseteq T}(-1)^{|T|-|P|}f(P) \]

    那设个 \(f(S)=y^{n-|S|}\) 推式子:

    \[\begin{aligned} &\sum_{E_2}y^{n-|E_1\cap E_2|}\\ =&\sum_{E_2}f(E_1\cap E_2)\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}\sum_{P\subseteq T}(-1)^{|T|-|P|}f(P)\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}\sum_{P\subseteq T}(-1)^{|T|-|P|}y^{n-|P|}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}\sum_{P\subseteq T}(-y)^{|T|-|P|}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}\sum_{i=0}^{|T|}\binom{|T|}i(-y)^{|T|-i}\\ =&\sum_{E_2}\sum_{T\subseteq E_1\cap E_2}y^{n-|T|}(1-y)^{|T|}\\ =&\sum_{T\subseteq E_1}y^{n-|T|}(1-y)^{|T|}g(T) \end{aligned} \]

    其中 \(g(T)\) 为包含边集 \(T\) 的树个数。由 prufer 序列容易得到

    \[g(T)=n^{k-2}\prod_{i=1}^ka_i \]

    其中 \(k\) 是连通块个数(也就是 \(n-|T|\)), \(a_i\) 是第 \(i\) 个连通块大小。

    那么代回原式:

    \[\begin{aligned} &\sum_{T\subseteq E_1}y^{n-|T|}(1-y)^{|T|}g(T)\\ =&\sum_{T\subseteq E_1}y^k(1-y)^{n-k}n^{k-2}\prod_{i=1}^ka_i\\ =&\frac{(1-y)^n}{n^2}\sum_{T\subseteq E_1}\prod_{i=1}^k\frac{ny}> {1-y}a_i \end{aligned} \]

    先特判掉 \(y=1\)

    考虑一下怎么算后边一堆东西。设个 \(dp_{x,i}\)\(x\) 的子树内 \(x\) 所在连通块大小为 \(i\) 的答案,转移考虑在 \(x\) 处枚举子树 \(v\)\((x,v)\) 这条边选不选。这样树上背包就是 \(O(n^2)\) 的。

    考虑优化。我们 \(dp\) 的瓶颈为第二维枚举联通块的大小,其实有个 trick 可以给他压掉:考虑到扩展Cayley定理里的式子 \(\prod_{i=1}^ka_i\) 的意义,是每个连通块里面选一个标记点连上,把联通块的贡献变成在联通块内选一个标记点的贡献,即每个联通块有且仅有一个标记点。如果选了标记点那么产生了一个有贡献的联通块,贡献要乘上 \(\dfrac ny{1-y}\)

    那么设 \(dp_{x,0/1}\)\(x\) 子树内 \(x\) 所在联通块是否选了点的贡献,转移有:

    初值:\(dp_{x,0}=1,dp_{x,1}=\dfrac ny{1-y}\)

    转移:

    1. \(dp_{x,1}\):两种情况,一种是和下边的儿子成为一个联通块,另一个是断开儿子的边(儿子的联通块必须选了标记点)。那么就是 \(dp_{x,1}\times dp_{v,0}+dp_{x,0}\times dp_{v,1}+dp_{x,1}\times dp_{v,1}\)

    2. \(dp_{x,0}\):也是要么断开要么不断。断开必须儿子选了,不断必须儿子没选。那么就是 \(dp_{x,0}\times dp_{v,0}+dp_{x,0}\times dp_{v,1}\)

    复杂度 \(O(n)\)

\(T3\) P229. 嘉然今天吃什么 \(0pts\)

  • 原题: luogu P8511 [Ynoi Easy Round 2021] TEST_68

  • 部分分

    • \(10 \%\)
      • 枚举 \(a_{i} \bigoplus a_{j}\) 可能会对哪些点产生影响,时间复杂度为 \(O(n^{3})\)

      • \(\{ a \}\) 插到 \(01Trie\) 上,每次遍历到一个新的节点就重构 \(01Trie\) ,时间复杂度为 \(O(n^{2} \log V)\)

        点击查看代码
        struct Trie
        {
        	ll trie[3000010][2],tot=0;
        	void init()
        	{
        		tot=0;
        		memset(trie,0,sizeof(trie));
        	}
        	void insert(ll s)
        	{
        		ll x=0,i;
        		for(i=60;i>=0;i--)
        		{
        			if(trie[x][(s>>i)&1]==0)
        			{
        				tot++;
        				trie[x][(s>>i)&1]=tot;
        			}
        			x=trie[x][(s>>i)&1];
        		}
        	}
        	ll query(ll s)
        	{
        		ll x=0,ans=0,i;
        		for(i=60;i>=0;i--)
        		{
        			if(trie[x][((s>>i)&1)^1]==0)
        			{
        				x=trie[x][(s>>i)&1];
        			}
        			else
        			{
        				x=trie[x][((s>>i)&1)^1];
        				ans|=(1ll<<i);
        			}
        		}
        		return ans;
        	}
        }T;
        struct node
        {
        	ll nxt,to;
        }e[1000010];
        ll head[1000010],a[1000010],pos[1000010],dfn[1000010],out[1000010],ans[1000010],fa[1000010],tot=0,cnt=0;
        void add(ll u,ll v)
        {
        	cnt++;
        	e[cnt].nxt=head[u];
        	e[cnt].to=v;
        	head[u]=cnt;
        }
        void dfs1(ll x,ll fa)
        {
        	tot++;
        	dfn[x]=tot;
        	pos[tot]=x;
        	for(ll i=head[x];i!=0;i=e[i].nxt)
        	{
        		if(e[i].to!=fa)
        		{
        			dfs1(e[i].to,x);
        		}
        	}
        	out[x]=tot;
        }
        ll ask(ll x,ll n)
        {
        	T.init();
        	for(ll i=1;i<=dfn[x]-1;i++)
        	{
        		T.insert(a[pos[i]]);
        	}
        	for(ll i=out[x]+1;i<=n;i++)
        	{
        		T.insert(a[pos[i]]);
        	}
        	ll ans=0;
        	for(ll i=1;i<=dfn[x]-1;i++)
        	{
        		ans=max(ans,T.query(a[pos[i]]));
        	}
        	for(ll i=out[x]+1;i<=n;i++)
        	{
        		ans=max(ans,T.query(a[pos[i]]));
        	}
        	return ans;
        }
        int main()
        {
        	ll n,i;
        	cin>>n;
        	for(i=2;i<=n;i++)
        	{
        		cin>>fa[i];
        		add(fa[i],i);
        		add(i,fa[i]);
        	}
        	for(i=1;i<=n;i++)
        	{
        		cin>>a[i];
        	}
        	dfs1(1,0);
        	for(i=1;i<=n;i++)
        	{
        		cout<<ask(i,n)<<endl;
        	}
        	return 0;
        }
        
    • \(20 \%\) :将 \(\{ a \}\) 插到 \(01Trie\) 上,可持久化 \(01Trie\) 维护,常数小的话应该可以通过。
    • 另外 \(20 \%\) :找到链顶后顺序插入,时间复杂度为 \(O(n \log n \log V)\)
  • 正解

    • 求出全局最大异或点对 \((x,y)\) ,只有 \(1 \to x,1 \to y\) 的两条链上的节点的答案不是 \(a_{x} \bigoplus a_{y}\)
    • 单独处理 \(1 \to x,1 \to y\) 的两条链上的节点即可,和链的部分分一样。
    点击查看代码
    struct Trie
    {
    	int trie[30000010][2],end[30000010],tot=0;
    	void init()
    	{
    		for(ll i=0;i<=tot;i++)
    		{
    			trie[i][0]=trie[i][1]=end[i]=0;
    		}
    		tot=0;
    	}
    	void insert(ll s,ll id)
    	{
    		ll x=0,i;
    		for(i=60;i>=0;i--)
    		{
    			if(trie[x][(s>>i)&1]==0)
    			{
    				tot++;
    				trie[x][(s>>i)&1]=tot;
    			}
    			x=trie[x][(s>>i)&1];
    		}
    		end[x]=id;
    	}
    	pair<ll,ll> query(ll s)
    	{
    		ll x=0,ans=0,i;
    		for(i=60;i>=0;i--)
    		{
    			if(trie[x][((s>>i)&1)^1]==0)
    			{
    				x=trie[x][(s>>i)&1];
    			}
    			else
    			{
    				x=trie[x][((s>>i)&1)^1];
    				ans|=(1ll<<i);
    			}
    		}
    		return make_pair(ans,end[x]);
    	}
    }T;
    struct node
    {
    	ll nxt,to;
    }e[1000010];
    ll head[1000010],a[1000010],vis[1000010],ans[1000010],fa[1000010],tot=0,cnt=0,sum=0;
    vector<ll>s;
    void add(ll u,ll v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs(ll x)
    {
    	T.insert(a[x],x);
    	sum=max(sum,T.query(a[x]).first);
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		dfs(e[i].to);
    	}
    }
    void dfs_init(ll x,ll y)
    {
    	T.insert(a[x],x);
    	sum=max(sum,T.query(a[x]).first);
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=y)
    		{
    			dfs(e[i].to);
    		}
    	}
    }
    void ask(ll x)
    {	
    	sum=0;
    	T.init();
    	s.clear();
    	for(ll i=x;i!=1;i=fa[i])	
    	{
    		vis[i]=1;
    		s.push_back(i);
    	}
    	for(ll i=s.size()-1;i>=0;i--)
    	{
    		dfs_init(fa[s[i]],s[i]);
    		ans[s[i]]=sum;
    	}
    }
    int main()
    {
    	ll n,maxx=0,i;
    	pair<ll,ll>tmp,pos;
    	cin>>n;
    	for(i=2;i<=n;i++)
    	{
    		cin>>fa[i];
    		add(fa[i],i);
    	}
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    		T.insert(a[i],i);
    		tmp=T.query(a[i]);
    		if(maxx<tmp.first)
    		{
    			maxx=tmp.first;
    			pos=make_pair(tmp.second,i);
    		}
    	}
    	vis[1]=1;
    	ask(pos.first);
    	ask(pos.second);
    	for(i=1;i<=n;i++)
    	{
    		cout<<((vis[i]==1)?ans[i]:maxx)<<endl;
    	}
    	return 0;
    }
    

\(T4\) P230. APJifengc \(10pts\)

  • 原题: [AGC036D] Negative Cycle
  • 部分分
    • \(10pts\) :枚举哪条边删或不删,最后跑 \(SPFA\) 判断有没有负环。

      点击查看代码
      struct node
      {
      	ll nxt,to,w,id;
      }e[300010];
      ll head[510],a[510][510],dis[510],vis[510],num[510],cnt=0,ans=0x7f7f7f7f;
      vector<ll>sh;
      void add(ll u,ll v,ll w)
      {
      	cnt++;
      	e[cnt].nxt=head[u];
      	e[cnt].to=v;
      	e[cnt].w=w;
      	e[cnt].id=cnt;
      	head[u]=cnt;
      }
      pair<ll,ll>divide(ll sum,ll n)
      {
      	ll u=ceil(1.0*sum/n),v=sum-(u-1)*n;
      	if(v==u)
      	{
      		v++;
      	}
      	return make_pair(u,v);
      }
      bool spfa(ll s,ll n)
      {
      	memset(dis,0x3f,sizeof(dis));
      	memset(vis,0,sizeof(vis));
      	memset(num,0,sizeof(num));
      	queue<ll>q;
      	q.push(s);
      	dis[s]=0;
      	vis[s]=1;
      	while(q.empty()==0)
      	{
      		ll x=q.front();
      		vis[x]=0;
      		q.pop();
      		for(ll i=head[x];i!=0;i=e[i].nxt)
      		{
      			ll flag=0;
      			for(ll j=0;j<sh.size();j++)
      			{
      				if(e[i].id==sh[j])
      				{
      					flag=1;
      					break;
      				}
      			}
      			if(flag==0)
      			{
      				if(dis[e[i].to]>dis[x]+e[i].w)
      				{
      					dis[e[i].to]=dis[x]+e[i].w;
      					num[e[i].to]=num[x]+1;
      					if(num[e[i].to]>=n+1)
      					{
      						return false;
      					}
      					if(vis[e[i].to]==0)
      					{
      						q.push(e[i].to);
      						vis[e[i].to]=1;
      					}
      				}
      			}
      		}
      	}
      	return true;
      }
      bool check(ll sum,ll n)
      {	
      	sh.clear();
      	for(ll i=1;i<=n*n;i++)
      	{
      		if((sum>>i)&1)
      		{
      			sh.push_back(i);
      		}
      	}
      	return spfa(0,n+1);
      }
      void dfs(ll pos,ll sum,ll n)
      {
      	if(pos==n*n+1)
      	{
      		if(check(sum,n)==1)
      		{
      			ll num=0;
      			pair<ll,ll>tmp;
      			for(ll i=0;i<sh.size();i++)
      			{
      				tmp=divide(sh[i],n);
      				num+=a[tmp.first][tmp.second];
      			}
      			ans=min(ans,num);
      		}
      	}
      	else
      	{
      		dfs(pos+1,sum|(1<<pos),n);
      		dfs(pos+1,sum,n);
      	}
      }
      int main()
      {
      	ll n,i,j;
      	cin>>n;
      	for(i=1;i<=n;i++)
      	{
      		for(j=1;j<=i-1;j++)
      		{
      			cin>>a[i][j];
      			add(i,j,1);
      		}
      		add(i,i+1,0);
      		for(j=i+1;j<=n;j++)
      		{
      			cin>>a[i][j];
      			add(i,j,-1);
      		}
      	}
      	for(i=1;i<=n;i++)
      	{
      		add(0,i,0);
      	}
      	dfs(1,0,n);
      	cout<<ans<<endl;
      	return 0;
      }
      
  • 正解
    • 逆天题面,讲题时讲都没讲,虽然讲了也听不懂。

    拜神。

    负环想到差分约束。设第 \(i\) 个点对应 \(x_i\)

    \(q_i=x_i-x_{i+1}\),首先根据初始的边可以知道 \(q_i\ge 0\)

    对于边权为 \(-1\) 的边 \(i\rightarrow j\),有 \(x_i-x_j\ge 1\),也就是 \(q_i+q_{i+1}+\dots+q_{j-1}\ge 1\)

    边权为 \(1\) 同理,有 \(x_j-x_i\le 1\),也就是 \(q_j+q_{j+1}+\dots+q_{i-1}\le 1\)

    假如我们知道 \(q_i\),那么边权为 \(-1\) 的边在区间和 \(\ge 1\) 时留下,边权为 \(1\) 的边在区间和 \(\le 1\) 时留下。也就是如果一段 \(q_i=0\),那么这段的边权为 \(-1\) 的边就要删掉,如果区间和 \(\ge 2\) 那么边权为 \(1\) 的边就要删掉。

    如果 \(q_i\ge 2\),那么显然不如 \(1\) 好。于是 \(q_i=0/1\)

    \(dp_{i,j}\) 为扫到 \(i\),最后一个 \(1\)\(q_i\),倒数第二个是 \(q_j\) 的最小代价,转移可以从 \(dp_{j,k}\) 转移,系数可以二维前缀和。复杂度 \(O(n^3)\)

总结

  • \(T2\) 忘了生成树的个数。
  • \(T3\)\(01Trie\) 树清空时没清空彻底且不会算空间,挂了 \(10pts\)

后记

  • 题目背景夹带私活。

  • 成分复杂。

posted @ 2024-08-11 17:34  hzoi_Shadow  阅读(48)  评论(0编辑  收藏  举报
扩大
缩小