Codeforces Round #826 (Div
Codeforces Round #826 (Div. 3)
Minimize the Thickness
给定数组a,要求将数组a分成若干个子序列,并且使得每个子序列中的元素和都相等,设这些子序列中最长的子序列的长度为\(len\),请你求出\(len\)的最小值
\(1<=n<=2000\)
题解:暴力+模拟
看到数据范围想到可以直接暴力,len最大就是整个数组a的长度,我们假设第一个序列的左端点为1,然后不断往右枚举右端点,那么在右端点固定后,我们从右端点往右遍历,如果下一段序列的序列和同第一个序列的序列和相同,我们再次固定第二个区间的右端点,然后从第二个区间的右端点出发,找第三个区间的右端点,如果说最后一个区间的右端点为n,说明我们找到了一个完整的若干个序列,他们的区间和都是一样的,我们将其所有序列的长度取最大值和答案\(ans\)进行比较即可
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e3 + 10, M = 4e5 + 10;
int n;
int a[N];
int pre[N];
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i];
for (int i = 1; i <= n; ++i)
pre[i] = pre[i - 1] + a[i];
int ans = n;
for (int i = 1; i <= n; ++i)
{
int res = i;
int sum = pre[i];
int pos = i;
for (int j = i + 1; j <= n; ++j)
{
if (pre[j] - pre[pos] == sum)
{
res = max(res, j - pos);
pos = j;
}
}
if (pos == n)
{
ans = min(ans, res);
}
}
cout << ans << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
cin >> T;
while (T--)
{
solve();
}
return 0;
}
Masha and a Beautiful Tree
给你一颗二叉树叶子节点的值,并且这些值形成排列,你可以进行这样的操作:
设\(u\)节点不是叶子节点,可以交换u节点的左右儿子,操作次数不限
最后你需要使得叶子节点所形成的排列是一个升序序列,请问在操作后能否使得该排列升序?
题解:分治思想--归并排序
根据题目大意发现和归并排序非常像,我们只需要更改一下合并左右区间的代码并在其中记录交换次数即可
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 3e5 + 10, M = 4e5 + 10;
int n;
int a[N], b[N];
int ans;
void merge_sort(int l, int r)
{
if (l < r)
{
int mid = l + r >> 1;
int mid1 = (r - l + 1) >> 1;
merge_sort(l, mid);
merge_sort(mid + 1, r);
if (a[l] > a[r])
{
ans++;
for (int i = l; i <= mid; ++i)
swap(a[i], a[i + mid1]); // 注意加上的是区间长度的一半
}
}
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
b[i] = a[i];
}
sort(b + 1, b + n + 1);
ans = 0;
merge_sort(1, n);
for (int i = 1; i <= n; ++i)
{
if (a[i] != b[i])
{
cout << -1 << endl;
return;
}
}
cout << ans << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
cin >> T;
while (T--)
{
solve();
}
return 0;
}
Sending a Sequence Over the Network
对于数组a,可以将其任意划分成若干段,对于每一段可以选择在其左侧或者右侧添加一个数字,这个数字就是这段长度,新构成的数组称为数组b,现在给出一个长度为n的数组b,问是否存在一个数组a能通过上述操作生成它。
题解:线性DP :好题目
对于数组\(b\)的前\(i\)个元素来说,对于最后结尾的第\(i\)个元素来说只有两种情况:
- 代表左边区间的长度
- 代表右边区间的长度
状态表示:\(f[i]\):代表前\(i\)个位置是否能够还原出一个数组\(a\)
状态属性:合法性
状态转移:
- 代表左边区间的长度:如果\(i-a[i]-1>=0\ \&\& \ f[i-a[i]-1]==true\),那么\(f[i]=true\),注意\(i-a[i]-1\)的位置是不属于左边区间左边的第一个元素
- 代表右边区间的长度:如果\(i+a[i]<=n\ \ \&\& \ \ f[i-1]==true\),那么\(f[i+a[i]]==true\),也就是说\(i-1\)位置是上区间的最后一个元素,只要上个位置是合法的,那么\(i\)位置代表右边区间的长度,所以我们可以直接预处理出后面某个位置是合法的
状态初始:\(f[0] = 1,其余f[i] = 0\)
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;
int n;
int f[N];
int a[N];
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
cin >> a[i], f[i] = 0;
f[0] = 1;
for (int i = 1; i <= n; ++i)
{
if (i - a[i] - 1 >= 0 && f[i - a[i] - 1])
f[i] = 1;
if (i + a[i] <= n && f[i - 1])
f[i + a[i]] = 1;
}
if (f[n])
cout << "YES" << endl;
else
cout << "NO" << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
cin >> T;
while (T--)
{
solve();
}
return 0;
}
Multi-Colored Segments
给定n个线段,每个线段都有颜色,我们需要对于每个线段需要求出距离它最近的不同颜色线段之间的距离,如果两个线段重合,那么他们之间的最短距离为0
题解:权值线段树+离散化+二分+STL:\(O(nlogn)\)
对于每一个线段来说我们可以利用\(multiset\)分别记录左端点和右端点,同时我们还应该根据颜色对线段进行分类,那么如果我们对任意一个线段求出距离它最近的不同颜色线段之间的距离,我们可以将所有和它同一颜色的线段在\(multiset\)中全部删除,然后利用\(std::lower\_bound\)二分找到最近的左右端点,对其情况进行分类讨论模拟一下即可得出,不再赘述;但是如果说线段之间存在重合,那么分类讨论的情况会比较复杂,所以我们考虑如何快速知道两个线段有没有重合,我们考虑利用线段树
注意:我们发现一个线段的左右端点太大了,线段树开不了\(1e9\),所以我们需要对左右端点进行离散化然后再建树
我们来谈谈线段树如何解决线段重合问题:
- 我们可以利用线段树维护一个区间最大值
- 对于某个线段来说,我们可以对其这个区间进行区间加,都加1
- 那么我们在查询的时候,我们可以通过线段树将这些区间都减1,如果说查询的当前的区间的最大值大于1,说明该线段存在与他重叠的颜色不同的线段
- 我们一定是一个一个颜色的离线查询,所以我们对一个颜色查询的时候我们可以先将其所有线段删除,然后查询完毕后我们再将这个颜色的线段添加回来
注意:对于多组数据,我们每次需要清空所有点上的\(lazy\)标记,否则会对下一次造成影响
同时我们需要知道\(std::prev\)函数的语法:找到该迭代器的前一个迭代器
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 2e5 + 10, M = 4e5 + 10;
int n;
multiset<int> L, R;
struct info
{
int maxx;
};
struct node
{
int lazy;
info val;
} seg[N << 3];
vector<array<int, 3>> qid[N];
vector<pii> range;
info operator+(const info &a, const info &b)
{
info c;
c.maxx = max(a.maxx, b.maxx);
return c;
}
void settag(int id, int tag)
{
seg[id].val.maxx += tag;
seg[id].lazy += tag;
}
void up(int id)
{
seg[id].val = seg[id << 1].val + seg[id << 1 | 1].val;
}
void down(int id)
{
if (seg[id].lazy == 0)
return;
settag(id << 1, seg[id].lazy);
settag(id << 1 | 1, seg[id].lazy);
seg[id].lazy = 0;
}
void build(int id, int l, int r)
{
if (l == r)
{
seg[id].val.maxx = 0;
seg[id].lazy = 0;
return;
}
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
up(id);
}
void modify(int id, int l, int r, int ql, int qr, int val)
{
if (ql <= l && r <= qr)
{
settag(id, val);
return;
}
down(id);
int mid = (l + r) >> 1;
if (qr <= mid)
modify(id << 1, l, mid, ql, qr, val);
else if (ql > mid)
modify(id << 1 | 1, mid + 1, r, ql, qr, val);
else
{
modify(id << 1, l, mid, ql, qr, val);
modify(id << 1 | 1, mid + 1, r, ql, qr, val);
}
up(id);
}
info query(int id, int l, int r, int ql, int qr)
{
if (ql <= l && r <= qr)
{
return seg[id].val;
}
down(id);
int mid = (l + r) >> 1;
if (qr <= mid)
return query(id << 1, l, mid, ql, qr);
else if (ql > mid)
return query(id << 1 | 1, mid + 1, r, ql, qr);
else
return query(id << 1, l, mid, ql, qr) + query(id << 1 | 1, mid + 1, r, ql, qr);
}
void solve()
{
cin >> n;
for (int i = 1; i <= n; ++i)
qid[i].clear();
for (int i = 1; i <= 4 * n + 10; ++i)
seg[i].lazy = seg[i].val.maxx = 0;
vector<int> ans(n + 10, inf);
vector<int> a;
range.clear();
L.clear();
R.clear();
for (int i = 1; i <= n; ++i)
{
int l, r, col;
cin >> l >> r >> col;
qid[col].push_back({l, r, i});
L.insert(l);
R.insert(r);
range.push_back({l, r});
a.push_back(l);
a.push_back(r);
}
sort(all(a));
a.erase(unique(all(a)), a.end());
int num = a.size();
build(1, 1, num);
for (auto &[l, r] : range)
{
l = lower_bound(all(a), l) - a.begin() + 1;
r = lower_bound(all(a), r) - a.begin() + 1;
modify(1, 1, num, l, r, 1);
}
for (int i = 1; i <= n; ++i)
{
for (auto &[l, r, id] : qid[i])
{
modify(1, 1, num, range[id - 1].first, range[id - 1].second, -1);
L.erase(L.lower_bound(l));
R.erase(R.lower_bound(r));
}
for (auto &[l, r, id] : qid[i])
{
if (query(1, 1, num, range[id - 1].first, range[id - 1].second).maxx)
{
ans[id] = 0;
continue;
}
auto it = L.lower_bound(l);
if (it != L.end())
{
ans[id] = min(ans[id], max(0ll, *it - r));
}
else
{
it = prev(it);
ans[id] = min(ans[id], max(0ll, l - *it));
}
it = R.lower_bound(r);
if (it != R.end())
{
if (it == R.begin())
ans[id] = min(ans[id], max(0ll, *it - r));
else
{
it = prev(it);
ans[id] = min(ans[id], max(0ll, l - *it));
}
}
else
{
it = prev(it);
ans[id] = min(ans[id], max(0ll, l - *it));
}
}
for (auto &[l, r, id] : qid[i])
{
modify(1, 1, num, range[id - 1].first, range[id - 1].second, 1);
L.insert(l);
R.insert(r);
}
}
for (int i = 1; i <= n; ++i)
cout << ans[i] << " ";
cout << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
cin >> T;
while (T--)
{
solve();
}
return 0;
}
Kirill and Company
给定n个节点和m条边的无向图,初始有f个人在节点1,他们每个人都要从节点1返回自己的家,且都要走最短路径,其中这f个人中有k(\(1<=k<=6\))人没有车,其他人都有车,没有车的人可以搭别人的顺风车回家,通过搭车安排使得k个人中无法搭车只能走回家的人数最少,求出最少的人数
题解:分组背包+状压DP+最短路
首先只有k个人没有车,且k<=6,我们可以将所有搭便车的情况利用二进制压缩,例如:1110,说明第一个人不带,然后带第2.3.4个人;那么每一个有车的人要在所有的方案中选择一种合法的带人方案,最多带k个人,即:\(11...11\),也就是说我们要在每一个组中任选一种方案,使得在不超过背包容量的情况下价值最大,这里的背包容量为\(11...11\)(最多带k个人),显然是一个分组背包的问题;
我们知道分组背包的状态转移方程为:\(f[i] = f[i-v[i]]+w[i]\),倒序遍历背包
但是这边我们该怎么办?
首先我们先解决几个简单的问题:
- 假设某个带人方案为\(1110\),那我们如何确定带的人具体是谁?实际上我们只要将没有车的人按照距离节点1的最短距离排序即可,因为我们肯定带的人肯定是从距离最近开始,我们不可能先把距离远的送回家然后再回来送距离近的,我们应该是送距离远的人的半路上将距离短的人送到家
- 我们如何确定对于任意一个有车的人来说,某个带人方案是一个合法方案,即能把该方案中的所有搭便车的人送回家,我们只需要求出每个没有车的人的家距离其他节点的最短距离,加上在节点1求的最短距离,我们最多求7次\(bfs\),可以接受,那么在我们知道距离后,假设有车的人的家为\(v\),只要从起点开始到每个搭便车人的家的所有距离加起来等于从起点到v的距离,就说明这几个人都能搭便车被送到家
现在我们来解决\(dp\)的问题:
状态表示:\(f[i]=0/1\):表示把二进制为\(i\)状态下的所有二进制位为1的人搭便车送回家的可行性
状态属性:可行性
状态转移:每个有车的人对其所有合法的带人方案进行分组背包,那么\(f[i-v[i]]\)怎么实现呢?实际上原本的意思是将背包空出来\(v[i]\)部分用来装新的物品,那么我们这边只需要将背包中的人放下来几个,用来装新的人即可,那实际上异或就能使得双方都有1的部分为0,即\(f[i] |= f[i\bigoplus j]\),滚动数组实现,注意倒序遍历背包
状态初始:\(f[0]=true,其余false\)
如何得出答案:
我们只需要在所有合法的二进制状态下(\(f[i]=true\))找到二进制中1最多的方案即可,可以使用\(\_\_builtin\_popcount()\)函数
#include <bits/stdc++.h>
#define Zeoy std::ios::sync_with_stdio(false), std::cin.tie(0), std::cout.tie(0)
#define debug(x) cerr << #x << '=' << x << endl
#define all(x) (x).begin(), (x).end()
#define rson id << 1 | 1
#define lson id << 1
#define int long long
#define mpk make_pair
#define endl '\n'
using namespace std;
typedef unsigned long long ULL;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<ll, ll> pll;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;
const double eps = 1e-9;
const int N = 1e4 + 10, M = 4e5 + 10;
int n, m, k, num;
vector<int> g[N];
int h[N];
int f[N];
int dis[10][N];
void bfs(int st, int k)
{
for (int i = 1; i <= n; ++i)
dis[k][i] = 0;
vector<bool> vis(n + 10, 0);
queue<int> q;
q.push(st);
dis[k][st] = 0;
vis[st] = 1;
while (q.size())
{
int u = q.front();
q.pop();
for (auto v : g[u])
{
if (!vis[v])
{
vis[v] = 1;
dis[k][v] = dis[k][u] + 1;
q.push(v);
}
}
}
}
bool cmp(int x, int y)
{
return dis[0][x] < dis[0][y];
}
void solve()
{
cin >> n >> m;
memset(f, 0, sizeof f);
for (int i = 1; i <= n; ++i)
g[i].clear();
for (int i = 1, u, v; i <= m; ++i)
{
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
bfs(1, 0);
cin >> num;
vector<bool> car(num + 10, 1); // 代表这个人有没有车
for (int i = 1; i <= num; ++i)
cin >> h[i];
cin >> k;
vector<int> vec;
for (int i = 1; i <= k; ++i)
{
int p;
cin >> p;
car[p] = false;
vec.push_back(h[p]);
}
sort(all(vec), cmp); //规定状态压缩中的每一1代表谁
int idx = 1;
for (auto v : vec) //最多6次的bfs
{
bfs(v, idx);
idx++;
}
f[0] = 1;
for (int i = 1; i <= num; ++i) // 遍历每个有车的人
{
if (car[i] == false) //没车跳过
continue;
vector<int> goods; //存放合法的方案
for (int j = 1; j <= (1 << k) - 1; ++j)
{
vector<int> a;
for (int pos = 0; pos <= k; ++pos)
{
if ((j >> pos) & 1) //找到1的位置,从而知道每个1代表谁
a.push_back(pos);
}
int sum = dis[0][h[i]]; //模拟是否为合法方案的过程
int sz = a.size();
sum -= dis[0][vec[a[0]]];
for (int pos = 1; pos < sz; ++pos)
{
sum -= dis[a[pos - 1] + 1][vec[a[pos]]];
}
sum -= dis[a.back() + 1][h[i]];
if (sum == 0) //合法
goods.push_back(j);
}
for (int j = (1 << k) - 1; j >= 1; --j) //倒序遍历背包
for (auto it : goods)
if (it <= j)
f[j] |= f[j ^ it]; //分组背包
}
int ans = 0;
for (int i = 1; i <= (1 << k) - 1; ++i)
{
if (f[i])
ans = max(ans, 1ll * __builtin_popcount(i));
}
cout << k - ans << endl;
}
signed main(void)
{
Zeoy;
int T = 1;
cin >> T;
while (T--)
{
solve();
}
return 0;
}