暑假集训CSP提高模拟13

暑假集训CSP提高模拟13

暑假集训CSP提高模拟13

组题人: @Delov | @joke3579

T1 P185. 小孩召开法 1 43pts

  • 原题: [ABC278F] Shiritori

  • 部分分

    • 未知 pts :乱搞。
  • 正解

    • 状压加记忆化搜索。
    • 记录所选字符串的状态及上一个选择的字符串。当存在对方必败时自己必胜。
    点击查看代码
    int f[1<<18][18];
    string s[20];
    bool dfs(int x,int last,int n)
    {
    	if(f[x][last]==-1)
    	{
    		f[x][last]=0;
    		for(int i=0;i<=n-1;i++)
    		{
    			if(((x>>i)&1)==0)
    			{
    				if(last==0||s[last][s[last].size()-1]==s[i+1][0])
    				{
    					if(dfs(x|(1<<i),i+1,n)==0)
    					{
    						f[x][last]=1;
    						break;
    					}
    				}
    			}
    		}
    	}
    	return f[x][last];
    }
    int main()
    {
    	int n,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>s[i];
    	}
    	memset(f,-1,sizeof(f));
    	if(dfs(0,0,n)==1)	
    	{
    		cout<<"First"<<endl;
    	}
    	else
    	{
    		cout<<"Second"<<endl;
    	}
    	return 0;
    }
    

T2 P188. 小孩召开法 2 20pts

  • 原题: LibreOJ 6669.Nauuo and Binary Tree

  • 部分分

    • 20pts :乱搞。
  • 正解

    • 由数据范围,猜测询问次数介于 O(n)O(nlogn) 之间。
    • n1 次询问处理出每个点的深度,然后从小到大枚举深度进行处理。
    • 接下来对 x,y 的询问,由 disx,y=depx+depy2depLCA(x,y) ,我们就可以得到 LCA(x,y) 的深度。
    • 设当前已经建出了深度为 0d1 层的二叉树,未知点为 x ,已知点为 y 。询问得到 LCA(x,y) 的深度后,我们就可以知道 LCA(x,y) 是哪个节点。由于是二叉树, x,y 一定位于 LCA(x,y) 的两棵子树内,那么 x 的父亲节点一定位于 LCA(x,y)y 所在子树内的另一棵子树。
    • y 初始为根节点, zy 所在的重链的末端点,此时 LCA(x,z) 一定在 y 所在的重链上,从 y 开始往下跳重儿子直至 LCA(x,z) ,然后走到轻子树,接着令 y 是这棵轻子树的根节点,重复上述过程直至 depy=depx1
    • 连上边后再往上跳更新轻重儿子。
    • 最后的询问次数为 O(nlogn) ,因为常数很小所以可以通过。
    点击查看代码
    int fa[3010],dep[3010],son[3010],siz[3010],bot[3010];
    vector<int>pos[3010],e[3010];
    void dfs(int x)
    {
    	siz[x]=1;
    	son[x]=0;
    	bot[x]=x;
    	for(int i=0;i<e[x].size();i++)
    	{
    		dfs(e[x][i]);
    		siz[x]+=siz[e[x][i]];
    		son[x]=(siz[e[x][i]]>siz[son[x]])?e[x][i]:son[x];
    		bot[x]=bot[son[x]];
    	}
    }
    void solve(int x)
    {
    	int y=1,d;
    	while(dep[y]!=dep[x]-1)
    	{
    		cout<<"? "<<x<<" "<<bot[y]<<endl;
    		cin>>d;
    		d=(dep[x]+dep[bot[y]]-d)/2;
    		while(dep[y]<d)
    		{
    			y=son[y];//跳重儿子
    		}
    		if(dep[y]!=dep[x]-1)
    		{
    			y=e[y][0]^e[y][1]^son[y];//取轻儿子
    			//因为一定有轻儿子,所以不用担心会 RE
    		}
    	}
    	fa[x]=y;
    	e[y].push_back(x);
    }
    int main()
    {
    	int n,i,j;
    	cin>>n;
    	for(i=2;i<=n;i++)
    	{
    		cout<<"? "<<1<<" "<<i<<endl;
    		cin>>dep[i];
    		pos[dep[i]].push_back(i);
    	}
    	for(i=1;i<=n;i++)
    	{
    		dfs(1);
    		for(j=0;j<pos[i].size();j++)
    		{
    			solve(pos[i][j]);
    		}
    	}
    	cout<<"! ";
    	for(i=2;i<=n;i++)
    	{
    		cout<<fa[i]<<" ";
    	}
    	cout<<endl;
    	return 0;
    }
    

T3 T176. 小孩召开法 3 30pts

  • 原题: luogu P6240 好吃的题目

  • 部分分

    • 30pts :每组询问跑一遍 01 背包。
  • 正解

    • 将询问离线下来,然后进行分治。
    • 考虑猫树分治,只单独处理跨过中点的询问,其他询问下放至左右递归中。特判叶子节点的处理。
      • 因过程和猫树很像,所以被称作猫树分治。
    • 对于跨过中点的询问,暴力进行背包合并即可。
    点击查看代码
    struct node
    {
    	ll l,t,id;
    };
    ll h[40010],w[40010],ans[200010],f[40010][210];
    vector<node>q[40010];
    void solve(ll l,ll r)
    {
    	if(l==r)
    	{
    		for(ll i=0;i<q[r].size();i++)
    		{
    			if(q[r][i].l==l)
    			{
    				ans[q[r][i].id]=(q[r][i].t>=h[l])*w[l];
    			}
    		}
    		return;
    	}
    	ll mid=(l+r)/2;
    	memset(f[mid],0,sizeof(f[mid]));//注意清空
    	f[mid][h[mid]]=w[mid];
    	for(ll i=mid-1;i>=l;i--)
    	{
    		for(ll j=1;j<=200;j++)
    		{
    			f[i][j]=f[i+1][j];
    			if(j-h[i]>=0)
    			{
    				f[i][j]=max(f[i][j],f[i+1][j-h[i]]+w[i]);
    			}
    		}
    	}
    	memset(f[mid+1],0,sizeof(f[mid+1]));//注意清空
    	f[mid+1][h[mid+1]]=w[mid+1];
    	for(ll i=mid+2;i<=r;i++)
    	{
    		for(ll j=1;j<=200;j++)
    		{
    			f[i][j]=f[i-1][j];
    			if(j-h[i]>=0)
    			{
    				f[i][j]=max(f[i][j],f[i-1][j-h[i]]+w[i]);
    			}
    		}
    	}
    	for(ll i=l;i<=r;i++)
    	{
    		for(ll j=1;j<=200;j++)
    		{
    			f[i][j]=max(f[i][j],f[i][j-1]);
    		}
    	}
    	for(ll i=mid+1;i<=r;i++)
    	{
    		for(ll j=0;j<q[i].size();j++)
    		{
    			if(l<=q[i][j].l&&q[i][j].l<=mid)
    			{
    				for(ll k=0;k<=q[i][j].t;k++)
    				{
    					ans[q[i][j].id]=max(ans[q[i][j].id],f[i][k]+f[q[i][j].l][q[i][j].t-k]);
    				}
    			}
    		}
    	}
    	solve(l,mid);
    	solve(mid+1,r);
    }
    int main()
    {
    	ll n,m,l,r,t,i;
    	cin>>n>>m;
    	for(i=1;i<=n;i++)
    	{
    		cin>>h[i];
    	}
    	for(i=1;i<=n;i++)
    	{
    		cin>>w[i];
    	}
    	for(i=1;i<=m;i++)
    	{
    		cin>>l>>r>>t;
    		q[r].push_back((node){l,t,i});
    	}
    	solve(1,n);
    	for(i=1;i<=m;i++)
    	{
    		cout<<ans[i]<<endl;
    	}
    	return 0;
    }
    

T4 T177. 小孩召开法 4 5pts

  • 原题: [AGC056B] Range Argmax
  • 部分分
    • 5pts :输出样例 1
  • 正解
    • 过于抽象,直接贺官方题解了。

    同一个 x 可能对应多个 p,因此这样计数比较困难。

    考虑反过来计数。

    对于给定的 x,我们将按照以下的方式构造 p

    • p=(1,1,,1)
    • 我们从 n 开始依次递减地考虑每个值 v。对于每个值,我们找到 v 能放的最左侧的位置,放进去。

    计数可以通过这种方式生成的 p

    设当前最值为 v。我们首先确定下标 m,使得 pm=v。对于所有包含 m 的区间 i,有 xi=m。删除这些包含 m 的区间后,我们可以分别考虑位于 m 左右两侧的区间。由于 m 为最左侧的可以放 v 的位置,右侧的数均小于左侧的数,这部分是和原问题等价但规模更小的子问题。

    现在考虑左侧。我们令 km 左侧最大元素对应的下标。有:必定存在一个左侧区间同时包含 km

    考虑反证。如果左侧没有区间同时包含 km,那我们可以令 pk=v,这必定是更优且满足要求的。这与假设矛盾,因此必定存在同时包含 km 的左侧区间。
    因此,左侧区间所填的数的最大值必定大于等于 v

    考虑区间 dp。我们设 f(l,r,m)[l,r] 区间满足最大值的下标大于等于 m 的方案数。可以通过枚举中点以及预处理区间最大值的方式转移。转移时加入后缀和即可快速得到最终答案。

    时间复杂度 O(n3)

总结

  • T4 赛时没读懂题。

后记

posted @   hzoi_Shadow  阅读(59)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
扩大
缩小
点击右上角即可分享
微信分享提示