10.06 国庆节第九场模拟赛

密钥(key)

Description

在这个问题中,一个密钥是指一个长度为(3n)的二进制序列,其中(n)是正整数。
序列的每一位从左往右依次被编号为(1)(3n) ,一个密钥的权值是指数字不同的相邻位的个数再加上(1) 。比如:
(000) 的权值是 (1)(011010100) 的权值是 (7)

密钥可以被修改。确切地说,你可以不断地进行下面的操作:任选两个相邻的位,然后同时将它们取反。例如,可以通过一次操作把 (000) 修改为 110 。

给定一个长度为(3n)的密钥,请通过不超过(n)次操作,将其修改为一个权值不少于(2n)的密钥。你可以认为合法方案必然存在。

Input

输入一行,包含一个长度为(3)的倍数的(01)序列。

Output

第一行包含一个整数(m),表示操作的次数,你需要保证(0 leq m leq n)

第二行包含(m)个正整数 (a_1,a_2,dots,a_m(1 leq a_i < n)),依次表示每次翻转第(a_i) 和第(a_{i+1})位。

如果初始密钥的权值已经不小于(2n)你可以仅输出一行一个整数(0)

xjb分析

玄学做法 (50pts)

​ 考虑到对答案有贡献的情况为当前位置与下一位置字符不同.

我们(O(n))枚举位置,判断当前位置(i)是否与下一位置(i+1)相同.

如果不同,我们可以尝试翻转这两个位置,但是想要对答案有贡献?我们需要再判断(i)位置与(i+2)位置与(i-1)的字符是否存在相同,再决定我们是否去变换这两个位置.

例如 :

我们这时候改变(i)位置,(i+1)位置的话,

这时候,对答案的贡献就会变多(+1)

然后莫名其妙这样做就(get)(50pts)

正解

看到(3n),我们考虑三个的情况.

存在这些情况.

  1. (000)  5.(111)
  2. (001)  6.(110)
  3. (011)  7.(100)
  4. (010)  8.(101)

我们发现,

(2.6)情况翻转中间位置可以使贡献更大.

(3.7)情况翻转最左边位置可以使贡献更大.

这个可以手试一下.

因此代码中简单地模拟一下即可 qwq。

代码

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cmath>
#include<string>
#include<cstring>
#define R register
using namespace std;
char s[300008];
int len,cnt,ans[300008],val;
int main()
{
//	freopen("key.in","r",stdin);
//	freopen("key.out","w",stdout); 
	scanf("%s",s+1);
	len=strlen(s+1);
	for(R int i=1;i<=len;i++)
		if(s[i]!=s[i-1] and i!=1)
			val++;
	if((val+1)>=2*(len/3))
	{
		puts("0");
		exit(0);
	}
	for(R int i=1;i<=len-2;i+=3)
	{
		if(s[i]=='0' and s[i+1]=='0' and s[i+2]=='1')
		{
			ans[++cnt]=i+1;
			s[i+1]='1';s[i+2]='0';
		}
		if(s[i]=='0' and s[i+1]=='1' and s[i+2]=='1')
		{
			ans[++cnt]=i;
			s[i]='1';s[i+1]='0';
		}
		if(s[i]=='1' and s[i+1]=='1' and s[i+2]=='0')
		{
			ans[++cnt]=i+1;
			s[i+1]='0';s[i+2]='1';
		}
		if(s[i]=='1' and s[i+1]=='0' and s[i+2]=='0')
		{
			ans[++cnt]=i;
			s[i]='0';s[i+1]='1';
		}
		if(s[i]=='1' and s[i+1]=='1' and s[i+2]=='1')
		{
			if(i==1)ans[++cnt]=i;
			else if(s[i-1]=='1')
			{
				ans[++cnt]=i;
				s[i]='0';
				s[i+1]='0';
			}
			else ans[++cnt]=i+1,s[i+1]='0',s[i+2]='1';
		}
		if(s[i]=='0' and s[i+1]=='0' and s[i+2]=='0')
		{
			if(i==1)ans[++cnt]=i;
			else if(s[i-1]=='0')
			{
				ans[++cnt]=i;
				s[i]='1';
				s[i+1]='1';
			}
			else ans[++cnt]=i+1,s[i+1]='1',s[i+2]='0';
		}
	}
	printf("%d
",cnt);
	for(R int i=1;i<=cnt;i++)
		printf("%d ",ans[i]);
	fclose(stdin);
	fclose(stdout);
	return 0;
}

最大公约数(gcd)

Description

Makik 是一名勤劳的学生。他在刚刚学完欧几里得算法时想多做一些练习题,于是在纸上写下了一个长度为(n)的排列。之后,他做了(m)轮练习。对于第(i)轮练习,他从排列中挑出一个区间([l_i,r_i]) ,并对处于这个区间中的元素两两求最大公约数,再找出这些最大公约数中的最大值。

Makik 已经成为最大公约数的大师了,而且仁慈地把这个练习方法介绍给了你。现在,请你也来做做看

Input

第一行包含两个数字(n,m),分别表示排列的长度和练习的轮数。
接下来一行(n)个数字,依次表示这个排列中的每个元素。
再接下来(m)行,其中第(i)行包含两个数字(l_i,r_i),表示在第(i)轮练习中挑出的区间。

Output

输出(m)行,每行一个数字,表示该轮练习里区间内的元素两两间最大公约数中的最大值。

xjb分析

暴力

 对于(m)次询问,直接枚举([l_i,r_i])区间的数,两两求(gcd)(max).

代码写法大致如下:

for(int i=1;i<=m;i++)
{
    scanf("%d%d",&l,&r);
    int now=0;
    for(int j=l;j<=r;j++)
        for(int k=j+1;k<=r;k++)
            now=max(now,gcd(a[j],a[k]));
}

时间复杂度为(O(m imes n^2 loga_i))

令人感到mmp的是,暴力部分全部(TLE)

正解

考虑到对答案有贡献的是这些数的约数.

因此我们对这些约数搞事。

做法

 对每个数求出约数,在一个区间内出现超过两次的约数,那么它肯定是某两个数的(gcd).

 这样我们把所有出现次数(geq 2)的约数加入线段树中维护最大值,并每次更新即可.

但是,如果在线做的话依旧不行.

  我们考虑将询问离线.

如何离线?

 考虑到我们必须要知道整个区间的数的约数的情况,所以我们对询问的右端点排序.

我们从小到大对右端点(r_i)进行排序,当遇到某个右端点的时候,输出ans即可.

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cctype>
#define ls o<<1
#define rs o<<1|1
#define N 100005
#define R register
using namespace std;
inline void in(int &x)
{
	int f=1;x=0;char s=getchar();
	while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
	while(isdigit(s)){x=x*10+s-'0';s=getchar();}
	x*=f;
}
struct cod{
	int l,r,idx;
	bool operator <(const cod&a)const
	{
		return r<a.r;
	}
}que[100008];
int ans[N];
int n,m,a[N],tr[N<<3],exist[N];
inline void up(int o){tr[o]=max(tr[ls],tr[rs]);}
void build(R int o,R int l,R int r)
{
	if(l==r)
	{
		tr[o]=0;
		return;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	up(o);
}
void change(int o,int l,int r,int pos,int del)
{
//	printf("%d %d %d %d %d",o,l,r,pos,del);
	if(l==r){tr[o]=max(tr[o],del);return;}
	int mid=(l+r)>>1;
	if(pos<=mid)change(ls,l,mid,pos,del);
	else change(rs,mid+1,r,pos,del);
	up(o);
}
int query(int o,int l,int r ,int x,int y)
{
//	printf("%d %d %d %d %d
",o,l,r,x,y);
	if(x<=l and r<=y)return tr[o];
	int mid=(l+r)>>1,res=0;
	if(x<=mid)res=max(res,query(ls,l,mid,x,y));
	if(y>mid)res=max(res,query(rs,mid+1,r,x,y));
	return res;
}
int main()
{
 //	freopen("gcd.in","r",stdin);
//	freopen("gcd.out","w",stdout);
	in(n),in(m);
	{
		for(R int i=1;i<=n;i++)in(a[i]);
		build(1,1,n);
		for(R int i=1;i<=m;i++)
			in(que[i].l),in(que[i].r),que[i].idx=i;
		sort(que+1,que+m+1);
		int now=1;
		for(R int i=1;i<=n;i++)
		{
			for(R int j=1;j*j<=a[i];j++)
			{
				if(a[i]%j==0)
				{
					if(exist[j])change(1,1,n,exist[j],j);//判断之前是否存在过
					if(exist[a[i]/j])change(1,1,n,exist[a[i]/j],a[i]/j);//判断之前是否存在过
					exist[j]=i,exist[a[i]/j]=i;
				}
			}
			while(i==que[now].r and now<=m)
			{
				ans[que[now].idx]=query(1,1,n,que[now].l,que[now].r);
				now++;
			}
		}
		for(R int i=1;i<=m;i++)printf("%d
",ans[i]);
	}
	fclose(stdin);
	fclose(stdout);
	return 0;
}

树(tree)

Description

给出一棵含有(n)个结点(n-1)条边的连通无向图,其中每个结点有点权(v_i) .

在这个无向图里,定义一条路径的权值为路径上的结点点权中最小值与路径上结点数量的乘积。那么,这个无向图里权值最大的路径的权值是多少?

Input

第一行包含一个整数(n), 表示这个无向图中结点数。

接下来一行包含(n)个整数,依次表示 (1)~(n)号结点的点权 。

接下来(n-1)行,每行包含两个整数(a,b),表示结点(a)与结点(b)间连有一条边

Output

输出一行一个整数,表示这个无向图里所有路径的权值中的最大值。

xjb分析

暴力((30pts+20pts))

​ 很明显,树上问题,还要两两点之间的.问题,我们优先考虑到了(LCA)

(LCA)预处理出来一些数组,并(n^2)枚举点对的话,我们可以get到(30pts)

(不过貌似其他人都(TLE)了?可能看脸(滑稽?

还有(20pts)的话是链的情况,我们可以判断这种情况,并写单调栈维护最值.从而get到(20pts).

这里代码就不放了 其实是找不到了QAQ.

正解

正解的话,还是比较麻烦的. emm

如果我们直接去求的话,显然复杂度上是过不去的.

首先考虑一个问题.我们可不可以通过来合并这些树.

合并这些树

如果我们知道了两棵树的直径,我们合并的话,显然可以知道一棵"大"树的直径.

合并时,一棵树的最长直径会对答案产生贡献,如果合并的话,我们显然会考虑加长这条最长的直径.

而合并时我们需要考虑如何合并才能使得到的直径最大(只需要判断即可.

存在情况如下.(图片只是帮助思考一下,建议手绘 qwq)

下面所说的(L)(R)均代表两点之间的距离.(引申为新树的直径).

1.左边树的(L)与右边树的(L)结合起来比较大.

2.左边树的(L)与右边树的(R)结合起来比较大.

3.左边树的(R)与右边树的(L)结合起来比较大.

4.左边树的(R)与右边树的(R)结合起来比较大.

5.左边树的(L)(R)本来就很大,不需要变化,直接合并即可.

6.右边树的(L)(R)本来就很大,不需要变化,直接合并即可.

如何维护路径上的权值最小?

​ 我们从大到小对点的点权排序,并按此顺序逐渐构建出一棵棵的树.(构建过程中可以合并.

​ 记当前要插入的点为(x),与其相连的点为(xx)

如果(v[xx]<v[x]),显然我们不会加入(xx)进去.免得对我们可能得到的更大的结果造成影响.

(此时我们钦定了当前点(x)的点权为一个衡量标准.小于它的点权的点将被舍去.

而我们按照点权从大到小加入点,会保证,我们计算出来的ans合法.

(即满足(color{red}{一条路径的权值为路径上的结点点权中最小值与路径上结点数量的乘积}).

因此考虑上面合并时的几种情况,我们便可构出代码.

代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cctype>
#define N 200008
#define R register
using namespace std;
inline void in(int &x)
{
	int f=1;x=0;char s=getchar();
	while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
	while(isdigit(s)){x=x*10+s-'0';s=getchar();}
	x*=f;
}
int depth[N],f[N][21],val[N],head[N],tot;
int n,ps[N],father[N],maxx;
int ans;
struct ccc{int l,r;}s[N];
struct cod{int u,v;}edge[N<<3];
struct code{
	int idx,va;
	bool operator <(const code&a)const
	{
		return va>a.va;
	}
}nw[N<<3];
inline void add(int x,int y)
{
	edge[++tot].u=head[x];
	edge[tot].v=y;
	head[x]=tot;
}
void dfs(int u,int fa)
{
	depth[u]=depth[fa]+1;
	f[u][0]=fa;
	ps[u]=ps[fa]+1;
	for(R int i=1;(1<<i)<=depth[u];i++)
		f[u][i]=f[f[u][i-1]][i-1];
	for(R int i=head[u];i;i=edge[i].u)
	{
		if(edge[i].v==fa)continue;
		dfs(edge[i].v,u);
	}
}
inline int lca(int x,int y)
{
	if(depth[x]>depth[y])swap(x,y);
	for(R int i=17;i>=0;i--)
		if(depth[x]+(1<<i)<=depth[y])
			y=f[y][i];
	if(x==y) return y;
	for(R int i=17;i>=0;i--)
	{
		if(f[x][i]==f[y][i])continue;
		x=f[x][i],y=f[y][i];
	}
	return f[x][0];
}
inline int calc(int i,int j){return ps[i]+ps[j]-2*ps[lca(i,j)]+1;}
int find(int x){return father[x]==x?x:father[x]=find(father[x]);}
inline int mxx(int d1,int d2,int d3,int d4,int d5,int d6)
{
	int x=max(max(max(max(max(d1,d2),d3),d4),d5),d6);
	maxx=max(maxx,x);
	if(x==d1)return 1;
	if(x==d2)return 2;
	if(x==d3)return 3;
	if(x==d4)return 4;
	if(x==d5)return 5;
	if(x==d6)return 6;
}
inline void m(int x)
{
	int fa=find(x);
	for(R int i=head[x];i;i=edge[i].u)
	{
		int xx=edge[i].v,faxx=find(xx);
		if(val[xx]<val[x] or faxx==fa)continue;
		int d1=calc(s[fa].l,s[fa].r);
		int d2=calc(s[fa].l,s[faxx].l);
		int d3=calc(s[fa].l,s[faxx].r);
		int d4=calc(s[fa].r,s[faxx].l);
		int d5=calc(s[fa].r,s[faxx].r);
		int d6=calc(s[faxx].l,s[faxx].r);
		int woc=mxx(d1,d2,d3,d4,d5,d6);
		if(woc==2)s[fa].r=s[faxx].l;
		if(woc==3)s[fa].r=s[faxx].r;
		if(woc==4)s[fa].l=s[faxx].l;
		if(woc==5)s[fa].l=s[faxx].r;
		if(woc==6)s[fa].l=s[faxx].l,s[fa].r=s[faxx].r;
		father[father[xx]]=father[x];
	}
}
inline void work()
{
	for(R int i=1;i<=n;i++)father[i]=i,s[i].l=s[i].r=i;
	for(R int i=1;i<=n;i++)
	{
		m(nw[i].idx);
		ans=max(ans,val[nw[i].idx]*maxx);
	}
}
int main()
{
//	freopen("tree.in","r",stdin);
//	freopen("tree.out","w",stdout);
	in(n);
	for(R int i=1;i<=n;i++)in(val[i]),nw[i].va=val[i],nw[i].idx=i;
	sort(nw+1,nw+n+1);
	for(R int i=1,a,b;i<n;i++)
	{
		in(a),in(b);
		add(a,b);
		add(b,a);
	}
	dfs(1,0);
	work();
	printf("%d",ans);
	fclose(stdin);
	fclose(stdout);
	return 0;
}
原文地址:https://www.cnblogs.com/-guz/p/9748068.html