CF Diary IV

10.28-11.22
\(10\) 题一篇 \(\texttt{>o<}\)

1741G. Kirill and Company

\(\texttt{*2200}\)

题意

\(n(2\le n\le10^4)\) 个点 \(m(n-1\le m\le min(10^4,\frac{n(n-1)}{2}))\) 条边的无向连通图, \(1\) 号点有 \(f(1\le f\le10^4)\) 个人,每个人的家为 \(h_i(2\le h_i\le n)\) 。有 \(k(1\le k\le min(6,f))\) 个人没车,其他人都有车,有车的人会选择一条到家的最短路开回家,没车的人可以选择搭一个会路过自己家的人的顺风车,问最少有多少人要走着回家。

题解

发现 \(k\) 很小,于是考虑状压,我们通过 \(bfs\) 预处理出对于每个节点,其能够搭载的无车人员的集合,注意多种方案不能直接或起来,因为只能选一条路径开回家,接下来做一下可行性 \(dp\) ,记 \(f_{i,j}\) 表示考虑前 \(i\) 个人,搭载无车人员状态为 \(j\) 是否可行即可,注意只有考虑到有车人员时进行转移,最后转移和答案也非常简单。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 10007;
const LL mod = 998244353;
const int maxn = 10010;

int T, N, M, H[maxn], F, K, d[maxn], nocar[maxn];
bool can[maxn][1 << 6], f[maxn][1 << 6], C[maxn];
vector<int>G[maxn];

void add_edge(int from, int to)
{
	G[from].push_back(to);
	G[to].push_back(from);
}

void bfs()
{
	queue<int>que;
	que.push(1), d[1] = 0;
	while (!que.empty())
	{
		int v = que.front(); que.pop();
		for (auto& to : G[v])
		{
			if (d[to] == -1)
				que.push(to), d[to] = d[v] + 1;
			if (d[to] == d[v] + 1)
			{
				for (int i = 0; i < (1 << 6); i++)
					can[to][i | nocar[to]] |= can[v][i];
			}
		}
	}
}

int cnt(int x)
{
	int ret = 0;
	while (x)
	{
		if (x & 1)
			ret++;
		x >>= 1;
	}
	return ret;
}

void solve()
{
	bfs(), f[0][0] = true;
	for (int i = 1; i <= F; i++)
	{
		if (!C[i])
		{
			for (int j = 0; j < (1 << 6); j++)
				f[i][j] = f[i - 1][j];
		}
		else
		{
			for (int j = 0; j < (1 << 6); j++)
			{
				for (int k = 0; k < (1 << 6); k++)
					f[i][j | k] |= (f[i - 1][j] & can[H[i]][k]);
			}
		}
	}
	int ans = 0;
	for (int i = 0; i < (1 << 6); i++)
	{
		if (!f[F][i])
			continue;
		ans = max(ans, cnt(i));
	}
	cout << K - ans << endl;
}

int main()
{
	IOS;
	cin >> T;
	while (T--)
	{
		cin >> N >> M;
		for (int i = 1; i <= N; i++)
			G[i].clear(), d[i] = -1, nocar[i] = 0;
		for (int i = 1; i <= N; i++)
		{
			for (int j = 0; j < (1 << 6); j++)
				can[i][j] = false;
		}
		int u, v, id;
		for (int i = 1; i <= M; i++)
			cin >> u >> v, add_edge(u, v);
		cin >> F;
		for (int i = 1; i <= F; i++)
			cin >> H[i], C[i] = true;
		cin >> K;
		for (int i = 0; i < K; i++)
			cin >> id, nocar[H[id]] |= (1 << i), C[id] = false;
		for (int i = 1; i <= N; i++)
			can[i][0] = true;
		for (int i = 1; i <= F; i++)
		{
			for (int j = 0; j < (1 << 6); j++)
				f[i][j] = false;
		}
		solve();
	}
	return 0;
}

1732C2. Sheikh (Hard Version)

\(\texttt{*2100}\)

题意

长为 \(n(1\le n\le 10^5)\) 的序列 \(a(0\le a_i\le10^9)\)\(q=n\) 次询问,每次询问 \(L,R\) ,求区间 \(L,R\) 内中一个最小的区间,其 \(\sum a_i-\oplus a_i\) 最大。

题解

由于 \(a+b\ge a\oplus b\) ,显然最大值就是选取整个区间的时候,发现这点可以轻松通过简单版本,对于困难版本,由于上述性质,随着区间的扩展,这个值单调不减,所以我们考虑缩短区间时该值何时会减少,缩短一个元素会使得第一项减去 \(a_i\) ,为了值不减,必须让第二项也恰好减少 \(a_i\) ,而这个条件是 \(a_i\) 的每一位 \(1\) 都恰好在第二项中出现,也就是说,缩短区间时扔掉的元素各位上 \(1\) 共最多出现一次,意味着删数不多于 \(31\) ,于是暴力枚举左右删数情况,注意预处理一下 \(0\) 的问题即可,复杂度 \(nlog^2a\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 10007;
const LL mod = 998244353;
const int maxn = 100010;

LL T, N, A[maxn], Q, S[maxn], X[maxn], Z[maxn], mx, L, R, nxt[maxn], rnxt[maxn], sl[maxn], sr[maxn];
vector<int>nz;

PII Check(int l, int r, int x, int y)
{
	l = sl[l], r = sr[r];
	LL a = S[R] - S[L - 1], b = X[R] ^ X[L - 1];
	for (int i = 1; i <= x; i++, l = nxt[l])
	{
		if (a - A[l] - (b ^ A[l]) != mx)
			return PII(0, 0);
		a -= A[l], b ^= A[l];
	}
	for (int i = 1; i <= y; i++, r = rnxt[r])
	{
		if (a - A[r] - (b ^ A[r]) != mx)
			return PII(0, 0);
		a -= A[r], b ^= A[r];
	}
	return PII(l, r);
}

void solve()
{
	for (int i = 1; i <= N; i++)
		S[i] = S[i - 1] + A[i], X[i] = X[i - 1] ^ A[i], Z[i] = Z[i - 1] + (A[i] != 0);
	int lst = 0;
	for (int i = 1; i <= N; i++)
	{
		if (A[i] != 0)
			nxt[lst] = i, nz.push_back(i), lst = i;
	}
	lst = N + 1;
	for (int i = N; i >= 1; i--)
	{
		if (A[i] != 0)
			rnxt[lst] = i, lst = i;
	}
	for (int i = 1; i <= N; i++)
	{
		auto it = lower_bound(all(nz), i);
		if (it != nz.end())
			sl[i] = *it;
		it = upper_bound(all(nz), i);
		if (it != nz.begin())
			sr[i] = *(it - 1);
	}
	while (Q--)
	{
		cin >> L >> R, mx = S[R] - S[L - 1] - (X[R] ^ X[L - 1]);
		LL lim = min(30LL, Z[R] - Z[L - 1] - 1), len = R - L + 1;
		PII ans(L, R);
		for (int i = lim; i >= 0; i--)
		{
			for (int j = 0; j <= i; j++)
			{
				PII tmp = Check(L, R, j, i - j);
				if (tmp != PII(0, 0))
				{
					if (tmp.second - tmp.first + 1 < len)
						len = tmp.second - tmp.first + 1, ans = tmp;
				}
			}
		}
		if (lim == -1)
			cout << L << ' ' << L << endl;
		else
			cout << ans.first << ' ' << ans.second << endl;
	}
}

int main()
{
	IOS;
	cin >> T;
	while (T--)
	{
		nz.clear();
		cin >> N >> Q;
		for (int i = 1; i <= N; i++)
			cin >> A[i];
		solve();
	}
	return 0;
}

1743E. FTL

\(\texttt{*2400}\)

题意

有两个人,攻击力 \(p1,p2(2\le p\le5000)\) ,充能时间 \(t1,t2(1\le t\le10^{12})\) 。怪物血量 \(h(1\le h\le5000)\) ,防御 \(s(1\le s\le min(p1,p2)\) 。单发伤害为 \(p_i-s\) 。两人一起发射伤害为 \(p_1+p_2-s\) 。求击杀怪物所需最少时间。

题解

本题难点在于状态的设计。
考虑两种情况,首先我们可以都单发,不合击,这种情况下的答案容易算出。
主要是有合击的情况,需要注意到每次合击之后,相当于二者充能时间都归零,即回到初始状态,因此可以由此来设计 \(dp\) 状态。记 \(f_i\) 为造成至少 \(i\) 点伤害,且最后一击是唯一一次合击时的最短时间, \(g_i\) 为造成至少 \(i\) 点伤害,进行了若干次合击的答案。 \(g\) 的转移十分显然,即

\[g_i=min_{j=0}^{i-1}\{g_j+f_i-j\} \]

对于 \(f\) ,我们考虑枚举在最后合击前, \(p_1\) 的攻击次数 \(j\) ,从 \(0\) 枚举到可能造成的最大伤害 \(n\) 的情况,即最后恰好多出一次攻击后的伤害,之后可以计算出 \(p_2\) 所需要的最少攻击次数,将 \(p_1,p_2\) 单次攻击的次数 \(+1\) (因为还有最后一次合击)分别乘以各自充能时间取 \(max\) 即可得到当前 \(j\) 下的最小时间,将所有枚举情况取 \(min\) 即可。
初值 \(g_0=f_0=0\) ,其余 \(inf\)\(dp\)\(1\)\(n\) ,伤害大于 \(h\) 的时候对答案产生贡献,取 \(min\) 即可,复杂度 \(O(n^2)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 10010;

LL P[3], T[3], H, S, f[maxn], g[maxn], N, D[4];

void solve()
{
	D[1] = P[1] - S, D[2] = P[2] - S, D[3] = P[1] + P[2] - S;
	mst(g, INF), mst(f, INF), g[0] = f[0] = 0;
	LL N = H + max(P[1], P[2]), ans = min((D[1] + H - 1) / D[1] * T[1], (D[2] + H - 1) / D[2] * T[2]);
	for (int i = 1; i <= N; i++)
	{
		for (int j = 0; j * D[1] + D[3] <= N; j++)
			f[i] = min(f[i], max(T[1] * (j + 1), T[2] * (max(0LL, (i - j * D[1] - D[3] + D[2] - 1)) / D[2] + 1)));
		for (int j = 0; j < i; j++)
			g[i] = min(g[i], g[j] + f[i - j]);
		if (i >= H)
			ans = min(g[i], ans);
	}
	cout << ans << endl;
}

int main()
{
	IOS;
	for (int i = 1; i <= 2; i++)
		cin >> P[i] >> T[i];
	cin >> H >> S;
	solve();
	return 0;
}

1743F. Intersection and Union

\(\texttt{*2300}\)

题意

给定若干个集合包含 \([l,r]\) 内所有整数,从前向后依次进行运算,每次运算有 \(3\) 种选择,分别为交,并,对称差,一共是 \(3^{n-1}\) 种,求所有不同的操作序列结束后的剩余元素数量之和。

题解

考虑每个数字对最后答案的贡献,考虑每个数在集合中是否出现,则三种运算分别对应按位 \(|\ \&\ \oplus\) 。对于单独的一个数字,记 \(x_i\) 为第 \(i\) 个集合内是否有该元素,我们可以轻松 \(dp\) 处理:

\[\begin{bmatrix} f_{i,0}\\ f_{i,1} \end{bmatrix} = \begin{bmatrix} 3-2x_i & 1\\ 2x_i & 2 \end{bmatrix} \cdot \begin{bmatrix} f_{i,0}\\ f_{i,1} \end{bmatrix} \]

但是这个矩阵并不是固定的,我们考虑用线段树来维护区间矩阵的乘积,之后从小到大考虑每个元素,注意到给的所有集合都是连续的,某一个集合处的矩阵只有两个端点处才会发生变化,变化只有 \(2n\) 次,于是直接单点修改即可,注意线段树维护矩阵乘积时的运算顺序。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;
using TP = tuple<int, int, int>;
using vec = vector<LL>;
using Mat = vector<vec>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
//#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
#define lc p*2
#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 300010;

LL N, L[maxn], R[maxn], mx = 0;
Mat A(2, vec(2)), B(2, vec(2));
vector<PII>change[maxn];

struct Node {
	int l, r;
	LL dat[2][2];
}tr[maxn * 4];

void pushup(int p)
{
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 2; j++)
			tr[p].dat[i][j] = 0;
	}
	for (int i = 0; i < 2; i++)
	{
		for (int k = 0; k < 2; k++)
		{
			for (int j = 0; j < 2; j++)
				tr[p].dat[i][j] = (tr[p].dat[i][j] + tr[rc].dat[i][k] * tr[lc].dat[k][j] % mod) % mod;
		}
	}
}

void build(int l, int r, int p)
{
	tr[p].l = l, tr[p].r = r;
	if (l + 1 == r)
		return;
	int mid = (l + r) / 2;
	build(l, mid, lc), build(mid, r, rc), pushup(p);
}

void modify(int a, int t, int p)
{
	if (tr[p].l + 1 == tr[p].r)
	{
		if (t == 0)
		{
			for (int i = 0; i < 2; i++)
			{
				for (int j = 0; j < 2; j++)
					tr[p].dat[i][j] = A[i][j];
			}
		}
		else
		{
			for (int i = 0; i < 2; i++)
			{
				for (int j = 0; j < 2; j++)
					tr[p].dat[i][j] = B[i][j];
			}
		}
		return;
	}
	int mid = (tr[p].l + tr[p].r) / 2;
	if (a < mid)
		modify(a, t, lc);
	else
		modify(a, t, rc);
	pushup(p);
}

void solve()
{
	A[0][0] = 3, A[0][1] = 1, A[1][0] = 0, A[1][1] = 2;
	B[0][0] = B[0][1] = 1, B[1][0] = B[1][1] = 2;
	Mat base(2, vec(1));
	build(1, N, 1);
	LL ans = 0;
	if (!L[1])
		base[0][0] = 0, base[1][0] = 1;
	else
		base[0][0] = 1, base[1][0] = 0;
	for (int i = 2; i <= N; i++)
	{
		if (!L[i])
			modify(i - 1, 1, 1);
		else
			modify(i - 1, 0, 1);
	}
	for (int i = 0; i <= mx; i++)
	{
		ans = (ans + (tr[1].dat[1][0] * base[0][0] % mod + tr[1].dat[1][1] * base[1][0] % mod) % mod) % mod;
		for (auto& [x, y] : change[i + 1])
		{
			if (x == 1)
				base[y ^ 1][0] = 0, base[y][0] = 1;
			else
				modify(x - 1, y, 1);
		}
	}
	cout << ans << endl;
}

int main()
{
	IOS;
	cin >> N;
	for (int i = 1; i <= N; i++)
		cin >> L[i] >> R[i], change[L[i]].push_back(PII(i, 1)), change[R[i] + 1].push_back(PII(i, 0)), mx = max(mx, R[i]);
	solve();
	return 0;
}

1746D. Equal Binary Subsequences

\(\texttt{*2200}\)

题意

一个长为 \(2n(1\le n\le10^5)\)\(01\) 串,可以至多进行一次操作,选择一个子序列,将其循环右移一次。之后构造出两个完全相等的子序列,输出操作序列的下标以及构造出的子序列的下标,无解输出 \(-1\)

题解

显然如果 \(1\)\(0\) 的数量有一个不是偶数即无解,其他情况我们一定可以按如下方法构造出一个解:
我们两个两个考虑,如果这两个位置相同则不用处理,如果不同,我们从中选出为 \(0\) 的加入操作序列,下一个这样的则选出为 \(1\) 的,以此类推,最后操作后原串从头开始两个两个考虑的时候两个位置都相同,最后选择奇数下标或者偶数下标输出即可。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair <int, int> PII;
#define all(x) x.begin(),x.end()
//#define int LL
//#define lc p*2+1
//#define rc p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 998244353;
const LL MOD = 1e9 + 7;
const int maxn = 100010;

int T, N, sum[2];
string S;

void solve()
{
	sum[0] = sum[1] = 0;
	for (int i = 1; i <= N * 2; i++)
		sum[S[i] - '0']++;
	if (sum[1] % 2 || sum[0] % 2)
	{
		cout << -1 << endl;
		return;
	}
	vector<int>pos;
	int now = 0;
	for (int i = 1; i <= N; i++)
	{
		if (S[2 * i] == S[2 * i - 1])
			continue;
		if (S[2 * i] - '0' == now)
			pos.push_back(2 * i);
		else
			pos.push_back(2 * i - 1);
		now ^= 1;
	}
	cout << pos.size() << ' ';
	for (auto& p : pos)
		cout << p << ' ';
	cout << endl;
	for (int i = 1; i <= N * 2; i += 2)
		cout << i << ' ';
	cout << endl;
}

int main()
{
	IOS, cin >> T;
	while (T--)
		cin >> N >> S, S = ' ' + S, solve();
	return 0;
}

1749E. Cactus Wall

\(\texttt{*2400}\)

题意

一个 \(nm(nm\le4\cdot10^5)\) 的网格,每个网格可以种仙人掌,任意两颗种植的仙人掌不共享一条边,怪物可以上下左右移动,现在给出一个网格,上面已经合法地种了一部分仙人掌,现在求种植最少的额外仙人掌,使得怪物无法从第一行走到最后一行的方案,无解输出-1。

题解

这题。。。只要想对了就非常简单,怪物的路经很多,不好整,我们从仙人掌的种植上来考虑,问题可以转化为求出从第一列开始只能向左下,右下,右上,左上走,走到最后一列的最短路径,首先不能种植仙人掌的格子可以预处理好,之后如果踩到空地则距离 \(+1\) ,否则 \(+0\) ,转化为了 \(01bfs\) ,直接做就可以了,复杂度 \(O(nm)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 400010;

int N, M, T, d[maxn], dx[8] = { 1,1,-1,-1,0,0,1,-1 }, dy[8] = { 1,-1,1,-1,1,-1,0,0 }, pr[maxn];
char A[maxn];
bool ban[maxn];
deque<int>deq;
inline int idx(int x, int y) { return (x - 1) * M + y; }
inline PII co(int id) { return PII((id - 1) / M + 1, (id - 1) % M + 1); }

void solve()
{
	for (int i = 1; i <= N * M; i++)
	{
		if (A[i] == '.')
			continue;
		PII tmp = co(i);
		for (int j = 4; j < 8; j++)
		{
			int nx = tmp.first + dx[j], ny = tmp.second + dy[j];
			if (nx >= 1 && nx <= N && ny >= 1 && ny <= M)
				ban[idx(nx, ny)] = true;
		}
	}
	for (int i = 1; i <= N; i++)
	{
		if (ban[idx(i, 1)])
			continue;
		if (A[idx(i, 1)] == '#')
			deq.push_front(idx(i, 1)), d[idx(i, 1)] = 0;
		else
			deq.push_back(idx(i, 1)), d[idx(i, 1)] = 1;
		pr[idx(i, 1)] = 0;
	}
	while (!deq.empty())
	{
		int t = deq.front(); deq.pop_front(); PII tmp = co(t);
		for (int j = 0; j < 4; j++)
		{
			int nx = tmp.first + dx[j], ny = tmp.second + dy[j];
			if (nx >= 1 && nx <= N && ny >= 1 && ny <= M && !ban[idx(nx, ny)] && d[idx(nx, ny)] == -1)
			{
				if (A[idx(nx, ny)] == '#')
					deq.push_front(idx(nx, ny)), d[idx(nx, ny)] = d[t];
				else
					deq.push_back(idx(nx, ny)), d[idx(nx, ny)] = d[t] + 1;
				pr[idx(nx, ny)] = t;
			}
		}
	}
	int ans = inf, S = 0;
	for (int i = 1; i <= N; i++)
	{
		if (d[idx(i, M)] == -1)
			continue;
		if (ans > d[idx(i, M)])
			S = idx(i, M), ans = d[idx(i, M)];
	}
	if (ans == inf)
		cout << "NO" << endl;
	else
	{
		cout << "YES" << endl;
		while (S != 0)
			A[S] = '#', S = pr[S];
		for (int i = 1; i <= N; i++)
		{
			for (int j = 1; j <= M; j++)
				cout << A[idx(i, j)];
			cout << endl;
		}
	}
}

int main()
{
	IOS, cin >> T;
	while (T--)
	{
		cin >> N >> M;
		for (int i = 1; i <= N * M; i++)
			cin >> A[i], d[i] = pr[i] = -1, ban[i] = 0;
		solve();
	}
	return 0;
}

1739E. Cleaning Robot

\(\texttt{*2400}\)

题意

一个 \(2\cdot n\) 的网格,有一些有垃圾,有个扫地机器人,从 \((1,1)\) 开始,每次找离自己最近的垃圾,走过去清扫,如果某一时刻有多个离自己最近,机器人自毁,但我们可以提前手动清扫一部分垃圾,求机器人能够清扫的最多垃圾数。

题解

考虑 \(dp\) ,显然机器人只会向右,下,上,不会向左,因为正着推判断当前哪个最近转移比较困难,我们倒着考虑,记 \(f_{i,j}\) 为机器人在 \((i,j)\) ,第 \(j\) 列之前垃圾全部清空时,需要手动清扫的最小数量。
我们根据 \((i\oplus1,j)\)\((i,j+1)\) 格子是否有垃圾的所有情况分类讨论转移的方式:
\((i\oplus1,j)\) 没有垃圾,直接向右走一格是没有问题的,即 \(f_{i,j}=f_{i,j+1}\)
\((i\oplus1,j)\) 有垃圾,还要看 \((i,j+1)\)
如果也有垃圾,此时二者距离相同,必须手动清一个,清了 \((i,j+1)\) 则直接走到 \((i\oplus1,j+2)\) 都没问题,而清了另外一个只走到 \((i,j+1)\) ,于是 \(f_{i,j}=min(f_{i,j+1},f_{i\oplus1,j+2})+1\)
如果没有,清或不清都行,清了就直接到 \((i,j+1)\) ,不清就到 \((i\oplus1,j+1)\) ,于是 \(f_{i,j}=min(f_{i,j+1},f_{i\oplus1,j+1}+1)\)
初值 \(f_{1,n}=f_{0,n}=0\) ,其他为 \(inf\) ,最后答案为 \(sum-f_{0,1}\) ,复杂度 \(O(n)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<int, int>;
using PLL = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 400010;

int N, f[2][maxn], all = 0;
char A[2][maxn];

void solve()
{
	mst(f, inf);
	f[0][N] = f[1][N] = 0;
	for (int i = N - 1; i >= 1; i--)
	{
		for (int j = 0; j <= 1; j++)
		{
			if (A[j ^ 1][i] == '0')
				f[j][i] = min(f[j][1], f[j][i + 1]);
			else if (A[j][i + 1] == '0')
				f[j][i] = min(f[j][i], min(f[j ^ 1][i + 1], f[j][i + 1] + 1));
			else
				f[j][i] = min(f[j][i], min(f[j ^ 1][i + 2], f[j][i + 1]) + 1);
		}
	}
	cout << all - f[0][1] << endl;
}

int main()
{
	IOS;
	cin >> N;
	for (int i = 0; i <= 1; i++)
	{
		for (int j = 1; j <= N; j++)
			cin >> A[i][j], all += A[i][j] - '0';
	}
	solve();
	return 0;
}

1733D2. Zero-One (Hard Version)

\(\texttt{*2000}\)

题意

两个长为 \(n(5\le n\le 5000)\)\(01\)\(a,b\) ,每次操作 \(l,r\) ,对 \(a_l\)\(a_r\) 分别取反,如果 \(r=l+1\) ,花费 \(X\) ,否则花费 \(Y(1\le X,Y\le10^9)\) 。求使 \(a\) 变为 \(b\) 的最小花费,或输出无解。

题解

首先 \(X\ge Y\) 的情况直接按 \(D1\) 的做法来就可以了,无解的情况依然不变。对于 \(X<Y\) ,我们考虑每一位是否需要翻转,对于一对 \(l,r\) ,如果要翻转他们可以选择花费 \(Y\) ,也可以选择花费 \((r-l)\cdot X\) ,因为限定 \(X<Y\) ,所以不考虑花 \(2Y\) 的情况。接下来我们考虑 \(a<b<c<d\) ,如果 \(a,c\)\(b,d\) 分别配对,那么改成 \(a,b\)\(c,d\) 配对一定更优,因为这样两对的距离都缩短了,之后再考虑如果 \(a,d\)\(b,c\) 分别配对,那么 \(a,d\) 这一对花费只能为 \(Y\) ,因为如果其花费为 \(X\cdot(d-a)\) ,那么改为 \(a,b\)\(c,d\) 分别配对,二者也可以使用 \(X\) 来操作,而区间长度更短,花费也就更小。我们考虑区间 \(dp\) ,预处理一下需要翻转的坐标,形成新的序列,显然仅有区间长度为偶数的区间有答案,之后对于新序列的区间 \([l,r]\) ,仅有 \([l+2,r],[l,r-2],[l+1,r-1]\) 这三种转移方式,不能从中间切开的原因是,如果从中间切开,仅有两边分别有 \(l,k\)\(k+1,r\) 配对时有影响,根据之前的结论,这两对花费均为 \(Y\) ,如果改成 \(l,r\)\(k,k+1\) 配对, \(l,r\) 花费仍为 \(Y\)\(k,k+1\) 花费不会劣于 \(Y\) ,所以这种情况一定不会作为最优解,所以不用考虑。之后就简单转移即可,复杂度 \(O(n^2)\)

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair < int, int > PII;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
//#define int LL
//#define lc p*2+1
//#define rc p*2+2
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 998244353;
const LL MOD = 1000000007;
const int maxn = 5010;

LL T, N, X, Y, f[maxn][maxn], pos[maxn];
bool rev[maxn];
string A, B;

void solve()
{
	LL ans = 0, cnt = 0;
	for (int i = 1; i <= N; i++)
		rev[i] = false;
	for (int i = 1; i <= N; i++)
	{
		if (A[i] != B[i])
			pos[++cnt] = i, rev[i] = true;
	}
	if (cnt & 1)
	{
		cout << -1 << endl;
		return;
	}
	if (X >= Y)
	{
		if (cnt == 2)
		{
			for (int i = 1; i < N; i++)
			{
				if (A[i] != B[i] && A[i + 1] != B[i + 1])
				{
					cout << min(X, Y * 2) << endl;
					return;
				}
			}
			cout << Y << endl;
		}
		else
			cout << cnt / 2 * Y << endl;
	}
	else
	{
		for (int i = 1; i <= cnt; i++)
		{
			for (int j = 1; j <= cnt; j++)
				f[i][j] = INF;
		}
		for (int i = 1; i < cnt; i++)
			f[i][i + 1] = min((pos[i + 1] - pos[i]) * X, Y);
		for (int len = 4; len <= cnt; len++)
		{
			for (int l = 1; l + len - 1 <= cnt; l++)
			{
				int r = l + len - 1;
				f[l][r] = min(f[l][r], f[l + 1][r - 1] + Y);
				f[l][r] = min(f[l][r], f[l + 2][r] + X * (pos[l + 1] - pos[l]));
				f[l][r] = min(f[l][r], f[l][r - 2] + X * (pos[r] - pos[r - 1]));
			}
		}
		cout << f[1][cnt] << endl;
	}
}

int main()
{
	IOS;
	cin >> T;
	int t = T;
	while (T--)
	{
		cin >> N >> X >> Y >> A >> B;
		A = ' ' + A, B = ' ' + B;
		solve();
	}

	return 0;
}

1738F. Connectivity Addicts

\(\texttt{*2400}\)

题意

交互题,一个 \(n(1\le n\le1000)\) 的简单无向图,已知每个节点度数 \(d_i(0\le d_i\le n-1)\) 。每次询问一个节点,对于该节点的第 \(i\) 次询问,给出该节点第 \(i\) 个相邻节点,要给本图染色,同一颜色 \(c\) 必须联通,且满足 \(\sum_{v\in c}d_v\le n_c^2\)\(n_c\) 为染成颜色 \(c\) 的节点个数,询问不能超过 \(n\) 次,保证有解。

题解

需要注意到 \(\sum_{v\in c}d_v\le n_c^2\) 应当进行转换,容易得到应满足的条件为 \(0\le\sum_{v\in c} n_c-\sum_{v\in c}d_v=\sum_{v\in c} (n_c-d_v)\) 。到这里就好做了,考虑一个更严格的限制,让所有节点 \(v\) 都满足 \(n_c\ge d_v)\) 。我们感性地认为应当先找个度数最多的节点开始,询问其所有相邻节点(就是感觉,没有为什么)。然后可以从中发现性质,如果选择以该节点为中心的菊花染同一颜色,该菊花则满足性质,因为 \(n_c=d_v+1\) ,而对于其他点,度数一定更小所以都满足。于是该结论容易推广到在一个连通块中,选择了度数最大的节点以及所有与其相邻的节点,该连通块一定合法,于是可以由此构造,每次找一个没染色的度数最大的,查询其所有节点,只要查到一个已经染色的,将该轮操作已获得的节点全部染上与其相同的颜色,若未能找到,将当轮所找到的菊花染成一个新的颜色,容易发现这样构造满足上面的性质,对于询问次数,每个节点被额外询问一次对应一个新的菊花中心,而菊花中心不会被询问,即每种颜色连通块总询问数为 \(n_c-1\) ,总询问数为 \(n-颜色数\) ,符合要求。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
typedef long long LL;
typedef unsigned long long ULL;
typedef pair < int, int > PII;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
//#define int LL
//#define lc p*2+1
//#define rc p*2+2
//#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
#pragma warning(disable :4996)
const double eps = 1e-8;
const LL mod = 998244353;
const LL MOD = 1000000007;
const int maxn = 5010;

int T, N, D[maxn], col[maxn], cnt = 0;

int ask(int u)
{
	int v;
	cout << "? " << u << endl;
	cin >> v;
	return v;
}

void solve()
{
	queue<int>que;
	while (true)
	{
		int u = 0, mx = -1;
		for (int i = 1; i <= N; i++)
		{
			if (col[i])
				continue;
			if (D[i] > mx)
				u = i, mx = D[i];
		}
		if (!u)
			break;
		que.push(u);
		bool flag = false;
		for (int i = 1; i <= D[u]; i++)
		{
			int v = ask(u);
			if (col[v])
			{
				flag = true;
				while (!que.empty())
					col[que.front()] = col[v], que.pop();
				break;
			}
			que.push(v);
		}
		if (!flag)
		{
			++cnt;
			while (!que.empty())
				col[que.front()] = cnt, que.pop();
		}
	}
	cout << "! ";
	for (int i = 1; i <= N; i++)
		cout << col[i] << ' ';
	cout << endl;
}

int main()
{
	IOS;
	cin >> T;
	while (T--)
	{
		cin >> N, cnt = 0;
		for (int i = 1; i <= N; i++)
			cin >> D[i], col[i] = 0;
		solve();
	}
	return 0;
}

1422D. Returning Home

\(\texttt{*2300}\)

题意

一个 \(n\times n(1\le n\le10^9)\) 的网格,有 \(m(1\le m\le10^5)\) 个传送门,每次可以花一秒走一步(上下左右四个方向),如果走到根某个传送门同一列,或者同一行,可以立即免费走到该传送门,从起点 \((sx,sy)\) 开始,走到终点 \((fx,fy)\) ,求最小花费。

题解

简单想法是直接建图跑最短路,但显然不行,建图要 \(m^2\) 。考虑优化掉一些边,考虑按 \(x\) 轴排序后,如果两个点不相邻,那么容易发现走中转点一定不劣,于是只连相邻的边即可,对于按 \(y\) 轴排序也一样处理,最后传送门向终点,起点向各传送门连边,注意起点要向终点直接连边,不然 wa5 ,这样边数仅有 \(2m\) ,直接做即可。

代码

view code
#include<bits/stdc++.h>
#include<unordered_map>
#include<unordered_set>
using namespace std;
using LL = long long;
using LD = long double;
using ULL = unsigned long long;
using PII = pair<LL, LL>;
using TP = tuple<int, int, int>;
#define all(x) x.begin(),x.end()
#define mst(x,v) memset(x,v,sizeof(x))
#define mul(x,y) (1ll*(x)*(y)%mod)
#define mk make_pair
//#define int LL
//#define double LD
//#define lc p*2
//#define rc p*2+1
#define endl '\n'
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
#pragma warning(disable : 4996)
#define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0)
const double eps = 1e-8;
const double pi = acos(-1);
const LL MOD = 1000000007;
const LL mod = 998244353;
const int maxn = 100010;

LL N, M, sx, sy, fx, fy, d[maxn];
struct Point {
	LL x, y, id;
}P[maxn];
struct Edge {
	LL to, cost;
};

bool cmpx(const Point& a, const Point& b)
{
	if (a.x == b.x)
		return a.y < b.y;
	return a.x < b.x;
}

bool cmpy(const Point& a, const Point& b)
{
	if (a.y == b.y)
		return a.x < b.x;
	return a.y < b.y;
}

vector<Edge>G[maxn];

void add_edge(int from, int to, LL cost)
{
	G[from].push_back(Edge{ to,cost });
}

void dijkstra(int S)
{
	mst(d, INF), d[S] = 0;
	priority_queue<PII, vector<PII>, greater<PII>>que;
	que.push(PII(0, S));
	while (!que.empty())
	{
		PII p = que.top(); que.pop();
		int v = p.second;
		if (p.first > d[v])
			continue;
		for (auto& e : G[v])
		{
			if (d[e.to] > d[v] + e.cost)
			{
				d[e.to] = d[v] + e.cost;
				que.push(PII(d[e.to], e.to));
			}
		}
	}
}

void solve()
{
	int S = M + 1, T = S + 1;
	sort(P + 1, P + M + 1, cmpx);
	for (int i = 2; i <= M; i++)
	{
		add_edge(P[i].id, P[i - 1].id, min(abs(P[i].y - P[i - 1].y), abs(P[i].x - P[i - 1].x)));
		add_edge(P[i - 1].id, P[i].id, min(abs(P[i].y - P[i - 1].y), abs(P[i].x - P[i - 1].x)));
	}
	sort(P + 1, P + M + 1, cmpy);
	for (int i = 2; i <= M; i++)
	{
		add_edge(P[i].id, P[i - 1].id, min(abs(P[i].y - P[i - 1].y), abs(P[i].x - P[i - 1].x)));
		add_edge(P[i - 1].id, P[i].id, min(abs(P[i].y - P[i - 1].y), abs(P[i].x - P[i - 1].x)));
	}
	for (int i = 1; i <= M; i++)
	{
		add_edge(S, P[i].id, min(abs(P[i].x - sx), abs(P[i].y - sy)));
		add_edge(P[i].id, T, abs(P[i].x - fx) + abs(P[i].y - fy));
	}
	add_edge(S, T, abs(sx - fx) + abs(sy - fy));
	dijkstra(S);
	cout << d[T] << endl;
}

int main()
{
	IOS;
	cin >> N >> M >> sx >> sy >> fx >> fy;
	for (int i = 1; i <= M; i++)
		cin >> P[i].x >> P[i].y, P[i].id = i;
	solve();
	return 0;
}
posted @ 2022-10-28 16:27  Prgl  阅读(35)  评论(0)    收藏  举报