暑假集训CSP提高模拟24

暑假集训CSP提高模拟24

\(A\) P268.与和 \(100pts\)

  • 原题: [ABC238D] AND and SUM

  • \(x,y\) 下界显然为 \(a\) ,不妨让 \(y=a,x=s-a\) 然后进行 \(check\) 。正确性由下一种做法可以进一步推导。

    点击查看代码
    int main()
    {
    	freopen("and.in","r",stdin);
    	freopen("and.out","w",stdout);
    	ll t,a,s,i;
    	scanf("%lld",&t);
    	for(i=1;i<=t;i++)
    	{
    		scanf("%lld%lld",&a,&s);
    		if(2*a<=s&&((s-a)&a)==a)
    		{
    			printf("Yes\n");
    		}
    		else
    		{
    			printf("No\n");
    		}
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 一个更通用且容易证明的方法是 \(s-2a\) 在二进制表示下 \(1\) 的位置只能出现 \(a\) 在二进制表示下 \(0\) 的位置上,以此来进行 \(check\)

    • \(1\) 的位置不妨都让 \(x\) 这一位为 \(1\) ,接着便有了上面第一种做法。
    点击查看代码
    int main()
    {
    	freopen("and.in","r",stdin);
    	freopen("and.out","w",stdout);
    	ll t,a,s,i;
    	scanf("%lld",&t);
    	for(i=1;i<=t;i++)
    	{
    		scanf("%lld%lld",&a,&s);
    		if(2*a<=s&&((s-2*a)&(~a))==(s-2*a))
    		{
    			printf("Yes\n");
    		}
    		else
    		{
    			printf("No\n");
    		}
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(B\) P239.函数 \(10pts\)

  • 原题: [ABC366F] Maximum Composition

  • 部分分

    • \(30pts\) :状压。

      点击查看代码
      ll a[200010],b[200010],f[2][(1<<20)+10];
      int main()
      {
      	freopen("func.in","r",stdin);
      	freopen("func.out","w",stdout);
      	ll n,k,maxx,ans=0,s,i,j;
      	cin>>n>>k;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i]>>b[i];
      	}
      	for(s=0;s<=(1<<n)-1;s++)
      	{
      		f[0][s]=1;
      	}
      	for(i=1;i<=k;i++)
      	{
      		for(s=0;s<=(1<<n)-1;s++)
      		{
      			maxx=0;
      			for(j=1;j<=n;j++)
      			{
      				if((s>>(j-1))&1)
      				{
      					maxx=max(maxx,a[j]*f[(i-1)&1][s^(1<<(j-1))]+b[j]);
      				}
      			}
      			f[i&1][s]=maxx;
      		}
      	}
      	for(s=0;s<=(1<<n)-1;s++)
      	{
      		ans=max(ans,f[k&1][s]);
      	}
      	cout<<ans<<endl;
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
  • 正解

    • 观察到 \(DP\) 式子很容易想,但选择顺序貌似很难处理。
    • 考虑推式子,假设 \(f_{j}(f_{i}(x))>f_{i}(f_{j}(x))\) ,式子展开后有 \(a_{j}b_{i}+b_{j}>a_{i}b_{j}+b_{i}\) ,移项后有 \(\dfrac{b_{i}}{a_{i}-1}>\dfrac{b_{j}}{a_{j}-1}\)
    • \(\{ f \}\)\(\dfrac{b}{a-1}\) 降序排序,实际排序时将除法转成乘法。
    • \(f_{i,j}\) 表示处理到第 \(i\) 个数时选择了 \(j\) 个数的最大值,状态转移方程为 \(f_{i,j}=\max(f_{i-1,j},a_{i}f_{i-1,j-1}+b_{i})\) ,边界为 \(f_{0,0}=1\)
    • 最终有 \(f_{n,k}\) 即为所求。
    点击查看代码
    struct node
    {
    	ll a,b;
    }c[200010];
    ll f[200010][15];
    bool cmp(node a,node b)
    {
    	return a.b*(b.a-1)>b.b*(a.a-1);
    }
    int main()
    {
    	ll n,k,i,j;
    	cin>>n>>k;
    	for(i=1;i<=n;i++)
    	{
    		cin>>c[i].a>>c[i].b;
    	}
    	sort(c+1,c+1+n,cmp);
    	f[0][0]=1;
    	for(i=1;i<=n;i++)
    	{
    		for(j=0;j<=k;j++)
    		{
    			f[i][j]=f[i-1][j];
    			if(j-1>=0)
    			{
    				f[i][j]=max(f[i][j],c[i].a*f[i-1][j-1]+c[i].b);
    			}
    		}
    	}
    	cout<<f[n][k]<<endl;
    	return 0;
    }
    

\(C\) P238.袋鼠 \(6pts\)

  • 原题: luogu P5999 [CEOI2016] kangaroo

  • 部分分

    • \(6pts\) :爆搜。

      点击查看代码
      const ll p=1000000007;
      ll vis[2010],ans=0;
      void dfs(ll now,ll prev,ll sum,ll t,ll n)
      {
      	if(now==t)
      	{
      		ans=(ans+(sum==n))%p;
      	}
      	else
      	{
      		if(prev<now)
      		{
      			for(ll i=1;i<now;i++)
      			{
      				if(vis[i]==0)
      				{
      					vis[i]=1;
      					dfs(i,now,sum+1,t,n);
      					vis[i]=0;
      				}
      			}
      		}
      		else
      		{
      			for(ll i=now+1;i<=n;i++)
      			{
      				if(vis[i]==0)
      				{
      					vis[i]=1;
      					dfs(i,now,sum+1,t,n);
      					vis[i]=0;
      				}
      			}
      		}
      	}
      }
      int main()
      {
      	freopen("kang.in","r",stdin);
      	freopen("kang.out","w",stdout);
      	ll n,s,t,i;
      	cin>>n>>s>>t;
      	for(i=1;i<=n;i++)
      	{
      		if(i!=s)
      		{
      			memset(vis,0,sizeof(vis));
      			vis[i]=vis[s]=1;
      			dfs(i,s,2,t,n);
      		}
      	}
      	cout<<ans<<endl;
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
  • 正解

    • 考虑转化题面。
      • 问有多少个长度为 \(n\) 的排列 \(\{ p \}\) 满足 \(p_{1}=s,p_{n}=t\)\(\forall i \in [2,n-1],p_{i-1},p_{i+1}\) 同时比 \(p_{i}\) 大或同时比 \(p_{i}\) 小。
      • luogu P2467 [SDOI2010] 地精部落 多了个起点和终点的限制。
    • 考虑预设性 \(DP\)
    • \(f_{i,j}\) 表示已经填入了 \([1,i]\) 且形成了 \(j\) 个连续段(填入位置不一定是最终位置)的方案,边界为 \(f_{1,1}=1\)
    • \(i(i \ne s,i\ne t)\) 填入的位置进行分讨。
      • 新开一个连续段(两端都比 \(i\) 大)
        • 填入 \(i-1\) 时只有 \(j-1\) 个连续段,可填的空有 \(j-1+1-[i>s]-[i>t]=j-[i>s]-[i>t]\) 种情况(头尾需要特判),此时有 \(f_{i,j}+=(j-[i>s]-[i>t]) \times f_{i-1,j-1}\)
      • 连接两个连续段(两端都比 \(i\) 小),同 T2731. DP搬运工1 没有位置硬插进去。
        • 填入 \(i-1\) 时只有 \(j+1\) 个连续段,可填的空有 \(j+1-1=j\) 种情况,此时有 \(f_{i,j}+=j \times f_{i-1,j+1}\)
    • \(i=s/t\) 时只能填在头/尾,有 \(f_{i,j}=f_{i-1,j-1}+f_{i-1,j}\)
    • 最终,有 \(f_{n,1}\) 即为所求。
    点击查看代码
    const ll p=1000000007;
    ll f[2010][2010];
    int main()
    {
    	freopen("kang.in","r",stdin);
    	freopen("kang.out","w",stdout);
    	ll n,s,t,i,j;
    	cin>>n>>s>>t;
    	f[1][1]=1;
    	for(i=2;i<=n;i++)
    	{
    		for(j=1;j<=i;j++)
    		{
    			if(i!=s&&i!=t)
    			{
    				f[i][j]=((j-(i>s)-(i>t))*f[i-1][j-1]%p+j*f[i-1][j+1]%p)%p;
    			}
    			else
    			{
    				f[i][j]=(f[i-1][j-1]+f[i-1][j])%p;
    			}
    		}
    	}
    	cout<<f[n][1]<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 组题人的温馨提醒

    建议参考 CEOI2016 Kangaroo 线性做法

\(D\) P240.最短路 \(8pts\)

  • 原题: CF464E The Classic Problem

  • 部分分

    • \(1 \sim 2\) :直接跑最短路,最后再取模。

      点击查看代码
      const ll p=1000000007;
      struct node
      {
      	ll nxt,to,x,w;
      }e[400010];
      ll head[400010],dis[400010],vis[400010],u[400010],v[400010],x[400010],cnt=0;
      void add(ll u,ll v,ll x,ll w)
      {
      	cnt++;
      	e[cnt].nxt=head[u];
      	e[cnt].to=v;
      	e[cnt].x=x;
      	e[cnt].w=w;
      	head[u]=cnt;
      }
      void dijstra(ll s,ll n)
      {
      	fill(dis+1,dis+1+n,1e18);
      	memset(vis,0,sizeof(vis));
      	priority_queue<pair<ll,ll> >q;
      	dis[s]=0;
      	q.push(make_pair(-dis[s],-s));
      	while(q.empty()==0)
      	{
      		ll x=-q.top().second;
      		q.pop();
      		if(vis[x]==0)
      		{
      			vis[x]=1;
      			for(ll i=head[x];i!=0;i=e[i].nxt)
      			{
      				if(dis[e[i].to]>dis[x]+e[i].w)
      				{
      					dis[e[i].to]=dis[x]+e[i].w;
      					q.push(make_pair(-dis[e[i].to],-e[i].to));
      				}
      			}
      		}		
      	}
      }
      int main()
      {
      	freopen("hellagur.in","r",stdin);
      	freopen("hellagur.out","w",stdout);
      	ll n,m,s,t,flag=1,i;
      	cin>>n>>m;
      	for(i=1;i<=m;i++)
      	{
      		cin>>u[i]>>v[i]>>x[i];
      		flag&=(x[i]<=30);
      	}
      	cin>>s>>t;
      	if(flag==1)
      	{
      		for(i=1;i<=m;i++)
      		{
      			add(u[i],v[i],x[i],(1ll<<x[i]));
      			add(v[i],u[i],x[i],(1ll<<x[i]));
      		}
      		dijstra(s,n);
      		if(dis[t]==1e18)
      		{
      			cout<<-1<<endl;
      		}
      		else
      		{
      			cout<<dis[t]%p<<endl;
      		}
      	}
      	else
      	{
      		cout<<"-1"<<endl;
      	}
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
    • \(8 \sim 9\) :观察到 \(x_{i}\) 互不相同,在最短路的更新过程一定不会产生进位(若产生了说明这条边重复走过,显然不优),且比较时只按二进制下最高位比较即可。具体实现时用二进制表示到每个点的最短路长度,可能需要一些诸如拿主席树来存等方法来保证空间复杂度正确,一定程度上算启发正解了(?)。

  • 正解

    • 上次遇到高精度最短路还是 2022年普及组3(因编号错误实际应该是2022年普及组2) T4 雷

    • 考虑保存最短路在二进制表示下的每一位,空位补 \(0\)

    • 因为每个节点的最短路长度仅会在松弛操作时进行更改,考虑主席树来节省空间。

    • 这样的话在 \(dijkstra\) 的过程中需要支持相加和比较。

      • 相加
        • 加上 \(2^{x}\) 等价于从 \(x\) 向左找到一个最靠左的位置 \(y\) 使得 \([x,y)\) 都为 \(1\) ,接着让 \([x,y)\) 推平成 \(0\) ,让 \(y\) 加一。
          • \(y\)
            • 每个节点存一下当前区间内 \(1\) 的个数,然后在线段树上二分即可。
          • 区间推平
            • 由于只有区间推平成 \(0\) 操作考虑初始时建出一棵二进制每一位为 \(0\) 的主席树。推平时将其连到这棵主席树上。
        • 新建一棵临时主席树存相加后的值进行比较。
      • 比较
        • 考虑哈希维护一段区间的数值来判断是否相等,找到两个数值最高不相同的位(叶子节点),然后进行比较。线段树上二分即可。
    • 类似建最短路树一样记录路径。

      点击查看代码
      const int mod=1000000007,base=2;
      struct node
      {
      	int nxt,to,x;
      }e[400010];
      int head[400010],vis[400010],cnt=0;
      ll mi[400010];
      void add(int u,int v,int x)
      {
      	cnt++;
      	e[cnt].nxt=head[u];
      	e[cnt].to=v;
      	e[cnt].x=x;
      	head[u]=cnt;
      }
      struct PDS_SMT
      {
      	int root[400010],rt_sum;
      	struct SegmentTree
      	{
      		int ls,rs,sum,len;
      		ll hsh;
      	}tree[400010<<5];
      	#define lson(rt) tree[rt].ls
      	#define rson(rt) tree[rt].rs
      	int build_rt()
      	{
      		rt_sum++;
      		return rt_sum;
      	}
      	void pushup(int rt)
      	{
      		tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum;
      		tree[rt].hsh=(tree[lson(rt)].hsh+tree[rson(rt)].hsh*mi[tree[lson(rt)].len]%mod)%mod;//为方便代码书写,二进制下的左右与题解中所说的正好相反
      	}
      	void build_tree(int &rt,int l,int r)
      	{
      		rt=build_rt();
      		tree[rt].len=r-l+1;
      		if(l==r)
      		{
      			return;
      		}
      		int mid=(l+r)/2;
      		build_tree(lson(rt),l,mid);
      		build_tree(rson(rt),mid+1,r);
      		pushup(rt);
      	}
      	void update1(int pre,int &rt,int l,int r,int pos)
      	{
      		rt=build_rt();
      		tree[rt]=tree[pre];
      		if(l==r)
      		{
      			tree[rt].sum++;
      			tree[rt].hsh++;
      			return;
      		}
      		int mid=(l+r)/2;
      		if(pos<=mid)
      		{
      			update1(lson(pre),lson(rt),l,mid,pos);
      		}
      		else
      		{
      			update1(rson(pre),rson(rt),mid+1,r,pos);
      		}
      		pushup(rt);
      	}
      	void update2(int init,int pre,int &rt,int l,int r,int x,int y)
      	{
      		if(x<=l&&r<=y)
      		{
      			rt=init;
      			return;
      		}
      		rt=build_rt();
      		tree[rt]=tree[pre];
      		int mid=(l+r)/2;
      		if(x<=mid)
      		{
      			update2(lson(init),lson(pre),lson(rt),l,mid,x,y);
      		}
      		if(y>mid)
      		{
      			update2(rson(init),rson(pre),rson(rt),mid+1,r,x,y);
      		}
      		pushup(rt);
      	}
      	int query(int rt,int l,int r,int x,int y)
      	{
      		if(x<=l&&r<=y)
      		{
      			return tree[rt].sum;
      		}
      		int mid=(l+r)/2,ans=0;
      		if(x<=mid)
      		{
      			ans+=query(lson(rt),l,mid,x,y);
      		}
      		if(y>mid)
      		{
      			ans+=query(rson(rt),mid+1,r,x,y);
      		}
      		return ans;
      	}
      	int find(int rt,int l,int r,int pos)
      	{
      		if(l==r)
      		{
      			return l;
      		}
      		int mid=(l+r)/2;
      		if(pos>mid)
      		{
      			return find(rson(rt),mid+1,r,pos);
      		}
      		if(query(lson(rt),l,mid,pos,mid)==mid-pos+1)
      		{
      			return find(rson(rt),mid+1,r,mid+1);
      		}
      		else
      		{
      			return find(lson(rt),l,mid,pos);
      		}
      	}
      	void add(int pre,int &rt,int pos)
      	{
      		int y=find(pre,0,200001,pos);
      		if(pos<=y-1)
      		{
      			update2(root[0],pre,rt,0,200001,pos,y-1);
      		}
      		else//如果找不到
      		{
      			rt=pre;
      		}
      		update1(rt,rt,0,200001,y);
      	}
      	bool cmp(int rt1,int rt2,int l,int r)
      	{
      		if(l==r)
      		{
      			return tree[rt1].sum>tree[rt2].sum;//在叶子节点比较
      		}
      		int mid=(l+r)/2;
      		if(tree[rson(rt1)].hsh==tree[rson(rt2)].hsh)
      		{
      			return cmp(lson(rt1),lson(rt2),l,mid);
      		}
      		else
      		{
      			return cmp(rson(rt1),rson(rt2),mid+1,r);
      		}
      	}
      }T;
      struct quality
      {
      	int id,rt;
      	bool operator < (const quality &another) const
      	{
      		return T.cmp(rt,another.rt,0,200001);
      	}
      };
      void dijkstra(int s,int n)
      {
      	T.build_tree(T.root[0],0,200001);
      	T.root[s]=T.root[0];
      	priority_queue<quality>q;
      	q.push((quality){s,T.root[s]});
      	while(q.empty()==0)
      	{
      		int x=q.top().id;
      		q.pop();
      		if(vis[x]==0)
      		{
      			vis[x]=1;
      			for(int i=head[x];i!=0;i=e[i].nxt)
      			{
      				T.add(T.root[x],T.root[n+1],e[i].x);//临时主席树
      				if(T.root[e[i].to]==0||T.cmp(T.root[e[i].to],T.root[n+1],0,200001)==true)
      				{
      					T.root[e[i].to]=T.root[n+1];
      					q.push((quality){e[i].to,T.root[e[i].to]});
      				}
      			}
      		}
      	}
      }
      int main()
      {
      	freopen("hellagur.in","r",stdin);
      	freopen("hellagur.out","w",stdout);
      	int n,m,u,v,x,s,t,i;
      	cin>>n>>m;
      	for(i=1;i<=m;i++)
      	{
      		cin>>u>>v>>x;
      		add(u,v,x);
      		add(v,u,x);
      	}
      	cin>>s>>t;
      	for(i=0;i<=200001;i++)
      	{
      		mi[i]=(i==0)?1:mi[i-1]*base%mod;
      	}
      	dijkstra(s,n);
      	if(vis[t]==0)//未被加入优先队列
      	{
      		cout<<"-1"<<endl;
      	}
      	else
      	{
      		cout<<T.tree[T.root[t]].hsh<<endl;
      	}
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      

\(Ex\) P269. 赤(red)

总结

  • \(B\) 结束前 \(5 \min\) 把原状压改成了爆搜想节省空间,结果挂了 \(20pts\)
  • \(D\) 结束前 \(10 \min\) 发现把 freopen("hellagur.out","w",stdout); 写成了 freopen("hellagur.out","r",stdin);
  • 感觉自己偏随机化和乱搞做法的题不是很敢写,明知没有正确性的做法被自己 \(Pass\) 掉就没尝试写过看实际能拿多少分。

后记

  • \(A\) 赛时多次出锅,包括但不限于对负数进行逻辑与运算, \(a,b,s\) 写反,以为非负整数 \(>0\)

    • 逻辑与运算仅在非负整数中有定义。
  • \(C\) 组题人貌似太高估我们了。

  • \(D\) 原属于 HZOI2024 冲刺 NOIP2024 400pts 计划 2.图论专题 A ,结果被搬到模拟赛了。

  • \[\]

posted @ 2024-08-19 15:00  hzoi_Shadow  阅读(95)  评论(6编辑  收藏  举报
扩大
缩小