E.
链接:https://ac.nowcoder.com/acm/contest/45670/E
来源:牛客网
妈妈发现小竹逃走了,非常的气愤,她决定出去寻找小竹!和小竹不同的是,妈妈是道路上行走。
妈妈和小竹所在的城市有 n 个路口,n−1 条道路,并且保证任意两个路口互相联通。每个路口根据妈妈审美的不同都有一个优雅值,而如果两个相邻的路口的优雅值存在至少两个共同的质因子p和q(p
q)则这两个相邻的路口就是共同优雅的。
妈妈将共同优雅联通块定义为:在城市中选取若干个路口,若这些路口们两两互相联通,且每两个相邻的路口都是共同优雅的,则该联通块称为共同优雅联通块。
注意:单独的一个路口也符合共同优雅联通块的定义。
输入描述:
第一行一个正整数n(1≤n≤1e6) 表示有 n 个路口。 第二行 n 个整数,第 i 个整数为 ai(1≤ai≤5×1e6) 表示该路口的优雅值。 接下来 n-1 行,每行两个整数 x,y(1≤x,y≤n) 表示 x 路口到 y 路口有一条道路。输出描述:
一行一个整数,表示最大优雅联通块包含多少个路口。示例1
输入
4 12 12 4 18 1 2 1 3 2 4输出
3说明
最大优雅联通块为 1,2,4 三个路口所在的联通块,两两相邻的路口均包含 2,3 两个质因子
这题可以用并查集。首先题目给出n个点,只有n-1条边并且保证点之间两两连通,所以可以得出题目给定的图是一棵树。
对于每给出的两个相邻点,如果两个点至少拥有2个共同质因数p,q(p!=q),那么就合并这两个点所在的集合。最后输出时输出节点数最多的集合的节点数即可。
这题唯一的难点在于如何求出两个数的共同质因数个数是否大于等于2。
首先我们可以求出两个数a,b之间的最大公因数,如果最大公因数是质数p,则两个数之间的共同质因数个数只有1。(a/p和b/p必然是互质的)
如果最大公因数是合数,那么分两种情况:
1.最大公因数是某个质数的幂的情况,这时共同质因数只存在1个。(即最大公因数只能分解出相同的质数)
2.最大公因数不是任何质数的幂的情况,这时共同质因数个数至少为2个。(即必然能分解出至少两个不同的质数)
代码如下:
#include<iostream>
#include<cmath>
#include<vector>
using namespace std;
const int N = 5e6+5;
int val[N], rt[N];
int fg[N], cnt[N], p[N], k = 0; //fg用来标记是否是质数,cnt[i]表示以i为根的集合的元素个数,p用来存质数
void Getprime() //埃筛求质数
{
for(int i=2; i<=N; i++)
{
if(!fg[i])
{
p[k ++] = i;
for(int j=i*2; j<=N; j+=i) fg[j] = 1;
}
}
}
int gcd(int x, int y) //求最大公因数
{
if(x<y) swap(x, y);
while(x%y)
{
int t = x % y;
x = y;
y = t;
}
return y;
}
bool Check(int x, int y) //检查两个数是否存在至少两个相同的质因数
{
int pp = gcd(x, y);
if(!fg[pp] || pp==1) return false; //如果gcd是质数,或者gcd为1,则不可能有两个及以上的相同质因数
else //如果gcd是合数的情况
{
int t = 1;
for(int i=0; p[i]<=sqrt(pp); i++) //判断是否为该合数是否为质数的幂的情况,这里循环的上界得sqrt一下,否则会超时
{
t = 1;
while(t<pp) t *= p[i];
if(t==pp) return false; //如果是质数的幂,则不满足条件
}
}
return true;
}
int Find(int x) //并查集查找
{
return x==rt[x]? x:rt[x] = Find(rt[x]);
}
void add(int x, int y) //并查集合并
{
int rx = Find(x), ry = Find(y);
if(rx!=ry)
{
rt[rx] = ry;
cnt[ry] += cnt[rx]; //合并后的集合的元素个数等于两个旧集合元素个数相加
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int n;
cin >> n;
Getprime();
for(int i=1; i<=n; i++)
cin >> val[i], rt[i] = i, cnt[i]=1;//初始化,最初时每个集合元素个数都是1,即只有它本身。
for(int i=0; i<n-1; i++)
{
int x, y;
cin >> x >> y;
if(Check(val[x], val[y])) add(x, y); //对每两个相邻点的值进行判断,满足条件相合并
}
int maxv =0;
for(int i=1; i<=n; i++)
{
maxv = max(maxv, cnt[Find(i)]); //找元素最多的那个集合
}
cout << maxv << endl;
return 0;
}
F.
链接:https://ac.nowcoder.com/acm/contest/45670/F
来源:牛客网
小竹正在和小胖激烈的玩着游戏,突然砰砰砰的敲门声响起。小竹走到猫眼面前一看,糟了!是妈妈。可是小竹还没说话小胖已经开了门。
妈妈看着偷跑的小竹十分气愤,于是她请来了小竹最好的朋友工口发动机出一道题折磨小竹,题目如下:
定义关于一个排列 p 的函数
。
其中[x]的括号为艾弗森括号,若x表示的条件成立,则[x]=1,否则[x]=0。
定义函数 inv(p) 为排列 p 的逆序对数量。
定义一个排列的价值:
现在给出一个 n,求出所有长度为 n 的排列的价值之和。
即求出:
H(p)其中Sp 表示所有长度为n的排列所组成的集合。考虑到答案可能很大,请将答案对 1e9+7 取模。
输入描述:
第一行一个正整数 t(1≤t≤10) 。 接下来 t 行,每行一个正整数 n(1≤n≤1e5)。输出描述:
t行,每行一个整数,为所求式子的值。示例1
输入
2 2 3输出
5 135
首先先看公式 , 这个公式可以理解成把区间[l, r]内所有小于x位置上的数的数变成0,大于等于x位置上的数的数变成1。 所以公式中
部分,我们可以理解成对于每个子区间[i, j]中,求有多少个数大于等于x位置上的数。所以公式:
, 就可以看成
。
如排列:1,3,4,2,5。大于等于1位置上的数有5个,大于等于2位置上的数有3个,大于等于3位置上
的数有2个,大于等于4位置上的数有4个,大于等于5位置上的数有1个。所以我们可以得出对于每个长度为len的区间[l, r]中,大于等于x(x=l,l+1,..,r)位置上的数有: 个,而对于一个
长度为n的排列中长度为len的区间个数为 n-len+1个,所以 :
。
那么公式:
所以:
。
然后又可以发现公式 的值和p无关,所以该公式又可以变换成:
。
由题可知函数inv(p)的意思是求排列p中逆序对的个数,的意思则是求所有长度为n的排列中逆序对的个数和。对于一个排序p而言它的逆序对个数期望为
,这是因为我们可以取排列中任意两个数,其情况数是
,对于每两个数,其中大的那个数在小的数前和在小的数后的概率是一样的(即正序的情况和逆序的情况数是一样的),所以要除以2。
而长度为n的全排列的个数为 n!,所以全排列逆序对个数为:。
所以最终公式为:。
代码如下:
#include<iostream>
using namespace std;
typedef long long LL;
const int N = 1e5+5, MOD = 1e9+7;
LL p[N];
LL Qpower(LL a, LL b) //快速幂,用于求逆元
{
LL res = 1;
while(b)
{
if(b&1) res = (res % MOD * a % MOD) % MOD;
b >>= 1;
a = (a % MOD * a % MOD) % MOD;
}
return res % MOD;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int t;
cin >> t;
p[0] = p[1] = 1;
for(int i=2; i<=N; i++)
p[i] = (p[i-1] % MOD * i % MOD) % MOD; //预处理出所有可能数n的阶乘
while(t --)
{
LL n;
cin >> n;
LL sum = 0, inv;
for(LL i=1; i<=n; i++)
sum = (sum % MOD + (n-i+1)%MOD * ((i+1)*i/2) % MOD) % MOD; //枚举每个len,求出 ∑(n-len+1)*(len+1)*len/2的值
inv = ((p[n] % MOD * Qpower((4*p[n-2])%MOD, MOD-2)) % MOD * p[n] % MOD) % MOD; //求全排列的逆序对个数
cout << (sum%MOD * inv%MOD) % MOD << endl;
}
return 0;
}