比赛链接:
https://ac.nowcoder.com/acm/contest/24872
D.Strange_Fractions
题目大意:
给一个分数 \(\frac{p}{q}\),找出 a,b,使 \(\frac{p}{q} = \frac{a}{b} + \frac{b}{a}\),若没有,则输出 "0 0".
思路:
首先 \(\frac{p}{q}\) 这个分数需要先约分。
然后,化简一下等式 \(\frac{p}{q} = \frac{a^2 + b^2}{a * b}\),可以得到第一个限制,\(p >= 2 * q\)。
接着化简,容易得到 \(a = \frac{\sqrt[]{p + 2 * q} + \sqrt[]{p - 2 * q}}{2}\),\(b = \frac{\sqrt[]{p + 2 * q} - \sqrt[]{p - 2 * q}}{2}\),这里需要判断一下 \(p + 2 * q\) 和 \(p - 2 * q\) 是不是完全平方数,因为答案要是有理的,这是第二个限制。
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int T, p, q;
void solve(){
cin >> p >> q;
int g = __gcd(p, q);
p /= g;
q /= g;
if (p < 2 * q) cout << "0 0\n";
else{
int m = sqrt(p + 2 * q), n = sqrt(p - 2 * q);
if (m * m != (p + 2 * q) || n * n != (p - 2 * q)) cout << "0 0\n";
else cout << (m + n) / 2 << " " << (m - n) / 2 << "\n";
}
}
int main(){
IOS();
cin >> T;
while (T--)
solve();
return 0;
}
E.Strange_Integers
题目大意:
给了长为 \(n\) 的序列 \(a\) 和一个 \(k\),找出一个最长的序列,使 \(\lvert a_i - a_j \rvert >= k(1 <= i < j <= n)\)。
思路:
将输入的序列排序,然后贪心找最长即可。
代码:
#include <bits/stdc++.h>
using namespace std;
#define IOS() ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
int n, k;
int main(){
IOS();
cin >> n >> k;
vector <int> a(n);
for (int i = 0; i < n; ++ i)
cin >> a[i];
sort(a.begin(), a.end());
int p = 0, ans = 1;
for (int i = 1; i < n; ++ i)
if (a[i] - a[p] >= k){
p = i;
ans++;
}
cout << ans << "\n";
return 0;
}
G.Edge Groups
题意:
给定一棵树,求出将整棵树拆成长度为 2 的路径的方案数。
思路:
树形 \(dp\),定义 \(f[i]\) 为以 \(i\) 为根的子树的划分方案数。
对于每一个节点 \(u\)。
如果它的一个以根为 \(v\) 的子树的边的数量为奇数,即需要 \(u - v\) 这条边才能将整棵子树拆成长度为 2 的路径。
设子树的边加上 \(u - v\) 这条边,总共有 \(n\) 条边。
方案数为 \(\frac{C_n^2 * C_{n - 2}^2 * ... * C_2^2}{\frac{n}{2}!} = \frac{n!}{\frac{n}{2}! * 2^{\frac{n}{2}}} = 1 * 3 * ... * (n - 3) * (n - 1)\)(因为 \(n\) 是偶数)。
所以 \(f[u]\) = \(f[v]\) * 上述结果。
否则,不需要 \(u - v\) 就能将整棵子树拆分,所以 \(f[u]\) 直接乘上 \(f[v]\) 就行。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 1e5 + 10, mod = 998244353;
LL n, f[N];
vector <LL> g[N];
bool dfs(LL u, LL fa){
f[u] = 1;
LL cnt = 0;
for (auto v : g[u]){
if (v == fa) continue;
if ( !dfs(v, u) ) cnt ++ ;
f[u] = f[u] * f[v] % mod;
}
for (int i = 1; i <= cnt; i += 2)
f[u] = f[u] * i % mod;
return cnt & 1;
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
cin >> n;
for (int i = 1; i < n; i ++ ){
LL u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
cout << f[1] << "\n";
return 0;
}
I.Steadily Growing Steam
题目大意:
有 \(n\) 件物品,第 \(i\) 件物品的体积是 \(t_i\),价值是 \(v_i\),最多选出 \(k\) 件使它们的体积翻倍,然后选出若干件物品将其分为 体积和 相同的两堆,求选出物品的价值之和最大值
思路:
从 \(n\) 件物品中选出若干件物品,可以想到 背包。
由于要分成两堆,我们可以假定,放入 第一堆 中是加入背包,放入 第二堆 中是拿出背包,也就是说放入第一堆中是 增加体积,放入第二堆中 减少体积,不论放入哪一堆,价值都是增加的。
dp[i][j][p]为 \(dp\) 方程,\(i\) 表示当前选择的是第几件物品, \(j\) 表示选中几件物品使其 体积翻倍,\(p\) 为 背包体积,就是一个简单背包啦。
可以发现,在求解的过程中,每一个物品只需要从上一个物品的状态转移过来,于是我们可以通过 滚动数组 优化掉一个维度。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 105;
const int W = 1310;
LL n, k, w[N], v[N], f[N][W * 2], g[N][W * 2], ans = -(1LL << 60);
int main(){
scanf("%lld %lld", &n, &k);
for (int i = 1; i <= n; i++)
scanf("%lld %lld", &v[i], &w[i]);
memset(f, 233, sizeof(f));
f[0][W] = 0;
for (int i = 1; i <= n; i++){
memcpy(g, f, sizeof(g));
for (int j = 0; j < i && j <= k; j++)
for (int p = 0; p < W * 2; p++){
if (p > w[i]) g[j][p - w[i]] = max(g[j][p - w[i]], f[j][p] + v[i]);
if (p > 2 * w[i]) g[j][p - 2 * w[i]] = max(g[j][p - 2 * w[i]], f[j][p] + v[i]);
if (p + w[i] < W * 2) g[j][p + w[i]] = max(g[j][p + w[i]], f[j][p] + v[i]);
if (p + 2 * w[i] < W * 2) g[j][p + 2 * w[i]] = max(g[j][p + 2 * w[i]], f[j][p] + v[i]);
}
memcpy(f, g, sizeof(f));
}
for (int i = 0; i <= k; i++)
ans = max(ans, f[i][W]);
cout << ans << "\n";
return 0;
}