Codeforces Global Round 13

F. Magnets

题目描述

点此看题

解法

不难发现机器返回的是 ((n_1-s_1)(n_2-s_2))

经典思路是找到一个未消磁的磁铁,然后挨个去验即可。

怎么找这个未消磁的磁铁呢?一开始我想的是把每个磁铁和剩下的所有磁铁验证,但是如果剩下磁铁是电中性的就判断不出来。正解是用机器去测 ([1,i-1])(i),如果第一次遇到有力那么 (i) 一定是为消磁的磁铁,这告诉我们这种一次问多个东西的题应该从少到多考虑

找到这个磁铁是 (n) 次,验证又需要 (n) 次,不是还过不了吗?设找到的磁铁是 (p),注意到 ([1,p-1]) 中有且仅有一个未消磁的磁铁,我们可以用 (log n) 的次数找到他,那么剩下的都是消磁磁铁,对于 ([p+1,n]) 我们暴力验证即可。

#include <cstdio>
#include <vector>
using namespace std; 
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,p;
int ask(int x,int y)
{
	printf("? %d %d
",x,1);
	for(int i=1;i<=x;i++) printf("%d ",i);
	printf("
%d
",y);
	fflush(stdout);
	return read();
}
void work()
{
	n=read();p=0;vector<int> rs;
	for(int i=2;i<=n;i++)
		if(ask(i-1,i)!=0)//there is a force
		{
			p=i;
			break;
		}
	int l=1,r=p-1,ans=0;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(ask(mid,p)!=0)
		{
			ans=mid;
			r=mid-1;
		}
		else l=mid+1;
	}
	for(int i=1;i<p;i++)
		if(i!=ans) rs.push_back(i);
	for(int i=p+1;i<=n;i++)
		if(ask(ans,i)==0) rs.push_back(i);
	printf("! %d",rs.size());
	for(int i=0;i<rs.size();i++) printf(" %d",rs[i]);
	puts("");fflush(stdout);
}
signed main()
{
	T=read();
	while(T--) work();
}

G. Switch and Flip

题目描述

点此看题

给你一个长度为 (n) 的硬币排列,初始 (i) 位置上的硬币是 (c_i),全部朝上,要求把这个硬币序列变成全部朝上的 ([1,n]) 的硬币排列,每次操作是交换两个硬币,然后翻转这两个硬币,最多使用 (n+1) 次操作。

(3leq nleq 2cdot 10^5)

解法

果然是 ( t tourist) 都做不出来的题,确实搅的很,我现在脑子还是痛。

排列问题可以从置换的角度思考,也就是初始有若干个置换环,要求通过操作得到 (n) 个置换环,可以把图建出来,第 (i) 个点有一个连出去的边 ((i,c[i])) ,我们设朝上为红朝下为蓝,操作就是交换两个点的边,这有助于我们思考问题的整体联系。

考虑一个置换环应该怎么做,这里需要有一个观察:如果是红点连向蓝点,那么对他们进行操作可以直接得到一个合法的大小为 (1) 的置换环,我们不妨先搞出两个蓝点(蓝点的数量一定是偶数),那么按顺序把剩下的红点消掉,最后消掉这两个蓝点,如下图:

对于一个长度为 (n) 用掉的操作是 (n+1),但是原图可能有若干个环,都这么做的话还是不行。

那么思考能不能让某些环一起操作来减少操作数,考虑两个环生成两个蓝点并且融合成一个环只需要一步,然后就可以用单个环的方法做了,所以总消耗步数是环大小之和的,如下图:

那么总操作数至多是 (n+1) 的,这里我必须提醒一点:建图只是用来帮助你思考的,但是本题却不能用图来实现代码,因为图上点代表的是权值,但是我们要操作的是位置,如果对着图硬想就凉了!还有如果环长为 (2) 的时候是不能用第一种方法的,所以我们优先找一个长度为 (1) 的置换环和他操作即可,如果整个图只有一个置换环就用第一种方法。

#include <cstdio>
#include <vector>
using namespace std;
const int M = 200005;
#define pii pair<int,int>
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}

	return x*f;
}
int n,p,a[M],b[M];vector<pii> r;
void work(int i,int j)//operation of swap
{
	swap(a[i],a[j]);
	a[i]=-a[i];a[j]=-a[j];
	r.push_back(make_pair(i,j));
}
void cyc(int x,int y)//operation of two cycles
{
	work(x,y);
	int now=x;//start from here
	while(a[-a[now]]>0) work(now,-a[now]);
	//after operation,the bule position is still now
	now=-a[now];//another blue one maybe is not y
	while(a[-a[now]]>0) work(now,-a[now]);
	work(now,-a[now]);
}
signed main()
{
	n=read();p=-1;
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=1;i<=n;i++)
	{
		if(b[i]) continue;
		int x=i;
		while(!b[x]) b[x]=1,x=a[x];
		if(p==-1) p=x;//there is no last cycle
		else cyc(p,x),p=-1;//operate with the last cycle
	}
	if(p>0)//there is still a cycle remain
	{
		int fl=0;
		for(int i=1;i<=n;i++)
			if(a[i]==i)
			{
				cyc(p,i);fl=1;//operate with a single element
				break;
			}
		if(!fl)//operate itself
		{
			int t1=a[p],t2=a[a[p]];
			work(p,t1);
			cyc(t1,t2);
		}
	}
	printf("%d
",r.size());
	for(int i=0;i<r.size();i++)
		printf("%d %d
",r[i].first,r[i].second);
}

H. Yuezheng Ling and Dynamic Tree

题目描述

点此看题

解法

势能分析好题,让张老师做她肯定会

首先如果去维护数的话就太麻烦了,既然这道题给的是序列,那我们在序列上处理这个问题。

考虑对序列分块,这道题的修改是 (max(a_i-x,1)),也就是单向的修改,每个点的父亲只会往前移动,这可以自然地想到势能分析。我们考虑块内要维护什么信息,肯定要维护适合势能分析的东西,那么我们就维护每个点在块内的最小祖先,因为可以一次跳过整块所以这样维护这东西是利于询问的。

考虑如果一个块被整块修改 (sqrt n) 次那么块内所有点最小祖先都会是自己,这时候修改就没有意义了。所以每次修改整块如果有意义就 (O(sqrt n)) 暴力重构,否则就打标记,散块直接暴力重构,使用并查集即可,时间复杂度 (O(nsqrt ncdot alpha))

那么怎么用我们维护的信息查询呢?如果两个点在同一个块内就判断一下是否有同一个祖先,如果有的话暴力跳,否则跳出这个块。如果不在同一个块就直接把编号大的那个点跳出块,时间复杂度也是 (O(nsqrt ncdot alpha))


还有一种做法是维护不在块内的最大祖先,大体思路是一样的,但是时间复杂度 (O(nsqrt n))

#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const int M = 400005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,bk,a[M],L[M],R[M],fa[M],pos[M],siz[M],del[M],tot[M];
int find(int x)
{
	if(x!=fa[x]) fa[x]=find(fa[x]);
	return fa[x];
}
void merge(int x,int y)
{
	int u=find(x),v=find(y);fa[v]=u;
}
void make(int b)
{
	for(int i=L[b];i<=R[b];i++) fa[i]=i;
	for(int i=L[b];i<=R[b];i++)
		if(pos[a[i]]==b) merge(a[i],i);
}
void add(int b,int x)
{
	tot[b]+=x;
	for(int i=L[b];i<=R[b];i++) a[i]=max(a[i]-x,1LL);
	make(b);
}
signed main()
{
	n=read();m=read();
	for(int i=2;i<=n;i++)
		a[i]=read();
	bk=sqrt(n);
	for(int i=1;i<=bk;i++)
	{
		L[i]=(i-1)*bk+1;R[i]=i*bk;
		siz[i]=R[i]-L[i]+1;
		for(int j=L[i];j<=R[i];j++) pos[j]=i;
		make(i);
	}
	if(R[bk]!=n)
	{
		bk++;L[bk]=R[bk-1]+1;R[bk]=n;
		siz[bk]=R[bk]-L[bk]+1;
		for(int i=L[bk];i<=R[bk];i++) pos[i]=bk;
		make(bk); 
	}
	while(m--)
	{
		int op=read();
		if(op==1)
		{
			int l=read(),r=read(),x=read();
			if(pos[l]==pos[r])
			{
				for(int i=l;i<=r;i++)
					a[i]=max(a[i]-x,1ll);
				make(pos[l]);
				continue;
			}
			for(int i=pos[l]+1;i<pos[r];i++)
			{
				if(tot[i]>siz[i]) del[i]+=x;
				else add(i,x);
			}
			for(int i=l;i<=R[pos[l]];i++) a[i]=max(a[i]-x,1ll);
			make(pos[l]);
			for(int i=L[pos[r]];i<=r;i++) a[i]=max(a[i]-x,1ll);
			make(pos[r]);
		}
		else
		{
			int u=read(),v=read(),ans=1;
			while(u!=1 && v!=1)
			{
				if(pos[u]==pos[v])//in the same block
				{
					if(find(u)==find(v))//own the same root in the block
					{
						while(u!=v)
						{
							if(u>=v) swap(u,v);
							v=max(1ll,a[v]-del[pos[v]]);
						}
						ans=u;
						break;
					}
					//otherwise the answer isn't in the block
					u=max(1ll,a[find(u)]-del[pos[u]]);
					v=max(1ll,a[find(v)]-del[pos[v]]);
				}
				else//otherwise we jump the bigger one out of the block
				{
					if(u>=v) swap(u,v);
					v=max(1ll,a[find(v)]-del[pos[v]]);
				}
			}
			printf("%lld
",ans);
		}
	}
}
#include <cstdio>
#include <iostream>
#include <cmath>
using namespace std;
const int M = 200005; 
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,q,a[M],b[M],L[M],R[M],cnt[M],f[M],c[M];
void build(int w)
{
	for(int i=L[w];i<=R[w];i++)
	{
		a[i]=max(1,a[i]-f[w]);
		if(a[i]<L[w]) b[i]=a[i];
		else b[i]=b[a[i]];
	}
	f[w]=0;
}
void upd()
{
	int l=read(),r=read(),x=read();
	if(c[l]==c[r])
	{
		for(int i=l;i<=r;i++)
			a[i]=max(a[i]-x,1);
		build(c[l]);
		return ;
	}
	for(int i=l;i<=R[c[l]];i++)
		a[i]=max(a[i]-x,1);
	for(int i=L[c[r]];i<=r;i++)
		a[i]=max(a[i]-x,1);
	build(c[l]);build(c[r]);
	for(int i=c[l]+1;i<c[r];i++)
	{
		if(cnt[i]<m)//brute
		{
			for(int j=L[i];j<=R[i];j++)
				a[j]=max(a[j]-x,1);
			build(i); 
			cnt[i]+=x;
		}
		else f[i]=min(n,f[i]+x);//tag
	}
}
signed main()
{
	n=read();q=read();
	m=sqrt(n);a[1]=1;
	for(int i=2;i<=n;i++)
		a[i]=read();
	for(int i=1;i<=n;i++)
	{
		c[i]=(i-1)/m+1;
		if(!L[c[i]]) L[c[i]]=i;
		R[c[i]]=i; 
	}
	for(int w=1;w<=c[n];w++)
		build(w);
	while(q--)
	{
		int op=read();
		if(op==1)
		{
			upd();
			continue;
		}
		int u=read(),v=read();
		while(b[u]!=b[v])
		{
			int t1=max(1,b[u]-f[c[u]]);
			int t2=max(1,b[v]-f[c[v]]);
			if(c[u]>c[v]) u=t1;
			else if(c[u]<c[v]) v=t2;
			else u=t1,v=t2; 
		}
		while(u!=v)
		{
			if(u>v) u=max(1,a[u]-f[c[u]]);
			else v=max(1,a[v]-f[c[v]]);
		}
		printf("%d
",u);
	}
}
原文地址:https://www.cnblogs.com/C202044zxy/p/14773556.html