清北学堂 2017-10-06

本文解析了三道算法竞赛题目:寻找因子最多的最佳进制数、树的同构节点标号及构建符合特定条件的二元关系数组。通过深入分析与代码实现,提供了清晰的解答思路。

****

因为是刚听完课所以想把思路记下来,有一些其实也是一知半解的,如果有dalao可以帮忙讲解那真是再感谢不过了。

*****还有为什么我画图这么丑,哇的一下哭出声*******

Problem A. 最佳进制

如今我们最常用的是十进制,据说这是因为人有十根手指。
但事实上这并不是十分方便,10 只有四个因子 1、2、5、10,

像 1/3、1/6 这些分数在十进制下的十数表示就
不是很优美。在这种要求下,12、24 甚至60 进制会更好一些。
现在想求出不超过 n 的最佳进制数,也就是拥有最多的因子。
Input
第一包含一个整数 n,(1 ≤ n ≤ 1e16)。
Output
输出一个数 c,表示最佳进制数。

Examples
Input

 100

Output
60

Subtasks
对于20% 的数据,n<=100。
对于40% 的数据,n<=1e5。
对于60% 的数据,n<=1e9。
对于100% 的数据,k<=1e6。

注:若有多个解,输出最小的那个

解析:

求因子最多的数

一个大多数人都知道但我不知道的关系:x=∏Pi^ai //∏为累乘符号

即x为多个质因数(Pi)的ai次方的乘积

则这个数所有的因子个数ans=∏(ai+1)

例:12=(2^2)*(3^1)

还有就是如果ai是x的因子,则a1~a(i-1)一定都为x的因子(这里不是很能理解,如果这个数是质数咋办???求告知) 即a1>=a2>=a3….>=an

由于题目范围为1e16,且2*3*5*……*47>1e16,则分解因数只需到47

另外就是如果x|y,则x的每个质因数一定也为y的质因数,且幂次一定比在y的幂次小

又因为(2^3)*(3^2)与(2^2)*(3^3)的因子数是一样的,因此最好让小的质因子的幂次更高

下面就是愉快的代码时间了

#include 
#include 
#include 
#include 
using namespace std;
long long n, maxd, ans;
int prime[] = {0, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47};
void dfs(int x, long long sum, long long nowd, long long Limit) {
	if (nowd > maxd || (nowd == maxd && sum < ans))
		maxd = nowd, ans = sum;
	if (x > 11) return;
	for (int i = 1; sum * prime[x] <= n && i <= Limit; i++)
		dfs(x + 1, sum *= prime[x], nowd * (i + 1), i);
}
int main()
{
	freopen("divisors.in","r",stdin);
	freopen("divisors.out","w",stdout);
	cin >> n;
	ans = 1;
	if (n > 1) dfs(1, 1, 1, 1e9);
	cout << ans << endl;
	return 0;  
}
 

 

Problem B. 树
File: tree.*
Time limit: 1s
Memory limit: 256MB
给出一棵节点数为n 的有根树,现在需要给每个节点标号,要求对应子树同构的节点标号相同。
若图G1,G2 同构,则存在G1 点集和G2 点集之间的双射ϕ,满足u ->v 是G1 中的一条边当且仅当
ϕ(u) ! ϕ(v) 是G2 中的一条边。
Input
第一包含一个数n(1<=n<=105),表示树的点数。
第二行包含n - 1 个数,第i 个数表示编号为i + 1 点的父节点编号,满足父节点的编号小于子节点,根
节点编号为1。
Output
一行包含n 个数,表示节点标号,标号大小范围在1 到n 之间。如果有多种标号,给出字典序最小的标
号。

Examples
Input
9
1 2 2 1 5 5 7 7

Output

1 2 3 3 4 3 2 3 3

Subtasks
对于30% 的数据,n<=12。
对于50% 的数据,n<=100。
对于100% 的数据,n<=105。

解析:

一道”简单的”Hash题

先说一下啥叫同构:即子树的节点和构成相同(即相对位置相同)

在下图中,4/5/7/8/9全为叶子结点,则他们位置相同;2/6都各有两个子节点,则它们位置相同;而1/3的子节点各不相同,应处于不同的位置(位置也即最后要求的节点编号)

 

另外,子树是可以旋转的,也就是说下面的这两棵树也是同构的

 

下面就开始Hash了

由于是否同构看到是子树,因此我们从叶子到根Hash,Hash值相同的则为同构

 

在上图中,令4/7/8/9/10/11都为空集,也就是[ ],则5/6均为[1,1],2/3为[1,2]/[2,1](一样的),1为[3,3].

Hash完可以发现4/7/8/9/10/11的值是一样的,5/6的值一样,2/3的值一样,1自己一个值

具体如何Hash呢?即将每个数看成n进制的,然后将它们化为10进制,例n进制下的12为10进制下的1*n+2

当所有节点的Hash值都求完后,我们设一个计数器cnt,每当出现一个新数,就cnt++,然后把cnt作为标号赋给当前值,若此Hash值之前已经出现,则直接输出

具体实现可用map函数,怎么写在代码里就不多说了

 

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
const int N = 120000;
vector E[N];
map, int> id;
int ans[N], p[N], vis[N];
int n, cnt;
int work(int x) {
	vector u;
	for(int i = 0; i < E[x].size(); i++) {
		int y = E[x][i];
		u.push_back(work(y));
	}
	sort(u.begin(), u.end());
	if (!id[u]) id[u] = ++cnt;
	ans[x] = id[u];
	return ans[x];//返回x的hash值 
}


int main() {
	freopen("tree.in", "r", stdin);
	freopen("tree.out", "w", stdout);
	scanf("%d", &n);
	for(int i = 2; i <= n; i++) {
		scanf("%d", &p[i]);
		E[p[i]].push_back(i);
	}
	work(1);
	cnt = 0;

	for(int i = 1; i <= n; i++) {
		if (!vis[ans[i]]) {
			vis[ans[i]] = ++cnt;
		}
		ans[i] = vis[ans[i]];
	}

	for(int i = 1; i <= n; i++) printf("%d ", ans[i]);
	puts("");
}


Problem C. 关系
File: relations.*
Time limit: 1s
Memory limit: 256MB
有一个二元关系R,R 可以被表示成n*n 的布尔数组。
现在希望找到长度都为n 的数组f 和g,要求满足Rx,y = 1 当且仅当f(x)<=g(y)。
Input
第一行包含一个整数n(1<=n<=1000),表示数组的大小。
接下来的n 行表示二元关系R。
Output
第一行输出能否找到数组f 和g,如果能找到输出YES,否则输出NO。-1e9<=fi; gi<=109
第二行输出n 个整数,表示数组f。
第三行输出n 个整数,表示数组g 。

Examples
Input
3
111
110
100

Output

YES
0 1 2
2 1 0

Subtasks
对于20% 的数据,n<=10。
对于50% 的数据,n<=100。
对于100% 的数据,n<=1000。

解析:

Zcc dalao说看到这个就应该想到图啊(我没想到但我记住了)

如果a<=b,b<=c,则一定有a<=c

即R[a][b]=1,R[b][c]=1--->>R[a][c]=1若不满足此条件则输出”NO”

我们另R[a][b]=1当且仅当f[a]>g[b]时(为了建图方便)

如果R[x][y]=1,则连一条x到y+n(为了方便输出)

同理,如果R[x][y]=0,则连一条x到y+n

则可得到一个DAG(有向无环图)[因为a<b,b<c,c<a不可能同时成立,则应为DAG]

之后就拓扑排序求值啦(第几个拿到则为几)

代码

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

int f[1005], g[1005], cnt[1005], n;

int cmp(int i, int j) {
	if (cnt[i] == cnt[j]) return (i < j);
	return cnt[i] < cnt[j];	
}

int main() {
	string s[1005];
	vector v;

	freopen("relations.in", "r", stdin);
	freopen("relations.out", "w", stdout);

	cin >> n;

	for(int i=0; i> s[i];
		v.push_back(i);
		cnt[i] = 0;

		for(int j=0; j s[y][j]) {
				correct = false;
				break;		
			}

		if(!correct) break;	
	}

	for(int i=0; i
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值