P5080 Tweetuzki 爱序列

题目大意

给出一些数,需要求出 a i + 1 3 = a i \frac{a_{i+1}}{3}=a_i 3ai+1=ai a i + 1 = 2 × a i a_{i+1}=2 \times a_i ai+1=2×ai 时最长的序列 a a a.

分析

可以发现符合条件的序列 a a a 中不会出现重复的数字,而且对于一个数它的下一个位置最多只有两种情况,于是问题就变成了无环有向图最长链,这样就可以想到记忆化搜索, f i f_i fi 表示 i i i 为起点时的最长链长度,为了记录下路径,所以还需要用一个 n x t i nxt_i nxti 表示 i i i 的下一个位置时什么,最后直接输出就好了.

代码

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int MAXN=1e6+7;
int N;
struct Edge//链式前向星
{
	int to,next;
}edge[MAXN*2];
int edge_head[MAXN];
int edge_cnt=0;
#define TO_POINT(now) for(int edge_i=edge_head[now];edge_i;edge_i=edge[edge_i].next)
#define TO edge[edge_i].to
void AddEdge(int f,int t)
{
	edge[++edge_cnt].to=t;
	edge[edge_cnt].next=edge_head[f];
	edge_head[f]=edge_cnt;
}
bool visit[MAXN];
long long arr[MAXN];//原数组
long long a[MAXN];//去重以后的数组
int f[MAXN];//记录每个位置的最长链长度
int Find(long long num)//二分查找是否存在这个数
{
	int left=1,right=N,middle;
	while(left<=right)
	{
		middle=(left+right)>>1;
		if(a[middle]>=num)
		{
			right=middle-1;
			if(a[middle]==num)
			{
				return middle;
			}
		}
		else
		{
			left=middle+1;
		}
	}
	return 0;
}
int nxt[MAXN];//记录下一个位置
void DFS(int now)//DFS
{
	if(f[now])//如果搜索过就不需要搜索了
	{
		return;
	}
	f[now]=1;//开始的长度为1
	TO_POINT(now)
	{
		DFS(TO);
		if(f[TO]+1>f[now])//找到在自己连出的边中的最长链长度
		{
			f[now]=f[TO]+1;
			nxt[now]=TO;//记录下一个位置
		}
	}
}
int main()
{
	scanf("%d",&N);
	REP(i,1,N)
	{
		scanf("%lld",&arr[i]);
	}
	sort(arr+1,arr+1+N);
	arr[0]=arr[1]-1;
	int cnt=0;
	REP(i,1,N)//去重
	{
		if(arr[i]!=arr[i-1])
		{
			a[++cnt]=arr[i];
		}
	}
	N=cnt;
	int l;
	REP(i,1,N)//对于每一个点连边
	{
		if(a[i]%3==0)//需要判断整除
		{
			l=Find(a[i]/3);
			if(l)
			{
				AddEdge(i,l);
			}
		}
		l=Find(a[i]*2);
		if(l)
		{
			AddEdge(i,l);
		}
	}
	int answer=0,st;
	REP(i,1,N)//如果没有访问过就DFS这个位置
	{
		if(!f[i])
		{
			DFS(i);
		}
		if(f[i]>answer)//记录下最长链的开头
		{
			answer=f[i];
			st=i;
		}
	}
	printf("%d\n",answer);//输出答案
	int now=st;
	while(now)
	{
		printf("%lld ",a[now]);
		now=nxt[now];
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值