zoukankan      html  css  js  c++  java
  • 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);
    	}
    }
    
  • 相关阅读:
    php写的几种常见算法
    无状态登陆:JWT
    boostrap中日期控件使用
    boostrap中文件上传使用组件fileinput
    ubuntu环境下homestead安装运行nsq
    Ubuntu 下修改mysqlroot密码
    从git到lnmp代码发布
    maven的xml中报错:org.apache.maven.archiver.MavenArchiver.getManifest(org.apache.maven.project.MavenProject, org.apache.maven.archiver.MavenArchiveConfiguration)
    给mysql查询添加序号列
    maven jar包下载不下来
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14773556.html
Copyright © 2011-2022 走看看