日常训练2025-1-13

日常训练2025-1-13

P5020 [NOIP2018 提高组] 货币系统

rating:普及+/提高

https://www.luogu.com.cn/problem/P5020

思路(01背包)

思考一下题目要干什么,原来的货币系统能够表示出一个集合,不能表示出一个集合,现在把货币数量减少之后能表示的集合和不能表示的集合不变——意味着原本的货币系统中有一些货币的存在是不影响表示出的集合的。

那么他们为什么没作用呢?假设能表示出来的货币为 x ,那么 \(x = na_i + ma_j\),如果这里的 \(a_j = ka_i\),也就是说\(a_j\)本身就能被 \(a_i\) 表示,那么 \(a_j\)​ 就是不需要存在的。所以本题就是让我们找哪些货币是能被原本有的货币表示出来的,这些货币需要被删掉。

假设现在要看最大的货币能否被表示,那么小的货币有选与不选两种可能,所以考虑背包。因为大的货币都是被小的货币表示,所以先排个序。

\(f[i][j]\)​:使用前 i 个货币能否表示 j 这个面额,那么 j 的最大值应该为数组中的最大值,对应最大背包容量。

状态转移:\(f[i][j] |= f[i-1][j]\),如果$ j - v[i] >= 0\(,\)f[i][j] |= f[i-1][j-v[i]]$​

最后,如果第 i 个货币 $f[i-1][v[i]] $存在,那么就应该被删除

代码(未优化)

没有优化的原因是优化后只能查看 i == n 时数组中的状态,而我们是需要知道 i 等于其他值时的状态的,所以优化

#include <bits/stdc++.h>

typedef std::pair<long long, long long> pll;
typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;

void solve(){
	int n;
	std::cin >> n;

	std::vector<int> v(n+1);
	for (int i = 1; i <= n; i++) std::cin >> v[i];

	std::sort(v.begin(), v.end());

	int V = v[n];
	std::vector f(n+1, std::vector<int>(V+1, 0));

	f[0][0] = 1;

	for (int i = 1; i <= n; i++){
		for (int j = 0; j <= V; j++){
			f[i][j] |= f[i-1][j];
			if (j >= v[i]) f[i][j] |= f[i][j-v[i]];
		}
	}

	int ans = n;
	for (int i = 1; i <= n; i++){
		if (f[i-1][v[i]]) ans -= 1;
	}

	std::cout << ans << '\n';
}

signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
	int t = 1, i;
	std::cin >> t;
	for (i = 0; i < t; i++){
		solve();
	}
	return 0;
}

代码(优化)

#include<stdio.h>
#include<algorithm>
#include<string.h>
using namespace std;
#define MAXAI 25005
#define MAXN 105
int f[MAXAI];
int a[MAXN];
int main()
{
    //freopen("money.in","r",stdin);
    //freopen("money.out","w",stdout);
    int i,j,n,T,ans;
    scanf("%d",&T);
    while(T--)
    {
        memset(f,0,sizeof(f));
        scanf("%d",&n);ans=n;
        for(i=1;i<=n;i++) scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        f[0]=1;
        for(i=1;i<=n;i++)
        {
            // 此刻的f数组还没有更新刚好表示 i - 1 时的状态。
            if(f[a[i]])
            {
                ans--;
                continue;
            }
            for(j=a[i];j<=a[n];j++)
            {
                f[j]=f[j]|f[j-a[i]];
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

P1757 通天之分组背包

rating:普及-

https://www.luogu.com.cn/problem/P1757

思路(分组背包)

就是一道分组背包的模版题,没啥思路。

代码

#include <bits/stdc++.h>

typedef std::pair<long long, long long> pll;
typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;

void solve(){
	int m, n, mx = 0;
	std::cin >> m >> n;

	std::vector v(n+1, std::vector<int>());
	std::vector<int> val(n+1), w(n+1);
	for (int i = 1; i <= n; i++){
		int a, b, c;
		std::cin >> a >> b >> c;
		w[i] = a, val[i] = b;
		v[c].push_back(i);
		mx = std::max(mx, c);
	}

	std::vector<int> f(m+1); //前i组,背包容量为j的情况下能得到的最大价值
	for (int i = 1; i <= mx; i++){
		for (int j = m; j >= 0; j--){
			for (auto idx : v[i]){
				if (j >= w[idx])
					f[j] = std::max(f[j], f[j-w[idx]]+val[idx]);
			}
		}
	}

	std::cout << f[m] << '\n';
}

signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
	int t = 1, i;
	for (i = 0; i < t; i++){
		solve();
	}
	return 0;
}

C. Bewitching Stargazer

rating:1500

https://codeforces.com/contest/2053/problem/C

思路(Trick)

可以拿一个样例来推一推

奇数:1 2 3 4 5 6 7 8(mid) 9 10 11 12 13 14 15,标出来的就是贡献答案的数

偶数:1 2 3 4 5 6 7(mid) 8 9 10 11 12 13 14

可以发现,不管是奇数还是偶数,mid左右的情况都是完全对称的,左边有一个数贡献了,右边也一定有一个数贡献了,并且贡献的大小也有关系,mid是两个左右对应的贡献的中点,所以他们的大小关系就是差mid,所以我们只需要递归左边,右边的情况可以根据左边的情况计算出来。

评述

此题递归的方法都告诉我们了,但是却又给了我们一个直接递归过不了的范围,可以说就是在引导我们思考如何优化递归过程。题目中很明显暴露出了左右对称这个性质,所以一定要利用好,既然题目给了,一定有作用。

代码

#include <bits/stdc++.h>

typedef std::pair<long long, long long> pll;
typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;

void solve(){
	i64 n, k;
	std::cin >> n >> k;

	auto dfs = [&](auto self, i64 l, i64 r) -> pll {
		if (r - l + 1 < k) return pll(0, 0);
		i64 mid = l + r >> 1;
		i64 res = 0, cnt = 0;
		if ((r - l + 1) % 2 == 0){
			auto [res1, cnt1] = self(self, l, mid);
			res = res1 + res1 + cnt1 * mid;
			cnt = cnt1 * 2;
		}else{
			auto [res1, cnt1] = self(self, l, mid - 1);
			res = res1 + res1 + cnt1 * mid + mid;
			cnt = cnt1 * 2 + 1;
		}

		return pll(res, cnt);
	};

	std::cout << dfs(dfs, 1, n).first << '\n';
}

signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
	int t = 1, i;
	std::cin >> t;
	for (i = 0; i < t; i++){
		solve();
	}
	return 0;
}

C. DIY

rating:1400

https://codeforces.com/problemset/problem/2038/C

思路(Trick)

注意序列中的数是不可以重复选的,草稿纸上画一个矩形发现它的四个顶点一定只由四个不同的数字组成,并且每个数字出现两次。所以选一个数一定就要选两次。

最后把符合条件的数筛选出来,贪心的选两个最小,选两个最大的数即可。

代码

#include <bits/stdc++.h>

typedef std::pair<long long, long long> pll;
typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;

void solve(){
	int n;
	std::cin >> n;

	std::vector<int> v;

	std::set<int> st;

	for (int i = 0; i < n; i++){
		int a; std::cin >> a;
		if (st.count(a)){
			v.push_back(a);
			st.erase(a);
		}else{
			st.insert(a);
		}
	}

	if (v.size() < 4){
		std::cout << "No\n";
		return;
	}

	std::sort(v.begin(), v.end());
	// for (auto e : v) std::cout << e << ' ';

	std::cout << "Yes\n";
	std::cout << v[0] << ' ' << v[1] << ' ' << v[0] << ' ' << v[v.size()-1] << ' ';
	std::cout << v[v.size()-2] << ' ' << v[1] << ' ' << v[v.size()-2] << ' ' << v[v.size()-1] << '\n';
}

signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
	int t = 1, i;
	std::cin >> t;
	for (i = 0; i < t; i++){
		solve();
	}
	return 0;
}

C. Alice's Adventures in Cutting Cake

rating:1600

https://codeforces.com/contest/2028/problem/C

思路(贪心)

只有两种切法,一种是从左开始,满足条件就切一刀,一种是从右开始,满足条件就切一刀,这样我们能够得到若干个切割位置。

最后再在这若干个切割位置中进行选择,选择满足条件的最大值即可。

代码

#include <bits/stdc++.h>

typedef std::pair<long long, long long> pll;
typedef std::pair<int, int> pii;
#define INF 0x3f3f3f3f
#define MOD 998244353
using i64 = long long;
const int N = 1e5+5;

void solve(){
	int n, m, v;
	std::cin >> n >> m >> v;

	std::vector<int> a(n);
	for (int i = 0; i < n; i++) std::cin >> a[i];

	std::vector<i64> pre(n+1);
	for (int i = 0; i < n; i++){
		pre[i+1] = pre[i] + a[i];
	}

	std::vector<int> f(m+1), g(m+1);
	f[0] = 0;
	for (int i = 1, j = 0; i <= m; i++){
		while (j <= n && pre[j] - pre[f[i-1]] < v) j++;
		f[i] = j;
	}

	g[0] = n;
	for (int i = 1, j = n; i <= m; i++){
		while (j >= 0 && pre[g[i-1]] - pre[j] < v) j--;
		g[i] = j;
	}

	i64 ans = -1;
	for (int i = 0; i <= m; i++){
		if (f[i] <= g[m-i]){
			ans = std::max(ans, pre[g[m-i]] - pre[f[i]]);
		}
	}

	std::cout << ans << '\n';

}

signed main()
{
	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout<<std::setiosflags(std::ios::fixed)<<std::setprecision(2);
	int t = 1, i;
	std::cin >> t;
	for (i = 0; i < t; i++){
		solve();
	}
	return 0;
}

952. 按公因数计算最大组件大小

rating:困难

思路

根据算术基本定理:一个大于1的自然数一定能表示成质数的乘积。

所以两个数是否有边只需要判断他们是否有相同的质因数就行。

因为题目要求最大联通组,正是并查集要干的事呀,所以自然想到并查集。

代码

struct DSU {
    std::vector<int> f, siz;
    
    DSU() {}
    DSU(int n) {
        init(n);
    }
    
    void init(int n) {
        f.resize(n);
        std::iota(f.begin(), f.end(), 0);
        siz.assign(n, 1);
    }
    
    int find(int x) {
        while (x != f[x]) {
            x = f[x] = f[f[x]];
        }
        return x;
    }
    
    bool same(int x, int y) {
        return find(x) == find(y);
    }
    
    bool merge(int x, int y) {
        x = find(x);
        y = find(y);
        if (x == y) {
            return false;
        }
        siz[x] += siz[y];
        f[y] = x;
        return true;
    }
    
    int size(int x) {
        return siz[find(x)];
    }
};

class Solution {
public:
    int largestComponentSize(vector<int>& nums) {
        const int N = 100001;

        DSU dsu(N);
        int n = nums.size();
        std::vector<int> fac(N, -1);
        for (int i = 0; i < n; i++){
            int x = nums[i];
            for (int j = 2; j * j <= x; j++){
                if (x % j == 0){
                    if (fac[j] == -1){
                        fac[j] = i;
                    }else{
                        dsu.merge(fac[j], i);
                    }
                }
                while (x % j == 0) x /= j;
            }
            if (x > 1) {
                    if (fac[x] == -1){
                        fac[x] = i;
                    }else{
                        dsu.merge(fac[x], i);
                    }
                }
        }

        int ans = 0;
        for (int i = 0; i < N; i++){
            ans = std::max(ans, dsu.siz[i]);
        }

        return ans;
    }
};
posted @ 2025-01-13 10:12  califeee  阅读(28)  评论(0)    收藏  举报