黑龙江省赛 The 16th Heilongjiang Provincial Collegiate Programming Contest(2022 CCPC练习赛)

目录

D. Doin' Time

H Hack DSU

F. Function

J. JOJO's Factory

K. Keep Eating


D. Doin' Time

D. Doin' Time

思路:区间DP板子 + 求逆元,

注意:预处理逆元,否则TLE

忘记区间DP的点这里 -> 区间DP模板题(石子合并)

与石子合并不同的地方:

        1,预处理不同,石子合并为区间和,此题为区间乘法

        2,状态计算不同,石子合并为s[r] - s[l] ,此题目为,s[r] / s[l] 

关于为什么取逆元的问题:

        目的是:保持取模后的除法的结果与没取模的结果一样,

        具体应用场景:数据很大,需要取模,同时取模过的数要做除法

如果不取逆元的错误举例

8/4%5 = 2,(8%5)/(4%5) = 0

8/4%5 == 8/qmi(4)%5 == 2

代码如下

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 310, mod = 1000003;

int n;
LL s[N], ns[N], f[N][N];

LL qmi(int a, int k)
{
    LL res = 1;
    while(k)
    {
        if(k & 1) res = (LL)res*a%mod;
        k >>= 1;
        a = (LL)a*a%mod;
    }
    return res;
}

int main()
{
    scanf("%d", &n);

    s[0] = 1, ns[0] = 1;
    for(int i = 1; i <= n; i ++ )
    {
        scanf("%lld", &s[i]);
        s[i] = s[i] * s[i-1] %  mod;
        ns[i] = qmi(s[i], mod - 2)%mod;
    }

    for(int len = 2; len <= n; len ++ )
    {
        for(int i = 1; i + len - 1 <= n; i ++ )
        {
            int l = i, r = i + len - 1;
            for(int k = l; k < r; k ++ )
                f[l][r] = max(f[l][r], f[l][k] + f[k+1][r] + (s[r]*ns[k]%mod - s[k]*ns[l-1]%mod) * (s[r]*ns[k]%mod - s[k]*ns[l-1]%mod));
        }
    }

    printf("%lld\n", f[1][n]);

    return 0;
}

H Hack DSU

H - Hack DSU!

思路:

题目中的代码

#include <iostream>
using namespace std;
const int MAXN = 1e5+10;
int parent[MAXN];
long long counter = 0;

int find(int x) {
    while (x != parent[x]) {
        if (x < parent[x]) {
            // 路径压缩
            parent[x] = parent[parent[x]];
        }
        x = parent[x];
        counter++;
    }

    return x;
}

void merge(int a, int b) {
    a = find(a);
    b = find(b);
    parent[a] = b;
}

int main() {
    int n, A, B, ans = 0;
    cin >> n;

    for (int i = 1; i <= n; i++)
        parent[i] = i;

    for (int i = 1; i <= n; i++) {
        cin >> A >> B;
        merge(A, B);
    }

    for (int i = 1; i <= n; i++)
        if (i == find(i))
            ans++;

    cout << ans << endl;
    return 0;
}

精髓在于 find 函数,与构造子链 

将 find 函数的代码拿出

while (x != parent[x]) {
    if (x < parent[x]) {
        // 路径压缩
        parent[x] = parent[parent[x]];
    }
    x = parent[x];
    counter++;
}

题目的要求是使 counter 尽可能的大,所以我们要尽可能的不进入 if 语句(尽可能的不路径压缩),即,让父节点尽可能地小于子节点

我们可以构造类似的链状结构:

10 -> 9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1

此代码要想 find(7) 需要 counter + 6,因为他的每一个父节点都小于子节点,如此,每次都在 if 外面,使 counter ++,直到找到 1 为止

可以先构造一条链,再查找

题目样例代码解读:

1 2
2 3
3 4
4 5
5 6
6 7
7 8
8 9
9 10
10 1

此代码进入了 if 带路径压缩,该样例的图比较乱(类似1->3->5),此处不解读了,(画不好图,,,)

附上调试代码:根据调试的代码结果判断就行,咱重点不在这个(他这个样例写的太烂了,还进入 if ……,麻烦,,,)

#include <iostream>
using namespace std;
const int MAXN = 1e5+10;
int parent[MAXN];
long long counter = 0;

int find(int x) {
	int res = 0;
    while (x != parent[x]) {
        if (x < parent[x]) {
        	cout << "x1:" << x << " " << parent[x] <<  endl;
            // Merge-by-rank and Path-compression
            parent[x] = parent[parent[x]];
        }
        cout << "x2:" << x  << " " << parent[x]<< endl;
        x = parent[x];
        counter++, res++;
    }
    cout << res << endl;

    return x;
}

void merge(int a, int b) {
    a = find(a);
    b = find(b);
    //cout << "a:" << a << endl;
    //cout << "b:" << b << endl;
    parent[a] = b;
}

int main() {
    int n, A, B, ans = 0;
    cin >> n;

    for (int i = 1; i <= n; i++)
        parent[i] = i;

    for (int i = 1; i <= n; i++) {
        cin >> A >> B;
        merge(A, B);
    }

	cout << " ------------------ " << endl;

    for (int i = 1; i <= n; i++)
        if (i == find(i))
            ans++;

    cout << counter << endl;
}

自测代码解读:

不进入 if ,即,使 counter 最大化(最优解)

输入题目代码(以 n = 10 例子)

朴素版输入代码:

10 9
9 8
8 7
7 6
6 5
5 4
4 3
3 2
2 1
1 10

解读:

// 构造链
// 将 a -> b
// b -> c
10 9 // 10 -> 9
9 8 // 10 -> 9 -> 8
8 7 // 10 -> 9 -> 8 -> 7
7 6 // 10 -> 9 -> 8 -> 7 -> 6
6 5
5 4
4 3
3 2
2 1

// 截至至此,构造链的结果
// 10 -> 9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1

// 此插入无效,当成查找
1 10

在构造链时,counter  = 0,每一个点都是父节点

在最后一步查找的时候 在,find(1) 时,counter = 0;find(10) 时,counter + 9

最后在 for 循环计算 ans 时,find(1) :counter + 0, find(2) = counter +  1, find(3) = counter +  2,…… find(10) = counter +  9

最终 counter 为 (0 + 1 + 2 + 3 ……+ 9 ) + 9 = 54

样例输入代码进化版

// 构造链
// 将 a -> b
10 9 
9 8 
8 7 
7 6 
6 5
5 4
4 3
3 2
2 1

// 构造结果
// 10 -> 9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1

// 查找
10 10

 将查找换为 10 10,主函数 for 循环得到的 counter 不变,只在最后一步插入时 将 find(1) 改为 find(10) 总结果 应该比上一次结果多 9 ,(find(1) :counter +  0,find(10) :counter +  0)counter = 54 + 9 = 63

超进化版

// 构造链
// 将 a -> b
10 9 // counter + 0
10 8 // counter + 1
10 7 // counter + 2
10 6 // counter + 3
10 5 // counter + 4
10 4 // counter + 5
10 3 // counter + 6
10 2 // counter + 7
10 1 // counter + 8

// 构造结果
// 10 -> 9 -> 8 -> 7 -> 6 -> 5 -> 4 -> 3 -> 2 -> 1

// 查找
10 10

边插入,边计算 counter 

插入时 counter + 0 + 1 + 2 + …… + 8 即,counter + 36

其余和进化版一致

如此的到的 counter 比 普通版样例 多了 9 + 36 

比 进化版多了 36 个 为恐怖的 99,思路完成

分析可行性:

,counter 一定越大越好,

题目: hack 到 f3a75392cd224c978b283f8d13de6976.png,即 n^2 / log2(n)

朴素版的counter数量

res = (1+……n-1)+ n-1

res = n*n/2 + (n-1)-n/2

类似上述分析也成立

如此程度,我们 hack 到 res 连朴素版的都能过,超进化版还怕什么???,但学算法不能只停驻与AC,应该精益求精,接下来看进化版本的counter

进化版本的呢?

res = (n-1)*2 + (1+……+n-1)

res = (n-1)*2 + n*(n-1)/2

res = n*n/2 + (n-1)*2 - n/2

可知当 n > 10 时,log2(10) = 3.32

 (n-1)*2  - n/2 > 0

n*n/2 > n*n/log2(10)

所以,成立

超进化版本的 res 可求为 

res = ( 1+ ……+n-2) + (n-1) + (n-1) + (1+……n-1)

res = (1+……n-1) * 2 + (n-1)

res = (1+n-1)*(n-1) + (n-1)

res = n *(n - 1) + (n - 1) 

res = n*(n-1) + n - 1

res = n*n - 1 

因为 n > 10,log2(n) >= 3

res = n*n-1 >> n*n/log2(n),(远大于)

所以超进化版本的一定成立

如此程度,我们 hack 到 res 连朴素版的都能过,超进化版还怕什么???

直接最优解(超进化版本)的代码:

超进化版本代码奉上:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 310, mod = 1000003;

int n, m;

int main()
{
    scanf("%d %d", &n, &m);

    //printf("%d %d\n", n, 1);
    for(int i=n-1;i>=1;i--)
		cout<<n<<" "<<i<<"\n";
	cout<<n<<" "<<n;

    return 0;
}

确实都能AC,🤣

ce7940b020694136a6a6272700c9b08f.png

F. Function

F. Function

方程拿过来,方便一点

解释方程:

        情况(1):n 为质数,f(n) = 1

        情况(2):合数 n 可分解为唯一质数 p ,且质数的数量大于 1 ,f(n) = p^(k+1)

        情况(3):合数 n 可分解为很多质数的积(算术基本定理),分解完全后的其中最小的质数 p 的 e1 次方,拿去做情况(1)或 情况(2);

思路:

考虑到最小的质数,我么应该想到线性筛

void get_primes(int n)
{
	for(int i = 2; i <= n; i ++ )
	{
		if(!st[i]) primes[cnt++] = i;
		for(int j = 0; primes[j] <= n/i; j ++ )
		{
			st[primes[j] * i] = true;
			if(i % primes[j] == 0) break; 
		 } 
	}
}

线性筛的筛掉合数有两种情况,

        (1). 当  i % primes[j] == 0 时,primes[j] 为 i 的最小质因子,同时也是要筛的数 i*primes[j] 的最小质因子,此时将 i * primes[j] 中的 primes[j] 分解完全, 乘以分解后的 i * primes[j] 得到 f[ i * primes[j] ] 

        (2),当 i % primes[j] != 0 时,primes[j] 不是 i 的质因子,primes[j] 为 i * primes[j] 的最小质因子,此时,因为 i 中没有 primes[j] 所以,primes[j] 的 k 为  1,f[ primes[j] ^ k] = 1,还剩下 i ,res *= i;

注:res 初始化为 1,f[1] = 1,且 1 没法筛出去,,,

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 1e7+10;

int n;
int cnt;
int primes[N];
bool st[N];
LL res = 1;

LL qmi(int a, int k)
{
    LL ans = 1;
    while(k)
    {
        if(k & 1) ans = (LL)ans*a;
        k >>= 1;
        a = (LL)a*a;
    }
    return ans;
}

void get_primes()
{
    for(int i = 2; i <= n; i ++ )
    {
        if(!st[i])
        {
            primes[cnt ++] = i;
            res ++;
        }
        for(int j = 0; primes[j] <= n/i; j ++ )
        {
            st[i * primes[j]] = true;

            // primes[j] 是 i 的最小质因子,也是 i * priems[j] 的最小质因子
            if(i % primes[j] == 0)
            {
                int s = 1;
                int t = i;
                while(t % primes[j] == 0) t /= primes[j], s ++;
                res += (LL)qmi(primes[j], s/2) * t;
                break;
            }
            // primes[j] 不是 i 的最小质因子,但primes[j] 是 i * primes[j] 的最小质因子,
            // 所以推出 primes[j] 为 i*primes[j] 的最小的唯一质因子,f(primes[j]的个数 k==1 ) = 1;
            res += i; // 等价于 res += f(k)*i;
        }
    }
}

int main()
{
    scanf("%d", &n);

    get_primes();

    cout << res << endl;

    return 0;
}

J. JOJO's Factory

J. JOJO's Factory

思路

        (1)由题目数据范围可知,M < 2*N - 3,远小于 所有的边数 N*N,所以不可能所有的A机器同时存在只能连一个B机器的情况(需要N*(N-1)条边)。

        (2) 一个机器完全报废为需要N,两个相同种类的机器报废为2*N,最多只能出现一个A机器报废(都不能连)或 一个B机器报废,或同时报废A,B一个的情况,。

        (3) 因为不存在只能连一个的情况,没报废的机器A可以与没报废的机器B任意连,可以连成一对的数量为 min(A,B)(没报废的最小值)

代码实现:

朴素版:map此机器不能连的的个数,若等于 n 则报废,对应的种类的机器数量 - 1,最后求min即可

代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 310, mod = 1000003;

int n, m;
map<int, int> mpa;
map<int, int> mpb;

int main()
{
    scanf("%d %d", &n, &m);

    int a, b;
    int t = 0;
    for(int i = 1; i <= m; i ++ )
    {
        scanf("%d %d", &a, &b);
        mpa[a] ++;
        mpb[b] ++;
        if(mpa[a] >= n) t = 1;
        if(mpb[b] >= n) t = 1;
    }

    printf("%d\n", n-t);

    return 0;
}

 貌似时间有点紧,,,差4毫秒,,

用unordered_map试一下,1450ms,,显著提高了500ms

更优化版:

 思路:我们知道A,B 中最多只能有一个报废,所以,标记报废数 t ,若存在报废的了(t == 1),其他的就不用存入 unordered_map,

又优化了点

优化代码如下:(unordered_map)实现 

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 310, mod = 1000003;

int n, m;
unordered_map<int, int> mpa;
unordered_map<int, int> mpb;

int main()
{
    scanf("%d %d", &n, &m);

    int a, b;
    int t = 0;
    for(int i = 1; i <= m; i ++ )
    {
        scanf("%d %d", &a, &b);

        if(t == 1) continue;

        mpa[a] ++;
        mpb[b] ++;
        if(mpa[a] >= n) t = 1;
        if(mpb[b] >= n) t = 1;
    }

    printf("%d\n", n-t);

    return 0;
}

 但虽然unordered_map插入删除的操作时间复杂度接近O(1),但也是不稳定的,冲突过多会变成O(log(n)),

再次观察数据范围,N < 1e5,这个长度完全可以用数组实现,插入删除绝对的O(1),

再更优化代码:(数组实现)

 时间空间复杂度都有显著降低,至此优化完成,

最终思路为:标记唯一报废的 机器,存在则 t == 1,之后continue,输出 n - 1;否则正常存入,输出 n

最终代码: 

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 1e6+10, mod = 1000003;

int n, m;
int mpa[N];
int mpb[N];

int main()
{
    scanf("%d %d", &n, &m);

    int a, b;
    int t = 0;
    for(int i = 1; i <= m; i ++ )
    {
        scanf("%d %d", &a, &b);

        if(t == 1) continue;

        mpa[a] ++;
        mpb[b] ++;
        if(mpa[a] >= n) t = 1;
        if(mpb[b] >= n) t = 1;
    }

    printf("%d\n", n-t);

    return 0;
}

K. Keep Eating

K. Keep Eating

思路

        全部合并,一点一点吃,每次吃 1 块,重量到 k 时,只吃的小一部分,剩余 k/2上取整数

(签到题)代码如下:

#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 2e5+10;

int n, m;
LL sum = 0;

int main()
{
    scanf("%d %d", &n, &m);

    int x;
    for(int i = 1; i <= n; i ++ )
        scanf("%d", &x), sum += x;

    if(sum >= m) printf("%lld\n", sum - (m+1)/2);
    else puts("0");

    return 0;
}

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个用Python实现Kmeans聚类算法对31个省份的消费水平进行聚类的示例代码: ```python import pandas as pd import numpy as np from sklearn.cluster import KMeans import matplotlib.pyplot as plt # 读取数据文件 data = pd.read_csv('consumption.csv') # 获取特征数据,即各省份的人均可支配收入、居民消费水平、城镇居民人均可支配收入和城镇居民消费水平 X = data.iloc[:, 1:5].values # 创建KMeans模型 kmeans = KMeans(n_clusters=4, init='k-means++', random_state=0) # 对数据进行聚类 y_kmeans = kmeans.fit_predict(X) # 输出聚类结果 print(y_kmeans) # 可视化聚类结果,横轴为居民消费水平,纵轴为城镇居民消费水平 plt.scatter(X[y_kmeans == 0, 1], X[y_kmeans == 0, 3], s = 100, c = 'red', label = 'Cluster 1') plt.scatter(X[y_kmeans == 1, 1], X[y_kmeans == 1, 3], s = 100, c = 'blue', label = 'Cluster 2') plt.scatter(X[y_kmeans == 2, 1], X[y_kmeans == 2, 3], s = 100, c = 'green', label = 'Cluster 3') plt.scatter(X[y_kmeans == 3, 1], X[y_kmeans == 3, 3], s = 100, c = 'cyan', label = 'Cluster 4') plt.scatter(kmeans.cluster_centers_[:, 1], kmeans.cluster_centers_[:, 3], s = 300, c = 'yellow', label = 'Centroids') plt.title('Clusters of Provinces') plt.xlabel('Per Capita Consumption Level') plt.ylabel('Urban Per Capita Consumption Level') plt.legend() plt.show() ``` 其中,数据文件`consumption.csv`的内容如下: ``` Province,Per Capita Disposable Income,Per Capita Consumption Level,Urban Per Capita Disposable Income,Urban Per Capita Consumption Level Beijing,55129,35383,79406,51424 Tianjin,35577,24472,45765,31297 Hebei,19022,12068,25825,16210 Shanxi,17515,11761,23119,14856 Inner Mongolia,18427,11576,27712,17447 Liaoning,21176,12654,30568,17867 Jilin,17904,11037,26508,15644 Heilongjiang,17349,10432,23519,13883 Shanghai,63186,38959,102921,73633 Jiangsu,36183,22282,54869,35764 Zhejiang,37250,23223,53932,38480 Anhui,16889,10237,22125,12589 Fujian,24147,15242,38829,23651 Jiangxi,15087,9463,19292,11433 Shandong,22002,14184,30855,18897 Henan,15975,10019,23571,13728 Hubei,19077,12018,25932,14948 Hunan,16878,10555,22672,13488 Guangdong,32346,21300,52723,33695 Guangxi,14342,9032,19569,12339 Hainan,17909,10744,25635,16184 Chongqing,21439,13707,30000,18954 Sichuan,16715,10523,23226,14083 Guizhou,11757,7329,14938,9045 Yunnan,13698,8591,19617,12093 Tibet,9842,6093,14200,9230 Shaanxi,18052,11414,24789,15445 Gansu,13314,8227,18948,11723 Qinghai,15521,9827,23134,15045 Ningxia,20138,12937,31474,22327 Xinjiang,14266,8888,22431,13954 ``` 运行以上代码后,将得到如下的聚类结果图: ![kmeans_clusters](https://img-blog.csdnimg.cn/20210629231807239.png) 可以看出,聚类结果将31个省份分为了4个聚类簇,其中簇1表示消费水平较高的地区,簇2表示消费水平较低的地区,簇3表示城镇居民消费水平较高的地区,簇4表示城镇居民消费水平较低的地区。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AC自动寄

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值