PS:实际打了2两个小时左右,被叫走去干活了。所以就直接补题了
A
| B
| C
| D
| E
| F
| G
| H
| I
| J
| K
| L
| M
|
–
| –
| 补
| AC
| AC
| –
| AC
| AC
| AC
| 补
| 补
| –
| 补
|
待补
题目分析
对于给定的,找到满足。找不到输出
令,则,故可得:,方程解为有理数时为有理数,则为完全平方数。那么对于是否有解只需判断即可。
Code
#include <bits/stdc++.h>
#define int long long
#define end '\n'
using namespace std;
inline void solve(){
int p, q; cin >> p >> q;
int res = pow(p * p - 4 * q * q, 0.5);
if(res * res == p * p - 4 * q * q){
int det = sqrt(p * p - 4 * q * q), a = 0, b = 2 * q;
if(det > p) a = p + det;
else a = p - det;
cout << a << ' ' << b << endl;
} else {
cout << "0 0\n";
}
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
int t = 0; cin >> t;
while(t--) solve();
return 0;
}
题目分析
要求从原集合中选择若干个数字组成新集合,使得新集合中的两两数字之差大于。
直接排序,然后贪心扫一遍即可。
Code
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e5 + 10;
int a[N];
inline void solve(){
int n, k; cin >> n >> k;
for(int i = 1; i <= n; i++) cin >> a[i];
sort(a + 1, a + 1 + n);
int pre = a[1], ans = 1;
for(int i = 2; i <= n; i++){
if(a[i] - pre >= k) ans++, pre = a[i];
}
cout << ans << endl;
}
signed main(){
solve();
return 0;
}
题目分析
给定一棵树,要求对所有的边进行两两分组,每组满足必须两条边+两条边共顶点。
考虑对子树的答案进行统计:如果子节点的数目为偶数个,那么两两配对的方案数(定序)为:
化简上式可得:
如果子节点为奇数个,那么由于当前节点存在连向父节点的边,需要去除一条单独的边与连向父节点的边配对,因此此时连向父节点的边被占用。
Code
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N = 1e5 + 10, MOD = 998244353;
vector<int> g[N];
int n = 0;
int dp[N];
bool dfs(int u, int fa){
dp[u] = 1;
int cnt = 0;
for(auto v : g[u]){
if(v == fa) continue;
if(!dfs(v, u)) cnt++;
(dp[u] *= dp[v]) %= MOD;
}
for(int i = 1; i <= cnt; i += 2) (dp[u] *= i) %= MOD;
return cnt & 1;
}
inline void solve(){
cin >> n;
for(int i = 1; i < n; i++){
int u, v; cin >> u >> v;
g[u].emplace_back(v);
g[v].emplace_back(u);
}
dfs(1, -1);
cout << dp[1] << endl;
}
signed main(){
solve();
return 0;
}
题目分析
给定一张个点条边的无向图,以及个询问。对于每个询问,给定初始点和初始经验值,经过一条边要求当前经验值大于边权,经过一个点后点权累加至经验值。求能够获得的最大经验。
首先容易证明,对于最终的答案,两个点之间的所有简单路径上最大边权的最小值 = 最小生成树上两个点之间的简单路径上的最大值 = l 重构树上两点之间的
那么容易想到对原图建重构树,不妨先对样例建树:
红色数字表示重构树的点权,即为原图最大边权最小值。叶节点均为原图节点,黑色数字表示经验值,由于后期计算方便需要,将经验值自叶子节点向根节点求和。
那么我们对于某个询问,只需要从对应的叶节点开始往上跳到祖先节点,直至跳不过去(经验值小于点权(红色数字)),同时我们处理出了到达每个父节点能够获得的经验值(黑色数字),这样只需要一路往上跳检查是否满足经验值大于重构树的点权值就可以了。
Code
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N = 2e5 + 10;
int n, m, q;
int pnt[N], val[N];
struct unionfind{
int par[N];
unionfind(int n){ for(int i = 1; i <= n; i++) par[i] = i; }
int find(int x){ return x == par[x] ? x : (par[x] = find(par[x])); }
void merge(int u, int v){
int fau = find(u), fav = find(v);
if(fau != fav) par[fav] = fau;
}
int ismerge(int u, int v){ return find(u) == find(v); }
};
vector<int> g[N];
namespace KR{
struct edge{
int u, v ,w;
const bool operator< (const edge &x) const { return w < x.w; }
}edges[N];
inline void add_edge(int i, int u, int v, int w){ edges[i] = {u ,v, w}; }
int kruskal(){
int tot = n, cnt = 0;
unionfind uf(n + 20);
sort(edges + 1, edges + 1 + m);
for(int i = 1; i <= m; i++){
int nu = edges[i].u, nv = edges[i].v, nw = edges[i].w;
int fau = uf.find(nu), fav = uf.find(nv);
if(fau != fav){
val[++tot] = edges[i].w;
uf.par[tot] = uf.par[fau] = uf.par[fav] = tot;
g[fau].emplace_back(tot), g[fav].emplace_back(tot);
g[tot].emplace_back(fau), g[tot].emplace_back(fav);
cnt++;
}
if(cnt == n - 1) break;
}
return tot;
}
}
int fa[N][20];
void dfs0(int u, int ufa){
fa[u][0] = ufa;
for(int i = 1; i < 20; i++) fa[u][i] = fa[fa[u][i - 1]][i - 1];
for(auto v : g[u]){
if(v == ufa) continue;
dfs0(v, u);
pnt[u] += pnt[v];
}
}
using KR::add_edge;
using KR::kruskal;
inline void solve(){
cin >> n >> m >> q;
for(int i = 1; i <= n; i++) cin >> pnt[i];
for(int i = 1; i <= m; i++){
int u, v, w; cin >> u >> v >> w;
add_edge(i, u, v ,w);
}
n = kruskal();
dfs0(n, 0);
val[0] = 1e18;
while(q--){
int u, s; cin >> u >> s;
int ans = pnt[u] + s;
while(u != n){
int t = u;
for(int i = 19; i >= 0; i--)
if(val[fa[u][i]] <= ans) u = fa[u][i];
if(t == u) break;
ans = pnt[u] + s;
}
cout << ans << endl;
}
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
solve();
return 0;
}
题目分析
给定个物品以及其体积和价值,每次最多选择个物品使其体积翻倍,然后选出若⼲物品并将其分为体积和 相同的两堆,问选出的物品价值之和最⼤是多少。
设表示只考虑前张牌,还可以翻倍次,当前体积为的最大价值。
那么有转移方程:
第一维可以滚动数组优化一下。
Code
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define ull unsigned long long
using namespace std;
int n, m;
const int N = 1e5 + 5;
int a[N], v[N], t[N];
int dp[105][5300][105];
int st = 2600, inf = 1e18;
void solve(){
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> v[i] >> t[i];
for (int i = 0; i <= n; i++){
for (int j = 0; j <= 5200; j++){
for (int k = 0; k <= m; k++) dp[i][j][k] = -inf;
}
dp[i][st][0] = 0;
}
for (int i = 1; i <= n; i++){
for (int j = 0; j <= 5200; j++){
for (int k = 0; k <= m; k++){
dp[i][j][k] = dp[i - 1][j][k];
if (j >= t[i])
dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - t[i]][k] + v[i]);
if (j + t[i] <= 5200)
dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j + t[i]][k] + v[i]);
if (k && j >= 2 * t[i])
dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - 2 * t[i]][k - 1] + v[i]);
if (k && j + 2 * t[i] <= 5200)
dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j + 2 * t[i]][k - 1] + v[i]);
}
}
}
int ans = -inf;
for (int i = 0; i <= m; i++) ans = max(ans, dp[n][st][i]);
cout << ans;
}
signed main(){
ios::sync_with_stdio(false), cin.tie(0);
int t = 1;
while (t--) solve();
return 0;
}
题目分析
给定正整数,以及两个长度为的二进制串和。同时,定义函数:
如果满足对于任意,则为幸运数。对于每个中的数字判断是否为幸运数。
定义前缀和,那么表示的个数大于等于的个数。
首先按照前缀和大小进行排序,然后顺序遍历,我们可以发现,对于的情况一定不是(表示的个数大于等于的个数),那么就把前面的出现过的位置全部标记掉。
如果那么就是直接或起来标记,否则需要对取反后标记。另外注意对于长度以内的前缀也需要检验合法性,因此还要要维护一个合法范围标记。
Code
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N = 5e4 + 10;
int s[N], id[N];
bitset<N> A, ans, flip;
inline void solve(){
A.reset(), ans.reset(), flip.reset();
int n = 0; cin >> n;
string a, b; cin >> a >> b;
a = '@' + a, b = '@' + b;
for(int i = 1; i <= n; i++) flip.set(i);
id[0] = 0;
for(int i = 1; i <= n; i++){
s[i] = s[i - 1] + (a[i] == '1' ? 1 : -1);
id[i] = i;
}
sort(id, id + 1 + n, [](int x, int y){ return s[x] > s[y] || s[x] == s[y] && x < y; });
int tg = n + 1;
for(int i = 0; i <= n; i++){
int pos = id[i];
if(pos){
if(b[pos] == '1') {
ans = ans | (A >> (n - pos));
if(s[pos] <= 0) tg = min(tg, pos + 1);
} else {
ans = ans | ((A ^ flip) >> (n - pos));
if(s[pos] > 0) tg = min(tg, pos + 1);
}
}
A[n - pos] = 1;
}
for(int i = 1; i <= n; i++){
if(ans[i] || i >= tg) cout << 0;
else cout << 1;
}
cout << endl;
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
int t = 0; cin >> t;
while(t--) solve();
return 0;
}
题目分析
是给一个二进制串,每一次变换之后第个会分裂成两个向左右两侧跑,如果两个分裂的撞在一起或碰到边界会消失,要求构造满足题意的串,使得在次循环以内变出原始串。
由于相撞可以抵消,那么考虑是否存在循环节能够使原串被割裂为若干个独立块,从而各块独立循环。可以得到最小的循环节为,那么对于构造的串而言,只需要单独对剩余部分进行讨论就可以了。
Code
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const string biao[5] = {"1001", "10001", "100110", "1001010"};
inline void solve(){
int n = 0; cin >> n;
if(n == 2) cout << "10\n";
else if(n == 3) cout << "Unlucky\n";
else if(n == 4) cout << "1000\n";
else if(n <= 7) cout << biao[n - 4] << endl;
else{
for(int i = 1; i <= ((n - 4) / 4); i++) cout << biao[0];
cout << biao[(n - 4) % 4] << endl;
}
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
solve();
return 0;
}
题目分析
非常结论的题…
分析待补
Code
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const string biao[5] = {"1001", "10001", "100110", "1001010"};
inline void solve(){
double n = 0; cin >> n;
cout << 1 / (n * ((int)((n + 1) / 2)) * ((int)((n + 2) / 2))) << endl;
}
signed main(){
ios_base::sync_with_stdio(false), cin.tie(0);
cout << fixed << setprecision(9);
solve();
return 0;
}