2024.1.31 寒假训练记录(14)

文章目录

  • CF 1918C XOR-distance
  • CF 1918D Blocking Elements
  • CF 1918E ace5 and Task Order

CF 1918C XOR-distance

题目链接

这题出思路还挺快的,就是考虑二进制每一位的贡献,可惜写了个假的贪心

正确贪心是,考虑两种情况,第一种情况是当处理结束时,a ^ x比b ^ x大,另一种情况是a ^ x比b ^ x小

当结束时前面比后面大,遍历每一位中 a是1 b是0 的位置就要尽可能让 a 变成 0 b变成 1,这样需要的代价是2的位数的次方,可以减少的差值是两倍的次方

当结束时前面比后面小,遍历每一位中 a是0 b是1 的位置就要尽可能让 a 变成 1 b变成 0,这样需要的代价是2的位数的次方,可以减少的差值是两倍的次方

有个需要注意的点是,要单独处理一下ab第一个不同的数,如果结束a大于b,那第一个不同的数一定要是a为1 b为0,如果不是就要调整成这样,调整不了就不考虑这种情况,a小于b也是类似的

下面是我很啰嗦的代码,可以把后面类似的两部分写成一个函数

#include 

using namespace std;

#define int long long

typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;

const int N = 1000010;

int cf[63];

void init()
{
	cf[0] = 1;
	for (int i = 1; i <= 61; i ++ ) cf[i] = 2 * cf[i - 1];
}

void solve()
{
	int a, b, r;
	cin >> a >> b >> r;
	if (a < b) swap(a, b);
	int ans = a - b;
	int res = ans;
	vector<int> aa, bb;
	if (a == 0) aa.push_back(0);
	if (b == 0) bb.push_back(0);
	int at = a, bt = b;
	while (at)
	{
		aa.push_back(at & 1);
		at >>= 1;
	}
	while (bt)
	{
		bb.push_back(bt & 1);
		bt >>= 1;
	}
	while (bb.size() < aa.size()) bb.push_back(0);

	int i = bb.size() - 1;
	int tmp = r;
	bool flag = false;
	while (i >= 0)
	{
		if (!flag && aa[i] != bb[i])
		{
			flag = true;
			if (aa[i] == 0)
			{
				if (cf[i] > r) break;
				else
				{
					r -= cf[i];
					ans -= 2 * cf[i];
					res = min(res, abs(ans));
					i -- ;
					continue;
				}
			}
			i -- ;
			continue;
		}
		if (r < cf[i] || aa[i] == bb[i])
		{
			i -- ;
			continue;
		}
		if (!flag && aa[i] == 0 && cf[i] >= r) break;
		if (aa[i] == 1 && bb[i] == 0 && flag)
		{
			r -= cf[i];
			ans -= cf[i] * 2;
			res = min(abs(ans), res);
		}
		i -- ;
	}
	r = tmp;
	i = bb.size() - 1;
	ans = b - a;
	flag = false;
	while (i >= 0)
	{
		if (!flag && aa[i] != bb[i])
		{
			flag = true;
			if (aa[i] == 1)
			{
				if (cf[i] > r) break;
				else
				{
					r -= cf[i];
					ans -= 2 * cf[i];
					res = min(res, abs(ans));
					i -- ;
					continue;
				}
			}
			i -- ;
			continue;	
		}
		if (r < cf[i] || aa[i] == bb[i])
		{
			i -- ;
			continue;
		}
		if (!flag && aa[i] == 0 && cf[i] >= r) break;
		if (bb[i] == 1 && aa[i] == 0 && flag)
		{
			r -= cf[i];
			ans -= cf[i] * 2;
			res = min(abs(ans), res);
		}
		i -- ;
	}
	cout << res << '\n';
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	init();

	int t = 1;
	cin >> t;
	while (t -- )
	{
		solve();
	}
}

CF 1918D Blocking Elements

题目链接

这题只能想到二分了,后面的单调队列没用过(极大可能是用过没总结就忘记了)

二分最终答案,然后让区间和都满足条件去判断端点和是否满足条件

dp[i]表示以i为最后一个分割点,最小的端点和是多少

j表示以i为最后一个端点,它前面能取到的最大的端点序号值

sum记录当前区间和,每当区间和超过mid了就把最前面一个值删掉,更新j

然后用单调队列维护以i为最后一个端点能取到的端点中端点和最小的序号,所以转移方程为dp[i] = dp[q.front()] + a[i]

关于单调队列总结在这

#include 

using namespace std;

#define int long long

typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;

const int N = 1000010;

void solve()
{
	int n;
	cin >> n;
	vector<int> a(n + 2);
	for (int i = 1; i <= n; i ++ ) cin >> a[i];

	auto check = [&](int mid)
	{
		vector<int> dp(n + 2);
		deque<int> q;
		int sum = 0;
		int j = 0;
		q.push_front(0);
		for (int i = 1; i <= n + 1; i ++ )
		{
			while (sum > mid) sum -= a[ ++ j];
			while (q.size() && q.front() < j) q.pop_front();
			if (!q.size()) return false;
			dp[i] = dp[q.front()] + a[i];
			while (q.size() && dp[q.back()] > dp[i]) q.pop_back();
			q.push_back(i);
			sum += a[i];
		}
		return dp[n + 1] <= mid;
	};

	int l = 0, r = 1e15;
	while (l < r)
	{
		int mid = l + r >> 1;
		if (check(mid)) r = mid;
		else l = mid + 1;
	}
	cout << r << '\n';
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int t = 1;
	cin >> t;
	while (t -- )
	{
		solve();
	}
}

CF 1918E ace5 and Task Order

题目链接

这题还是挺妙的

目标是给数组排序,还有个用于比较的元素 x,通过这个想到题目的交互可能是利用快排的思想,小于x的丢到左边,大于丢到右边,然后左右两边再递归排序

数据已经有序的时候快排会退化到 O ( n 2 ) O(n^2) O(n2),所以做之前先打乱顺序

#include 

using namespace std;

#define int long long

void solve()
{
	mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
	
	int n;
	cin >> n;

	vector<int> pos(n); // 存储值i的位置
	for (int i = 0; i < n; i ++ ) pos[i] = i;
	shuffle(pos.begin(), pos.end(), rnd);

	auto ask = [&](int x)
	{
		cout << "? " << x + 1 << endl;
		char nn;
		cin >> nn;
		return nn;
	};
	
	function<void(int, int)> qsort = [&](int l, int r)
	{
		if (l >= r) return;

		int mid = pos[l + r >> 1];
		while (ask(mid) != '=') ;

		vector<int> a, b, c; // = > <
		for (int i = l; i <= r; i ++ )
		{
			if (pos[i] == mid) a.push_back(pos[i]);
			else
			{
				char t = ask(pos[i]);
				ask(mid);
				if (t == '<') c.push_back(pos[i]);
				else b.push_back(pos[i]);
			}
		}

		copy(c.begin(), c.end(), pos.begin() + l);
		copy(b.begin(), b.end(), pos.begin() + c.size() + l + 1);
		pos[l + c.size()] = mid;

		qsort(l, l + c.size() - 1);
		qsort(l + c.size() + 1, r);
	};

	qsort(0, n - 1);

	vector<int> ans(n);
	for (int i = 0; i < n; i ++ ) ans[pos[i]] = i;
	cout << "!";
	for (int i = 0; i < n; i ++ ) cout << ' ' << ans[i] + 1;
	cout << endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int t = 1;
	cin >> t;
	while (t -- )
	{
		solve();
	}
}

你可能感兴趣的:(2024寒假训练记录,算法)