2019CCPC-江西省赛
A - Cotree
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6567
题目大意:
给了两棵树,插入一条边,构成一个树,要求所有点的连线距离之和最小。
解题思路:
首先明确什么是重心。一棵树上的重心到这棵树上其他点的距离之和最短,如果让一颗树上加一个点使这个点到其他点的距离最短,那么这个点应该加在这个重心上,所以可以将一个树看作一个点,将这棵树加在重心上的得到的所有点间的距离之和最小。求距离之和也就是每条边的贡献之和,一条边的贡献和也就是它左右节点的乘积乘以权值。求贡献合时可以用求重心的思路,记录每条边的父亲端的所有节点个数之和,在它本身和儿子节点个数之和。最后计算相加即可。
代码:
1 #include <bits/stdc++.h>
2 using namespace std;
3 typedef long long ll;
4 const ll N = 2e5;
5 vector<ll>arr[N];
6 bool vis[N];
7 ll du[N];
8 ll balan[N];
9 ll x1 , n , x2 , s2 , s1 = 0 , ans = 0;
10 ll balance1 = N,balance2 = N;
11
12 void dfs1(ll u,ll fa){//求第一棵树的重心
13 ll v,sum = 0;
14 vis[u] = 1 , s1 ++ ;//标记第一棵树的所有节点并记录个数
15 du[u] = 1;
16 for(ll i = 0;i < arr[u].size();i ++ ){
17 v = arr[u][i];
18 if(v == fa) continue;
19 dfs1(v,u);
20 du[u] += du[v];
21 balan[u] = max(balan[u],du[v]);
22 }
23 }
24
25 void dfs2(ll u,ll fa){//求第二棵树的重心
26 ll v,sum = 0;
27 du[u] = 1;
28 for(ll i = 0;i < arr[u].size();i ++ ){
29 v = arr[u][i];
30 if(v == fa) continue;
31 dfs2(v,u);
32 du[u] += du[v];
33 sum = max(sum,du[v]);
34 }
35 sum = max(sum , s2 - du[u]);
36 if(balance2 > sum){
37 balance2 = sum;
38 x2 = u;
39 }
40 }
41
42 void dfs3(ll u , ll fa){//求所有点的距离之和
43 ll v , sum1 = 1 , sum2;
44 du[u] = 1;
45 for(ll i = 0;i < arr[u].size();i ++ ){
46 v = arr[u][i];
47 if(v == fa) continue;
48 dfs3(v , u);
49 du[u] += du[v];
50 sum1 = sum1 + du[v];
51 }
52 sum2 = n - du[u];
53 ans += (sum1 * sum2);
54 }
55 int main(){
56 ll u,v;
57 cin >> n;
58 for(ll i = 0; i < n-2;i ++){
59 cin >> u >> v;
60 arr[u].push_back(v);
61 arr[v].push_back(u);
62 }
63 dfs1(1,0);
64 for(ll i = 1;i <= n;i ++ ){
65 if(vis[i]){
66 balan[i] = max(balan[i] , s1-du[i]);
67 if(balance1 > balan[i]){
68 balance1 = balan[i];
69 x1 = i;
70 }
71 }
72 else{
73 u = i;
74 }
75 }
76 s2 = n - s1;//第二棵树的节点个数
77 dfs2(u , 0);
78 arr[x1].push_back(x2);
79 arr[x2].push_back(x1);
80 memset(du , 0 , sizeof(du));
81 dfs3(1 , 0);
82 cout << ans << endl;
83 return 0;
84 }
D - Wave
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6570
题目大意:
wave的定义是一个序列最少两个数字,序列中奇数位数字相同,偶数位的数字也相同,但奇数位与偶数位不同。给定一个数组,数组中元素在1到c之间,找到最长wave子序列。子序列的定义是不改变原序列的顺序,删除部分序列中元素的得到的序列。也就是说子序列中的元素在原序列中不一定是连在一起的。
解题思路:
在这个序列中a,b组成的子序列的是a b a b a b a b,在原序列中可以递推出最长的子序列长度,dp[a][b]存储a与b组成的子序列的长度,在求dp[b][a]的时候,前n个序列中的dp[b][a]时,它是等于前n-1个序列中dp[a][b]+1,然后在读到第n个序列时更新一下dp数组,并记录最大值,最后输出最大值。
代码:
1 #include <bits/stdc++.h>
2 using namespace std;
3 const int N=1e5+10;
4 int arr[N];
5 int dp[200][200];
6
7 int solve(int n , int c ){
8 int MAX = 0;
9 for(int i = 0 ; i < n ; i ++ ){
10 for(int j = 1 ; j <= c ; j ++ ){
11 dp[arr[i]][j] = dp[j][arr[i]] + 1;
12 if(arr[i] == j )
13 continue;
14 MAX = max ( dp[arr[i]][j] , MAX );
15 }
16 }
17 return MAX ;
18 }
19
20 int main(){
21 int n,c;
22 cin >> n >> c;
23 for(int i = 0; i < n; i ++ ){
24 scanf( "%d" , arr+i );
25 }
26 cout << solve(n , c) << endl;
27 return 0;
28 }
F - String
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6572
题目大意:
一个长度为n的字符串中选取四个字符,求构成"avin"的概率,输出最简分数。
解题思路:
第一个字符是'a'的概率是'a'出现的次数比上n,所以"avin"出现的概率是'a','v','i','n'出现的个数乘积比上pow(n,4)。化简用最大公约数即可。
代码:
1 #include <bits/stdc++.h>
2 using namespace std;
3 typedef long long ll;
4 int main(){
5 int n,sum1,sum2,sum;
6 string s;
7 while(cin >> n >> s){
8 map<char,int>mp;
9 for(int i = 0;i < n;i ++ ){
10 mp[s[i]] ++ ;
11 }
12 sum1=mp['a'] * mp['v'] * mp['i'] * mp['n'];
13 sum2=n * n * n * n;
14 if(sum1 == 0){
15 puts("0/1");
16 }
17 else {
18 int gcd = __gcd(sum1,sum2);
19 sum1 = sum1 / gcd;
20 sum2 = sum2 / gcd;
21 cout << sum1 << "/" << sum2 << endl;
22 }
23 }
24 return 0;
25 }
G - Traffic
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6573
题目大意:
在十字路口有从南到北的汽车和从东到西的汽车,让所有从南到北的车等待n分钟,使得车可以顺利通过,求n的最小值。
解题思路:
数据太水,暴力即可。若增大数据就要用二分。
H - Rng
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6574
题目大意:
一个长度为n的数组,先随机找个右端点,再在1,r之间在找个左端点,用相同的方法再找到一组l,r。求两组范围交叉的概率。
解题思路:
求交叉过于复杂,所以要求不交叉的概率。
如果r1 > r2,那么不交叉的概率为:(r1 - r2)/ r1
所有的取法的个数是n*n,不交叉的情况是,右边区间的左端点大于左边区间的右端点。从r2 = 2开始遍历,得到等差数列前n-1项和 ( n * ( n - 1 ) ) / 2。所求概率为1 - ( ( n * ( n - 1 ) ) / 2 / ( n * n ) ),也就是 ( n + 1 )/ ( 2 * n ) % mod,用费马小定理计算逆元。
代码如下:
1 #include <bits/stdc++.h>
2 using namespace std;
3 typedef long long ll;
4 const ll mod=1e9+7;
5 ll ksm(ll a,ll b){
6 ll sum = 1;
7 while( b ){
8 if( b & 1 ) sum = sum * a % mod;
9 a = a * a % mod;
10 b >>= 1;
11 }
12 return sum;
13 }
14 ll inv( ll n ){
15 return ksm( n , mod - 2);
16 }
17 ll solve(ll n){
18 return (n + 1) * inv( 2 * n ) % mod;
19 }
20 int main(){
21 int n;
22 while( cin >> n ){
23 cout << solve( n ) << endl;
24 }
25 return 0;
26 }
I - Budget
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6575
题目大意:
给一组保留小数点后三位的小数,四舍五入保留到第二位,计算与原来的差值。
解题思路:
数据范围较大,用字符串存取,直接根据第三位小数计算结果即可。
J - Worker
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=6576
题目大意:
有n个不同的工厂,m个工人,不同工厂中工人完成的订单量是不同的,给出你不同的工厂中每个人一天可以完成的订单量,计算是否可以有一种安排使得所有工厂完成的订单量相同。
解题思路:
求出每个工厂单个工人完成的订单量的最大公倍数,然后用这个公倍数对m取模。如果等于0代表可以分配成功,否则失败。
代码:
1 #include <bits/stdc++.h>
2 using namespace std;
3 typedef long long ll;
4 ll a[1000000];
5 int main(){
6 ll n,m;
7 while(cin >> n >> m){
8 ll ans = 1,sum = 0;
9 for(ll i = 0;i < n;i ++){
10 scanf("%lld",a + i);
11 ans=(ans * a[i] / (__gcd(ans,a[i])));
12 }
13 for(ll i = 0;i < n;i ++ ){
14 sum = sum + ans / a[i];
15 }
16 if(m % sum == 0){
17 puts("Yes");
18 ll flag = m / sum;
19 for(ll i = 0;i < n;i ++ ){
20 if(i != 0){
21 cout << " ";
22 }
23 cout << (ans / a[i] * flag);
24 }
25 cout << endl;
26 }
27 else{
28 puts("No");
29 }
30 }
31 return 0;
32 }
K - Class
水。。。