等比数列三角形 (数论 + 黄金分割点)+ JOISC 2016 Day3 T3 「电报」(基环树 + 拓扑排序)

7 篇文章 0 订阅
4 篇文章 0 订阅

T1:等比数列三角形

题目

求三边都是 ≤n 的整数,且成等比数列的三角形个数
注意三角形面积不能为 0
注意 oeis 中未收录此数列,所以并不需要去搜了

输入格式
一行一个整数 n
输出格式
一行一个整数表示答案

样例
样例输入1
9
样例输出1
10
样例解释1
除去 9 个等边三角形,还有 {4,6,9} 。

样例输入2
100
样例输出2
133

数据范围与提示
一共有 4 个子任务,对于每一个子任务,你只有通过了该子任务的所有测试点,才能获得此子任务的分数
有 10pts,保证 n≤10
有 20pts,保证 n≤105
有 20pts,保证 n≤105
有 50pts,保证 n≤1012
对于所有数据,有1≤ n≤1012

题解

在这里插入图片描述
注意{4,6,9},{6,4,9},{9,6,4}算同一个三角形,所以不用管顺序
设三条边从小到大分别为 a , a k , a k 2 a,ak,ak^2 a,ak,ak2( k k k为公比且为正整数)
所以 1 ≤ k 1≤k 1k


就然要构成三角形,必然要满足
a + a k > a k 2 = > 1 + k > k 2 = > k 2 − k − 1 < 0 a+ak>ak^2=>1+k>k^2=>k^2-k-1<0 a+ak>ak2=>1+k>k2=>k2k1<0
解得 k < √ 5 + 1 2 k<\frac{√5+1}{2} k<25+1

综上 k ∈ [ 1 , √ 5 + 1 2 ) k∈[1,\frac{√5+1}{2}) k[1,25+1)


接下来令 k = p q k=\frac{p}{q} k=qp( q , p q,p q,p互质),最大边就表示为 a ∗ p 2 q 2 a*\frac{p^2}{q^2} aq2p2
最大边 ≤ n ≤n n,故此 q ≤ √ n q≤√n qn
因为 q = p k q=\frac{p}{k} q=kp,那么

  • k k k取最大时, q q q取最小,把 k = √ 5 + 1 2 k=\frac{√5+1}{2} k=25+1带入就解出了 q q q的最小值,
    但因为k取不到这个开区间,q的这个最小值也是一个开区间
  • 当k取最小时, q q q取最大,这是满足 q = p q=p q=p q q q的这个最大值是一个闭区间

综上 q ∈ ( p ∗ 2 √ 5 + 1 , p ] q∈(p*\frac{2}{√5+1},p] q(p5+12,p],在这里有个转化思想,把 p p p代换成 q q q,得到 q ∈ ( q ∗ 2 √ 5 + 1 , q ] q∈(q*\frac{2}{√5+1},q] q(q5+12,q]
我们就可以枚举 p ∈ [ 1 , √ n ] p∈[1,√n] p[1,n],算出此时q的可取值个数
注意:其实理解成枚举 q q q也是说得通的
在这里插入图片描述


当这样统计完后,会出现一个问题
{ 2 , 3 , 6 } , { 4 , 6 , 9 } \{2,3,6\},\{4,6,9\} {236},{469}这种公比为 3 2 \frac{3}{2} 23的三角形,会被重复计算进公比为 6 4 \frac{6}{4} 46
所以我们需要把这些排除掉,这也是为什么上面推导的时候 p q \frac{p}{q} qp要保证互质
这里就可以用类似埃筛的方法,把 x x x的因数里面算过的三角形减掉

要正着减,如果倒着,公比为 12 8 \frac{12}{8} 812会先减掉公比为 9 6 \frac{9}{6} 69而这里面还包含着 3 2 \frac{3}{2} 23
会导致 3 2 \frac{3}{2} 23被重复减掉


代码实现

#include <cstdio>
#include <cmath>
using namespace std;
#define LL long long
#define MAXN 1000005
LL n, result;
int ok[MAXN];
int main() {
	scanf ( "%lld", &n );
	int sqt = sqrt ( n );
	for ( int i = 1;i <= sqt;i ++ ) {
		int t = i * ( 2 / ( sqrt ( 5 ) + 1 ) );
		ok[i] = i - t;
	}
	for ( LL i = 1;i <= sqt;i ++ )
		for ( LL j = i << 1;j <= sqt;j += i )
			ok[j] -= ok[i];
	for ( LL i = 1;i * i <= n;i ++ )
		result += ( n / ( i * i ) ) * ok[i];
	printf ( "%lld", result );
	return 0;
}

T2:电报

题目

给出 n 个点,每个点的出度均为 1,给出这 n 个点初始指向的点A[i] ,和改变这个点指向的目标所需要的价值 C[i]。
求让所有点强连通的最小花费。

输入格式
第一行输入一个数 n 表示点的个数。
之后的 n 行每行两个数 A[i],C[i] 表示第 i 个点指向第 A[i] 个点,更改该点指向的点花费为 C[i]。
输出格式
共一行,为让所有点强连通的最小花费。

样例
样例输入 1
4
2 2
1 4
1 3
3 1
样例输出 1
4
样例解释 1
在这里插入图片描述
很显然,把 1–>2 的这条边改成 (花费 4)的情况下构成强连通分量花费最小。

样例输入 2
4
2 2
1 6
1 3
3 1
样例输出 2
5
样例解释 2
很显然把 1–>2 的这条边改成 1–>4 花费 2,把 3–>1 的这条边改成 3–>2 花费 3 的情况下构成强连通分量花费最小,总花费为 5。

样例输入 3
4
2 2
1 3
4 2
3 3
样例输出 3
4

样例输入 4
3
2 1
3 1
1 1
样例输出 4
0
在这里插入图片描述

题解

在这里插入图片描述
首先为了能变成强连通,树上的点彼此之间需要破掉,先不动环上的点

如图中: 7 , 8 , 9 7,8,9 789 10 , 11 , 12 , 13 10,11,12,13 10111213就必须破掉,破成一条链

要么把 7 → 8 7\rightarrow8 78破掉,要么把 7 → 9 7\rightarrow9 79破掉,然后让 7 − 8 − 9 7-8-9 789连成一条链

可以用拓扑排序找到不是环上的点,然后把这棵树破成链,如果本身是链就不进行操作

图就变成多个环和多棵树的形态

显然,环彼此之间是相互独立的,就可以扫一次先处理出所有的环

我们在上面进行树上破成链的时候,把环延伸的链或树也一起破掉

如图中:就把 6 → 7 6\rightarrow7 67 6 → 10 6\rightarrow10 610都给破掉

接着如果变成强连通,环与环之间必须相互有路去连通

就是复活环与之外连的某一条边

意味着我们要把环破掉一条边和外界相连
在这里插入图片描述
那么这个时候,对于环上的最佳答案点肯定满足把它与它父亲在环上的边破掉,然后把它与自己延伸的链的点进行相连的操作花费最小

破环就是自己的 C C C值,与链上的点保留一条边,就是找链上点的 C C C的最大值

如图中:我们要把 6 → 1 6\rightarrow1 61破掉,复活 6 → 7 6\rightarrow7 67或者 6 → 10 6\rightarrow10 610任意一条边

而且必须复活至少一条,这样才能让环与外面进行连通

但是如果图上有多个点的复活值是负数,那么肯定是全选上,使答案变得更小

在上面破 环与链的边 的时候,我们就记录最大值的 C C C,复活的时候,肯定复活这一条边,其他边的消耗远小于这一条边破掉的消耗


最后我们来解释一下代码的一些地方,旁边的小姐姐问了我很久

  • 为什么是建反图:
    想一想,如果我们建正图,每个点都只会有一个指向点,即每个点的vector里面都只有 1 1 1个点,怎么破链,复活等以上的操作呢?
    换言之,每个点要知道自己的后继,才能知道破哪些边

  • flag||tot>1的问题,我们要知道
    当只有环的时候,如果环是多个,也需要破掉,使所有环彼此强连通
    当只有一个环的时候,如果环上有链也需要破掉,不然环上的点无法走到链上的点
    所以这里是取或,当且仅当原图本身就是一个环才不用考虑复活


在这里插入图片描述

代码实现

这里定义isCircle[i]
1:表示这个点不在环上(PS:可能误导了许多亲故 )
2:表示这个点在环上且已经经过了破树或破链处理
0:表示这个点在环上但等待被处理

#include <queue>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
using namespace std;
#define MAXN 100005
queue < int > q;
vector < int > G[MAXN], circle[MAXN];
int n, tot;
bool flag;
int a[MAXN], c[MAXN];
int d[MAXN], g[MAXN], isCircle[MAXN];
long long result;

int main() {
	scanf ( "%d", &n );
	for ( int i = 1;i <= n;i ++ ) {
		scanf ( "%d %d", &a[i], &c[i] );
		G[a[i]].push_back ( i );
		d[a[i]] ++;
	}
	for ( int i = 1;i <= n;i ++ )
		if ( ! d[i] ) {
			q.push ( i );
			isCircle[i] = 1;
			flag = 1;
		}
	while ( ! q.empty() ) {
		int t = q.front();
		q.pop();
		d[a[t]] --;
		if ( ! d[a[t]] ) {
			q.push ( a[t] );
			isCircle[a[t]] = 1;
		}
	}
	for ( int i = 1;i <= n;i ++ ) {
		if ( isCircle[i] == 1 ) {//破树为链
			int Max = 0;
			for ( int j = 0;j < G[i].size();j ++ ) {
				Max = max ( Max, c[G[i][j]] );
				result += c[G[i][j]];
			}
			result -= Max;
		}
		else {
			for ( int j = 0;j < G[i].size();j ++ )
				if ( isCircle[G[i][j]] == 1 ) {//破环与外面延伸的链
					g[i] = max ( g[i], c[G[i][j]] );//记录一条链的最大值
					result += c[G[i][j]];
				}
			if ( isCircle[i] == 0 )
				tot ++;
			int x = i;
			while ( isCircle[x] == 0 ) {//分离出环
				isCircle[x] = 2;
				circle[tot].push_back ( x );
				x = a[x];
			}
		}
	}
	if ( flag || tot > 1 ) {
		for ( int i = 1;i <= tot;i ++ ) {
			int Min = 0x3f3f3f3f;
			for ( int j = 0;j < circle[i].size();j ++ )//复活
				Min = min ( Min, c[circle[i][j]] - g[a[circle[i][j]]] );
			if ( Min >= 0 )//必须复活的一条边
				result += Min;
			else {
				for ( int j = 0;j < circle[i].size();j ++ )
					if ( c[circle[i][j]] - g[a[circle[i][j]]] < 0 )//可多复活几条边
						result += c[circle[i][j]] - g[a[circle[i][j]]];
						//破掉i与fi的环上边的时候,接的那一条边肯定是接在fi上
			}
		}
	}
	printf ( "%lld", result );
	return 0;
}

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值