2022“杭电杯”中国大学生算法设计超级联赛(8)
[题目链接](Search Result (hdu.edu.cn))
D Quel’Thalas
题目大意
在二维平面上,[0,n]*[0,n]的整数点中去掉(0,0)之后,最少需要多少条直线使得每个整数点至少有一条边覆盖。
题解
易得平行于x轴的n条直线和平行于y轴的n条直线即可满足上述条件,且此为最少的直线数量。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
int t, n;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> n;
cout << 2 * n << endl;
}
return 0;
}
A Theramore
题目大意
有一个01序列,可以任意翻转奇数长度的区间,求能达到的最小字典序。
题解
使用长度为 3 的翻转,那么位置奇偶性相同的位置可以随便换。对奇数位置和偶数位置,分别把 0 放到前面,1 放到后面即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 5e5 + 5;
int t, n, id1, id2;
string s;
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> s;
n = s.size();
vector<char> v1, v2;
id1 = id2 = 0;
for (int i = 0; i < n; i++)
{
if (i % 2)
v1.push_back(s[i]);
else
v2.push_back(s[i]);
}
sort(v1.begin(), v1.end());
sort(v2.begin(), v2.end());
for (int i = 0; i < n; i++)
{
if (i % 2)
cout << v1[id1++];
else
cout << v2[id2++];
}
cout << endl;
}
return 0;
}
K Stormwind
题目大意
一个n*m的长方形,可以沿水平或竖直方向画若干条线,每条线的两端点都在长方形的边界上。要求
这些线划分出的每个小长方形面积都>=k,求最多可以画几条线。
题解
二分。
checked函数:因为每一个小长方形的面积都需要大于等于k,所以划分的时候应该尽量平均划分,所以只需要求最小的长方形的面积是否大于等于k即可,而最小的长方形的边长就是n / (i + 1),即长度除以划分的线的数量。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll maxn = 1e2 + 5;
const ll mod = 1e9 + 7;
ll t, n, m, k;
bool checked(ll mid)
{
ll x, y, j;
for (ll i = 0; i <= mid; i++)
{
j = mid - i;
x = n / (i + 1), y = m / (j + 1);
if (x * y >= k)
return 1;
}
return 0;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> n >> m >> k;
ll l = 0, r = n + m - 2, mid, ans;
while (l <= r)
{
mid = (l + r) / 2;
if (checked(mid))
{
ans = mid;
l = mid + 1;
}
else
r = mid - 1;
}
cout << ans << endl;
}
return 0;
}
H Orgrimmar
题目大意
求一棵树的最大分离集的大小,分离集中每个点至多有一条边与分离集中另一个点相连。
题解
树形dp。
定义dp数组中,父节点u及其子节点v的状态:dp[u] [0]表示u不选择;dp[u] [1]表示u选择,v不选择;dp[u] [2]表示u选择,且v选择。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5 + 5;
const int Max = 0x3f3f3f3f;
int t, n;
vector<int> g[maxn];
int dp[maxn][5];
void dfs(int u, int fa)
{
int x0 = 0, x1 = 0, x2 = -Max;
for (auto v : g[u])
{
if (v == fa)
continue;
dfs(v, u);
x0 += max({dp[v][0], dp[v][1], dp[v][2]});
x1 += dp[v][0];
x2 = max(x2, dp[v][1] - dp[v][0]);
}
dp[u][0] = x0;
dp[u][1] = x1 + 1;
dp[u][2] = x2 + x1 + 1;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int size(512 << 20);
__asm__("movq %0, %%rsp\n" ::"r"((char *)malloc(size) + size));
cin >> t;
while (t--)
{
cin >> n;
for (int i = 1; i <= n; i++)
g[i].clear();
for (int i = 1; i < n; i++)
{
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
cout << max({dp[1][0], dp[1][1], dp[1][2]}) << endl;
}
exit(0);
}
E Ironforge
题目大意
一条链,每个点上有一个数ai,每条边上有一个质数bi。一开始在某个点上,有一个空背包,走到一个
点上可以把它的质因子放进背包,一条边如果背包里有那个质数就可以走。多组询问求从 x 出发能否走
到 y。
题解
[大佬链接](2022 杭电多校(8) 个人题解 更新至6题 - 知乎 (zhihu.com))
讲的超级清楚!!!太牛了!!!
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5;
int t, n, m;
int a[maxn], b[maxn];
int l[maxn], r[maxn], minp[maxn];
vector<int> pos[maxn];
void init()
{
for (int i = 1; i <= 200000; i++)
pos[i].clear();
for (int i = 1; i <= n; i++)
{
while (a[i] > 1)
{
int p = minp[a[i]];
while (a[i] % p == 0)
a[i] /= p;
pos[p].push_back(i);
}
}
}
bool checked(int p, int l, int r)
{
if (pos[p].size() == 0)
return 0;
return upper_bound(pos[p].begin(), pos[p].end(), r) - lower_bound(pos[p].begin(), pos[p].end(), l);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
for (int i = 2; i <= 200000; i++)
{
if (minp[i])
continue;
for (int j = i; j <= 200000; j += i)
if (!minp[j])
minp[j] = i;
}
cin >> t;
while (t--)
{
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i < n; i++)
cin >> b[i];
init();
for (int i = n; i >= 1; i--)
{
r[i] = i;
while (r[i] < n && checked(b[r[i]], i, r[i]))
{
r[i] = r[r[i] + 1];
}
}
for (int i = 1; i <= n; i++)
{
l[i] = i;
if (i > 1 && r[i - 1] >= i)
{
if (checked(b[i - 1], i, r[i]))
{
l[i] = l[i - 1];
r[i] = r[i - 1];
}
}
else
{
bool flag = 1;
while (flag)
{
flag = 0;
while (l[i] > 1 && checked(b[l[i] - 1], l[i], r[i]))
{
flag = 1;
l[i] = l[l[i] - 1];
}
while (r[i] < n && checked(b[r[i]], l[i], r[i]))
{
flag = 1;
r[i] = r[r[i] + 1];
}
}
}
}
for (int i = 1; i <= m; i++)
{
int x, y;
cin >> x >> y;
if (y >= l[x] && y <= r[x])
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}
return 0;
}
G Darnassus
题目大意
给出一个排列p,把每个位置视为点,建一个无向图,i,j之间的边权为|i-j|*|pi-pj|。求这个图的最小生成树。
题解
依次连接i和i+1,每条边的边权等于|p[i+1]-p[i]|<=n-1(因为p数组是排列),且有n-1条,因此最小生成树中的每条边的边权一定<=n-1。
边权为|i-j|*|pi-pj|,意味着|i-j|和|pi-pj|必有至少一个<=sqrt(n-1),因此需要找到所有这样的边,然后使用Kruskal 算法求出最小生成树即可。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 5e4 + 5;
const int maxm = 5e7 + 5;
int t, n, cnt, num, ans;
ll res;
int a[maxn], pos[maxn], head[maxn];
int f[maxn], r[maxn];
struct Edge
{
int u, v, next;
} e[maxm];
void init()
{
cnt = res = ans = 0;
num = sqrt(n);
memset(head, -1, sizeof head);
memset(r, 0, sizeof r);
for (int i = 1; i <= n; i++)
f[i] = i;
}
int find(int i)
{
if (f[i] == i)
return i;
return f[i] = find(f[i]);
}
void unionn(int i, int j)
{
int i_fa = find(i);
int j_fa = find(j);
if (i_fa != j_fa)
{
if (r[i_fa] < r[j_fa])
swap(i_fa, j_fa);
f[j_fa] = i_fa;
if (r[i_fa] == r[j_fa])
r[i_fa]++;
}
}
void add(int u, int v, int w)
{
e[cnt].u = u;
e[cnt].v = v;
e[cnt].next = head[w];
head[w] = cnt++;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> t;
while (t--)
{
cin >> n;
init();
for (int i = 1; i <= n; i++)
{
cin >> a[i];
pos[a[i]] = i;
}
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n && j <= i + num; j++)
{
int w = abs(i - j) * abs(a[i] - a[j]);
if (w < n)
add(i, j, w);
}
}
for (int i = 1; i <= n; i++)
{
for (int j = i + 1; j <= n && j <= i + num; j++)
{
int w = abs(i - j) * abs(pos[i] - pos[j]);
if (w < n)
add(pos[i], pos[j], w);
}
}
for (int i = 0; i <= n - 1; i++)
{
for (int j = head[i]; j != -1; j = e[j].next)
{
int u = e[j].u, v = e[j].v;
if (find(u) == find(v))
continue;
unionn(u, v);
res += i;
if (++ans == n - 1)
break;
}
if (ans == n - 1)
break;
}
cout << res << endl;
}
return 0;
}