csp-s模拟10

csp-s模拟10

\(T1\) T3673. 欧几里得的噩梦 \(0pts\)

  • 部分分

    • \(0 \%\) :状压加枚举子集。
    • \(20 \%\) :线性基暴力做。
  • 正解

    • 考虑模拟线性基贪心法插入的过程。顺序加入集合的过程已经处理了字典序最小的问题。
    • 当只有 \(x\) 位为 \(1\) 时,在线性基插入的过程终止位置是固定的。
      • 转化为 \(x\) 和虚拟节点 \(m+1\)\(1\)
    • 当有两位为 \(1\) 时,这两位相互独立,若终止位置一样则说明可以通过异或得到,否则加入线性基。
    • 寻找终止位置的过程本质是遍历整条链寻找终止位置。并查集判断是否在线性基内,从而优化遍历过程。
    点击查看代码
    const ll p=1000000007;
    vector<ll>ans;
    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;
    }
    struct DSU
    {
    	int fa[500010];
    	void init(int n)
    	{
    		for(int i=1;i<=n;i++)
    		{
    			fa[i]=i;
    		}
    	}
    	int find(int x)
    	{
    		return (fa[x]==x)?x:fa[x]=find(fa[x]);
    	}
    }D;
    int main()
    {
    	freopen("Euclid.in","r",stdin);
    	freopen("Euclid.out","w",stdout);
    	ll n,m,pd,x,y,i;
    	cin>>n>>m;
    	D.init(m+1);
    	for(i=1;i<=n;i++)
    	{
    		cin>>pd>>x;
    		if(pd==1)
    		{
    			y=m+1;
    		}
    		else
    		{
    			cin>>y;
    		}
    		x=D.find(x);
    		y=D.find(y);
    		if(x!=y)
    		{
    			D.fa[x]=y;
    			ans.push_back(i);
    		}
    	}
    	cout<<qpow(2,ans.size(),p)<<" "<<ans.size()<<endl;
    	for(i=0;i<ans.size();i++)
    	{
    		cout<<ans[i]<<" ";
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T2\) T3672. 清扫 \(6pts\)

  • 原题: [AGC010C] Cleaning

  • 钦定根节点 \(rt\) 的度数 \(\ge 2\) ,所以需要特判 \(n=2\) 的情况。

  • 部分分

    • 未知 \(pts\)
      • 等价于查询是否存在一种进行若干次点差分方式,对于差分数组 \(d\) 满足 \(\forall x \in [1,n],a_{x}=\sum\limits_{y \in Subtree(x)}d_{y}\)
      • 直接进行高斯消元时间复杂度不可接受,手动解出 \(d_{x}=a_{x}-\sum\limits_{y \in Son(x)}a_{y}\)
      • 又因为对于非叶子节点的 \(x\)\(d_{x}\)\(x\) 作为 \(fa_{\operatorname{LCA}(u,v)}\)\(x\) 作为 \(\operatorname{LCA}(u,v)\) 两部分的贡献组成,设它们分别为 \(f_{x},g_{x}\) ,有 \(f_{x}+g_{x}=d_{x}\)
      • 不难得到 \(f_{x}=\sum\limits_{y \in Son(x)}g_{y}\) ,即 \(d_{x}=g_{x}+\sum\limits_{y \in Son(x)}g_{y}\) ,类似 \(\{ d \}\) 的求法再次手动求出 \(\{ f \},\{ g \}\)
      • 问题就转化为是否存在一种构造方式使得点差分时叶子节点 \(x\) 恰好访问了 \(d_{x}\) 次,而非叶子节点 \(y\) 作为任意两个叶子节点的 \(\operatorname{LCA}\) 被访问了 \(-g_{y}\) 遍。
      • 赛时因为不知道怎么判断,所以只写了最基本的 \(-\sum\limits_{i=1}^{n}[du_{i} \ne 1] \times g_{i}=\sum\limits_{i=1}^{n}[du_{i}=1] \times d_{i}\) ,没过大样例。
  • 正解

    • \(\{ f \}\) 数组好像没啥用,直接扔掉。并令 \(\{ g \}\) 都乘以 \(-1\) 表示作为 \(\operatorname{LCA}(u,v)\) 的次数,此时 \(g_{x}=-d_{x}-\sum\limits_{y \in Son(x)}g_{y}\) ,令叶子节点的 \(g=0\)
    • \(f_{x}\) 表示 \(x\) 子树内叶子节点向上拓展的总路径条数(差分剩下的),有 \(f_{x}=\begin{cases} a_{x} & du_{x}=1 \\ -2g_{x}+\sum\limits_{y \in Son(x)}f_{y} & du_{x} \ne 1 \end{cases}\)
    • 考虑此时如何判断有解。
      • \(g_{x} \ge 0\)
      • \(f_{rt}=0\)
      • \(0 \le f_{x} \le a_{x} \land \sum\limits_{y \in Son(x)}f_{y}- \max\limits_{y \in Son(x)}\{ f_{y} \} \ge g_{x}\)
        • 前者隐含了 \(\frac{\sum\limits_{y \in Son(x)}f_{y}}{2} \ge g_{x}\) 的条件。
        • 后者的感性理解是若 \(\max\limits_{y \in Son(x)}\{ f_{y} \} > \frac{\sum\limits_{y \in Son(x)}f_{y}}{2}\) ,则合并次数在满足 \(f_{y}\) 最大的 \(y\) 子树和其他子树进行合并时取到最多 \(\sum\limits_{y \in Son(x)}f_{y}- \max\limits_{y \in Son(x)}\{ f_{y} \}\) ;否则两两进行匹配,合并次数为 \(\frac{\sum\limits_{y \in Son(x)}f_{y}}{2}\)
    • 证明有点抽象,感性理解吧。
    点击查看代码
    struct node
    {
    	ll nxt,to;
    }e[200010];
    ll head[200010],du[200010],a[200010],d[200010],f[200010],g[200010],cnt=0;
    void add(ll u,ll v)
    {
    	cnt++;
    	e[cnt].nxt=head[u];
    	e[cnt].to=v;
    	head[u]=cnt;
    }
    void dfs(ll x,ll fa)
    {
    	ll sum=0,maxx=0;
    	d[x]=a[x];
    	if(du[x]==1)
    	{
    		f[x]=a[x];
    		return;
    	}
    	for(ll i=head[x];i!=0;i=e[i].nxt)
    	{
    		if(e[i].to!=fa)
    		{
    			dfs(e[i].to,x);
    			d[x]-=a[e[i].to];
    			g[x]-=g[e[i].to];
    			sum+=f[e[i].to];
    			maxx=max(maxx,f[e[i].to]);
    		}
    	}
    	g[x]-=d[x];
    	f[x]=-2*g[x]+sum;
    	if(g[x]<0||f[x]<0||f[x]>a[x]||sum-maxx<g[x])
    	{
    		cout<<"NO"<<endl;
    		exit(0);
    	}
    }
    int main()
    {
    	freopen("tree.in","r",stdin);
    	freopen("tree.out","w",stdout);
    	ll n,u,v,rt,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	for(i=1;i<=n-1;i++)
    	{
    		cin>>u>>v;
    		du[u]++;
    		du[v]++;
    		add(u,v);
    		add(v,u);
    	}
    	if(n==2)
    	{
    		if(a[1]==a[2])
    		{
    			cout<<"YES"<<endl;
    		}
    		else
    		{
    			cout<<"NO"<<endl;
    		}
    	}
    	else
    	{
    		for(i=1;i<=n;i++)
    		{
    			if(du[i]>=2)
    			{
    				rt=i;
    				break;
    			}
    		}
    		dfs(rt,0);
    		if(f[rt]==0)
    		{
    			cout<<"YES"<<endl;
    		}
    		else
    		{
    			cout<<"NO"<<endl;
    		}
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T3\) T1072. 购物 \(20pts\)

  • 部分分

    • 子任务 \(1,3\) :背包处理出能拼出来的商品价格,然后写一个静态推平全局查询的数据结构即可。

      点击查看代码
      struct ODT
      {
      	struct node
      	{
      		ll l,r;
      		mutable ll col;
      		bool operator < (const node &another) const
      		{
      			return l<another.l;
      		}
      	};
      	set<node>s;
      	void init()
      	{
      		s.insert((node){0,1000000000010,0});
      	}
      	set<node>::iterator split(ll pos)
      	{
      		set<node>::iterator it=s.lower_bound((node){pos,0,0});
      		if(it!=s.end()&&it->l==pos)
      		{
      			return it;
      		}
      		it--;
      		if(it->r<pos)
      		{
      			return s.end();
      		}
      		ll l=it->l,r=it->r,col=it->col;
      		s.erase(it);
      		s.insert((node){l,pos-1,col});
      		return s.insert((node){pos,r,col}).first;
      	}
      	void assign(ll l,ll r,ll col)
      	{
      		set<node>::iterator itr=split(r+1),itl=split(l);
      		s.erase(itl,itr);
      		s.insert((node){l,r,col});
      	}
      	ll query(ll l,ll r)
      	{
      		set<node>::iterator itr=split(r+1),itl=split(l);
      		ll ans=0;
      		for(set<node>::iterator it=itl;it!=itr;it++)
      		{
      			ans+=(it->col==1)*(it->r-it->l+1);
      		}
      		return ans;
      	}
      }O;
      ll a[100010];
      bool f[1000010];
      void dfs(ll pos,ll n,ll sum)
      {
      	if(pos==n+1)
      	{
      		if(sum!=0)
      		{
      			O.assign(ceil(sum/2.0),sum,1);
      		}
      	}
      	else
      	{
      		dfs(pos+1,n,sum+a[pos]);
      		dfs(pos+1,n,sum);
      	}
      }
      int main()
      {
      	freopen("buy.in","r",stdin);
      	freopen("buy.out","w",stdout);
      	ll n,m=0,i,j;
      	cin>>n;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i];
      		m+=a[i];
      	}
      	O.init();
      	if(m<=1000000)
      	{
      		f[0]=1;
      		for(i=1;i<=n;i++)
      		{
      			for(j=m;j>=a[i];j--)
      			{
      				f[j]|=f[j-a[i]];
      			}
      		}
      		for(i=1;i<=m;i++)
      		{
      			if(f[i]==1)
      			{
      				O.assign(ceil(i/2.0),i,1);
      			}
      		}
      	}
      	else
      	{
      		dfs(1,n,0);
      	}
      	cout<<O.query(0,1000000000000)<<endl;
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
  • 正解

    • 因为只选一个数 \(i\) 时对答案的贡献为 \([\left\lceil \frac{a_{i}}{2} \right\rceil,a_{i}]\) ,不妨考虑先将 \(\{ a \}\) 升序排序。
    • \(sum_{i}=\sum\limits_{j=1}^{i}a_{j}\)
    • 假设第 \(i-1\) 次操作造成了一个右端点为 \(sum_{i-1}\) 的连通块,当增加 \(a_{i}\) 时,更新的区间为 \([\left\lceil \frac{a_{i}}{2} \right\rceil,a_{i}+sum_{i}]\)
    • 判断更新的区间与上一个区间有交还是不交(中间有断开的区间)即可。
      • 若有断开的区间 \((sum_{i},\left\lceil \frac{a_{i}}{2} \right\rceil)\) ,因为 \(\{ a \}\) 已经升序和 \(\left\lceil \frac{a_{i}}{2} \right\rceil>sum_{i}\) 所以后面不可能再填补上。
    点击查看代码
    ll a[100010];
    int main()
    {
    	freopen("buy.in","r",stdin);
    	freopen("buy.out","w",stdout);
    	ll n,sum=0,ans=0,i;
    	cin>>n;
    	for(i=1;i<=n;i++)
    	{
    		cin>>a[i];
    	}
    	sort(a+1,a+1+n);
    	for(i=1;i<=n;i++)
    	{
    		if(ceil(a[i]/2.0)>sum)
    		{
    			ans+=(a[i]+sum)-ceil(a[i]/2.0)+1;
    		}
    		else
    		{
    			ans+=a[i];
    		}
    		sum+=a[i];
    	}
    	cout<<ans<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T4\) T1042. ants \(20pts\)

  • 弱化版: BZOJ4358 permu
  • 部分分
    • \(20pts\) :枚举值域起点,时间复杂度为 \(O(nm)\)

      点击查看代码
      int a[100010],pos[100010],vis[100010];
      int main()
      {
      	freopen("ants.in","r",stdin);
      	freopen("ants.out","w",stdout);
      	int n,m,l,r,ans;
      	scanf("%d%d",&n,&m);
      	for(int i=1;i<=n;i++)
      	{
      		scanf("%d",&a[i]);
      		pos[a[i]]=i;
      	}
      	for(int j=1;j<=m;j++)
      	{
      		scanf("%d%d",&l,&r);
      		ans=0;
      		for(int i=1;i<=n;i++)
      		{
      			if(vis[i]==0)
      			{
      				int k;
      				for(k=i;l<=pos[k]&&pos[k]<=r;k++)
      				{
      					vis[k]=1;
      				}
      				ans=max(ans,k-i);
      			}
      			vis[i]=0;
      		}
      		printf("%d\n",ans);
      	}
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      
    • \(50pts\) :同 BZOJ4358 permu 的做法,时间复杂度为 \(O(n \sqrt{m} \log n)\) 。因普通版线段树常数过大所以无法通过。

      点击查看代码
      int a[400000],ans[400000],L[400000],R[400000],pos[400000],klen,ksum;
      struct ask
      {
      	int l,r,id;
      }q[400000];
      bool q_cmp(ask a,ask b)
      {
      	return (pos[a.l]==pos[b.l])?((pos[a.l]%2==1)?(a.r<b.r):(a.r>b.r)):(a.l<b.l);
      }
      struct SegmentTree
      {
      	int l,r,maxl,maxr,ans;
      }tree[400000];
      int lson(int x)
      {
      	return x*2;
      }
      int rson(int x)
      {
      	return x*2+1;
      }
      void pushup(int rt)
      {
      	tree[rt].maxl=tree[lson(rt)].maxl+(tree[lson(rt)].maxl==tree[lson(rt)].r-tree[lson(rt)].l+1)*tree[rson(rt)].maxl;
      	tree[rt].maxr=tree[rson(rt)].maxr+(tree[rson(rt)].maxr==tree[rson(rt)].r-tree[rson(rt)].l+1)*tree[lson(rt)].maxr;
      	tree[rt].ans=max(max(tree[lson(rt)].ans,tree[rson(rt)].ans),tree[lson(rt)].maxr+tree[rson(rt)].maxl);
      }
      void build(int rt,int l,int r)
      {
      	tree[rt].l=l;
      	tree[rt].r=r;
      	if(l==r)
      	{
      		return;
      	}
      	int mid=(l+r)/2;
      	build(lson(rt),l,mid);
      	build(rson(rt),mid+1,r);
      	pushup(rt);
      }
      void update(int rt,int pos,int ans)
      {
      	if(tree[rt].l==tree[rt].r)
      	{
      		tree[rt].ans=tree[rt].maxl=tree[rt].maxr=ans;
      		return;
      	}
      	else
      	{
      		int mid=(tree[rt].l+tree[rt].r)/2;
      		if(mid>=pos)
      		{
      			update(lson(rt),pos,ans);
      		}
      		else
      		{
      			update(rson(rt),pos,ans);
      		}
      		pushup(rt);
      	}
      }
      SegmentTree query(int rt,int l,int r)
      {
      	if(l<=tree[rt].l&&tree[rt].r<=r)
      	{
      		return tree[rt];
      	}
      	else
      	{
      		int mid=(tree[rt].l+tree[rt].r)/2;
      		if(r<=mid)
      		{
      			return query(lson(rt),l,r);
      		}
      		else
      		{
      			if(l>mid)
      			{
      				return query(rson(rt),l,r);
      			}
      			else
      			{
      				SegmentTree p=query(lson(rt),l,r),q=query(rson(rt),l,r),num;
      				num.maxl=p.maxl+(p.maxl==p.r-p.l+1)*q.maxl;
      				num.maxr=q.maxr+(q.maxr==q.r-q.l+1)*p.maxr;
      				num.ans=max(max(p.ans,q.ans),p.maxr+q.maxl);
      				return num;
      			}
      		}
      	}
      }
      void init(int n,int m)
      {
      	klen=n/sqrt(m)+1;
      	ksum=500;
      	for(int i=1;i<=ksum;i++)
      	{
      		L[i]=R[i-1]+1;
      		R[i]=R[i-1]+klen;
      	}
      	if(R[ksum]<n)
      	{
      		ksum++;
      		L[ksum]=R[ksum-1]+1;
      		R[ksum]=n;
      	}
      	for(int i=1;i<=ksum;i++)
      	{
      		for(int j=L[i];j<=R[i];j++)
      		{
      			pos[j]=i;
      		}
      	}
      }
      void add(int x)
      {
      	update(1,a[x],1);
      }
      void del(int x)
      {
      	update(1,a[x],0);
      }
      int main()
      {
      	freopen("ants.in","r",stdin);
      	freopen("ants.out","w",stdout);
      	int n,m,l=1,r=0,i;
      	cin>>n>>m;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i];
      	}
      	build(1,1,n);
      	init(n,m);
      	for(i=1;i<=m;i++)
      	{
      		cin>>q[i].l>>q[i].r;
      		q[i].id=i;
      	}
      	sort(q+1,q+1+m,q_cmp);
      	for(i=1;i<=m;i++)
      	{
      		while(l>q[i].l)
      		{
      			l--;
      			add(l);
      		}
      		while(r<q[i].r)
      		{
      			r++;
      			add(r);
      		}
      		while(l<q[i].l)
      		{
      			del(l);
      			l++;
      		}
      		while(r>q[i].r)
      		{
      			del(r);
      			r--;
      		}
      		ans[q[i].id]=query(1,1,n).ans;
      	}
      	for(i=1;i<=m;i++)
      	{
      		cout<<ans[i]<<endl;
      	}
      	return 0;
      }
      
  • 正解
    • 考虑只加不减莫队。加入一个数 \(x\) 时判断 \(x-1,x+1\) 是否已经加入并连一条边,接着链表或可撤销并查集维护即可。

      点击查看代码
      struct ask
      {
      	int l,r,id;
      }q[100010];
      int a[100010],b[100010],ans[100010],L[100010],R[100010],pos[100010],vis[100010],klen,ksum;
      bool q_cmp(ask a,ask b)
      {
      	return (pos[a.l]==pos[b.l])?(a.r<b.r):(a.l<b.l);
      }
      void init(int n,int m)
      {
      	klen=n/sqrt(m)+1;
      	ksum=n/klen;
      	for(int i=1;i<=ksum;i++)
      	{
      		L[i]=R[i-1]+1;
      		R[i]=R[i-1]+klen;
      	}
      	if(R[ksum]<n)
      	{
      		ksum++;
      		L[ksum]=R[ksum-1]+1;
      		R[ksum]=n;
      	}
      	for(int i=1;i<=ksum;i++)
      	{
      		for(int j=L[i];j<=R[i];j++)
      		{
      			pos[j]=i;
      		}
      	}
      }
      struct DSU
      {
      	struct quality
      	{
      		int id,fa,siz;
      	};
      	stack<quality>s;
      	int fa[100010],siz[100010];
      	void init(int n)
      	{
      		for(int i=1;i<=n;i++)
      		{
      			fa[i]=i;
      			siz[i]=1;
      		}
      	}
      	int find(int x)
      	{
      		return (fa[x]==x)?x:find(fa[x]);
      	}
      	void merge(int x,int y,int &sum)
      	{
      		x=find(x);
      		y=find(y);
      		if(x!=y)
      		{
      			if(siz[x]<siz[y])
      			{
      				swap(x,y);
      			}
      			s.push((quality){x,fa[x],siz[x]});  
      			s.push((quality){y,fa[y],siz[y]});
      			fa[y]=x;
      			siz[x]+=siz[y];
      			sum=max(sum,siz[x]);
      		}
      	}
      	void split(int tmp)
      	{
      		while(s.empty()==0&&s.size()>tmp)
      		{
      			fa[s.top().id]=s.top().fa;
      			siz[s.top().id]=s.top().siz;
      			s.pop();
      		}
      	}
      }D;
      void add(int x,int &sum)
      {
      	vis[x]=1;
      	if(vis[x-1]==1)
      	{
      		D.merge(x,x-1,sum);
      	}
      	if(vis[x+1]==1)
      	{
      		D.merge(x,x+1,sum);
      	}
      }
      int main()
      {
      	freopen("ants.in","r",stdin);
      	freopen("ants.out","w",stdout);
      	int n,m,l=1,r=0,sum,tmp=1,num,lastblock=0,i,j;
      	cin>>n>>m;
      	for(i=1;i<=n;i++)
      	{
      		cin>>a[i];
      	}
      	for(i=1;i<=m;i++)
      	{
      		cin>>q[i].l>>q[i].r;
      		q[i].id=i;
      	}
      	init(n,m);
      	sort(q+1,q+1+m,q_cmp);
      	D.init(n);
      	for(i=1;i<=m;i++)
      	{
      		if(pos[q[i].l]==pos[q[i].r])
      		{
      			sum=num=1;
      			for(j=q[i].l;j<=q[i].r;j++)
      			{
      				b[j]=a[j];
      			}
      			sort(b+q[i].l,b+q[i].r+1);
      			for(j=q[i].l+1;j<=q[i].r;j++)
      			{
      				num=(b[j-1]==b[j]-1)*num+1;
      				sum=max(sum,num);
      			}
      		}
      		else
      		{
      			if(lastblock!=pos[q[i].l])
      			{
      				while(r>R[pos[q[i].l]])
      				{
      					vis[a[r]]=0;
      					r--;
      				}
      				while(l<R[pos[q[i].l]]+1)
      				{
      					vis[a[l]]=0;
      					l++;
      				}
      				tmp=1;
      				r=R[pos[q[i].l]];
      				l=R[pos[q[i].l]]+1;
      				lastblock=pos[q[i].l];
      				D.split(0);
      			}
      			while(r<q[i].r)
      			{
      				r++;
      				add(a[r],tmp);
      			}
      			sum=tmp;
      			num=D.s.size();
      			while(l>q[i].l)
      			{
      				l--;
      				add(a[l],sum);
      			}
      			D.split(num);
      			while(l<R[pos[q[i].l]]+1)
      			{   
      				vis[a[l]]=0;
      				l++;
      			}
      		}
      		ans[q[i].id]=sum;
      	}
      	for(i=1;i<=m;i++)
      	{
      		cout<<ans[i]<<endl;
      	}
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      

总结

  • 感觉思维越来越不行了,需要稍微跳跃点的就反应不过来了。
  • \(T2\) 只判了叶子节点的 \(d\) 忘判非叶子节点的 \(g\) 了,挂了 \(20pts\)
  • \(T3\) 只记得写子任务 \(3\) 的超大背包部分分了,忘了写子任务 \(1\) 的正常背包部分分了,挂了 \(30pts\)
  • \(T4\) 赛时没记得做过原题,我是不是该拉出去毙了。
posted @ 2024-10-08 18:22  hzoi_Shadow  阅读(34)  评论(0编辑  收藏  举报
扩大
缩小