2024牛客寒假算法基础集训营1 复盘

赛后总结

这一场总体上打得稀烂,自己的水平没有完全发挥出来。直接原因就是在一道简单题上卡住了,没有灵活变通,致使自己思路也混沌了,其实后面很多题都可以做。在赛场上要及时调整,做好决策。
另外有一题数据范围没有看仔细,导致浪费了很多时间。有一题使用了静态数组,但规模开小了,少打了一个0,导致多WA了一发。这些问题以后都要注意。
理论上ACM不需要对拍,但最好还是准备好相应的模板,以备不时之需。

补题

B

赛时卡住的题,思路是对的,但是代码出现了很难发现的问题。
这种题完全不应该卡,主要原因就在于没有十分认真对待题目,思路不是很清晰就写题,越写越乱。
以后遇到这种情况需要及时跳出来,转换策略做其余的题目。
对于像这种思路很简单的题目,最重要的是理清思路,完全清晰后再写代码,不然十分容易爆错。
而写了屎山后发现是错的,要及时决断,简单题不乏尝试一下重构代码。

I

比较奇特的一道题。
关键点在于找到两种方法的不同点,分析在数据量很大情况下的差异。
甚至可以手动模拟两种方法进行生成,用自己生成的结果之间的差异作为评判标准,再和输入进行比较,进而得出结果。
多WA几发就过了

H

学到了一种高效的无遗漏的方法,用于枚举所有物品组合的合法方案,条件为满足物品重量按位或起来不超过背包重量:
对于背包重量 m m m按位分析,假设当前为第 i i i位,如果该位为 1 1 1, 那么该位将 m m m分成三个部分,第 0 0 0位到第 i − 1 i - 1 i1位、第 i i i位、第 i + 1 i+1 i+1位到最高位。对于要选择的物品组合,我们可以强制重量按位或的和的第 i i i位为 0 0 0, 第 0 0 0位到第 i − 1 i-1 i1位随意,第 i + 1 i+1 i+1位到最高位必须是 m m m的“子集”(也就是按位或和某一位为 1 1 1, 那么 m m m对应的这一位也必须为 1 1 1)。
这样可以不重不漏地枚举所有情况,注意要考虑 i = − 1 i=-1 i=1的情况(即按位或和所有位都必须是 m m m所有位的“子集”)。

#include 
using namespace std;
using ll = long long;
using ai = array<int, 2>;
using al = array<ll, 2>;

void solve() {
    int n, m;
    cin >> n >> m;
    vector<int> w(n + 1);
    vector<ll> v(n + 1);
    for (int i = 1; i <= n; ++i) {
        cin >> v[i] >> w[i];
    }
    bitset<30> a(m);
    ll ans = 0LL;
    auto choose = [&](int p) {
        ll now = 0LL;
        for (int i = 1; i <= n; ++i) {
            bitset<30> b(w[i]);
            if (p != -1 && b[p]) continue;
            bool fl = 1;
            for (int j = p + 1; j < 30; ++j)
                if (b[j] && !a[j]) {fl = 0; break;}
            if (!fl) continue;
            now += v[i];
        }
        ans = max(ans, now);
    };
    for (int i = 0; i < 30; ++i) {
        if (a[i]) {
            choose(i);
        }
    }
    choose(-1);
    cout << ans << '\n';
}

int main() {
    cin.tie(nullptr) -> sync_with_stdio(false);
    int _ = 1;
    cin >> _;
    while (_--)
        solve();
    return 0;
}

D

这一题破题点在于数据范围,发现询问的数字不大,而数组长度很大,所以可以进行分类讨论。

  • 当数组长度 n > 30 n > 30 n>30时,因为询问的数字绝对值最大为1e9, 且 2 30 > 1 e 9 2^{30} > 1e9 230>1e9, 所以整个数组绝对值非1的个数不能超过 29 29 29, 进而对于每一个数,我们先判断把它变成1或-1后整个数组绝对值非1的个数是否符合要求,如果符合,用一个set记录改变之后的结果。在这种限制条件下,能改变的数比较少。
  • 当数组长度不超过 30 30 30时,我们来寻找一下改变的值的边界条件,当数组中存在至少两个绝对值大于等于 1 e 9 \sqrt{1e9} 1e9 的数时,这样乘积肯定会出界。所以我们只需要计算一下第二大的数到 1 e 9 \sqrt{1e9} 1e9 的距离和第二小的数到 − 1 e 9 -\sqrt{1e9} 1e9 的距离,将这两个距离作为上下界,枚举所有情况即可,最多有 2 × 1 e 9 2 \times \sqrt{1e9} 2×1e9 种情况,不会超时。
#include 
using namespace std;
using ll = long long;

int main() {
	cin.tie(0) -> sync_with_stdio(0);
	int n, Q;
	cin >> n >> Q;
	vector<ll> a(n + 1);
	map<ll, int> cnt;
	for (int i = 1; i <= n; ++i) {
		cin >> a[i];
		cnt[a[i]] += 1;
	}
	set<ll> s{0LL};
	auto change = [&](ll x) {
		ll ret = 1;
		for (int i = 1; i <= n; ++i) {
			ret *= a[i] + x;
			if (abs(ret) > 1e9) return;
		}
		s.insert(ret);
	};	
	if (n > 30) {
		map<int, bool> vis;
		for (int i = 1; i <= n; ++i) {
			if (vis.find(a[i]) != vis.end()) continue;
			vis[a[i]] = 1;
			ll x = 1 - a[i], y = -1 - a[i];
			int now = n - (cnt[a[i]] + cnt[-1LL - x]);
			if (now < 30) change(x);
			now = n - (cnt[a[i]] + cnt[1LL - y]);
			if (now < 30) change(y);
		}
	} else {
		sort(a.begin() + 1, a.end());
		ll L, R;
		R = 4e4 - a[n - 1];
		L = -4e4 - a[2];
//         cout << L << ' ' << R << '\n';
		for (ll i = L; i <= R; ++i)
			change(i);
	}
//     cout << s.size() << '\n';
	while (Q--) {
		ll M;
		cin >> M;
		if (s.count(M)) cout << "Yes\n";
		else cout << "No\n";
	}
	return 0;
}

你可能感兴趣的:(算法)