A. Eshag Loves Big Arrays
思路
找出比最小数大的数字的数量,就是答案
代码
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define Pir pair<int, int>
#define fi first
#define se second
#define pb push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 105;
int a[N], vis[N];
int main()
{
int T; scanf("%d", &T);
while (T --)
{
memset(vis, 0, sizeof vis);
int n; scanf("%d", &n);
int ans = 0;
for (int i = 1; i <= n; i ++)
{
scanf("%d\n", &a[i]);
}
for (int i = 1; i <= n; i ++)
{
for (int j = 1; j <= n; j ++)
{
if (a[j] > a[i])
vis[j] = 1;
}
}
for (int i = 1; i <= n; i ++) ans += vis[i];
printf("%d\n",ans);
}
return 0;
}
B. Sifid and Strange Subsequences
题意
- 从一个数组中找出一个最长子序列,要求这个最长子序列的任意两个数的绝对值之差 > 这个子序列的最大值。
思路
- 显然 所有的负数和 0 都要选,
- 整数只能选择一个(肯定选择最小的正整数啦),如果选择两个以上的话,则两个正整数相减的差值必然小于 序列中的最大值。
- 因此只需对序列排序,然后暴力相邻两个数的值是不是 >= 序列最大值就行了,如果不满足的话,最小正整数就不能去了。
代码
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define Pir pair<int, int>
#define fi first
#define se second
#define pb push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 1e5 + 10;
int a[N];
int main()
{
int T; scanf("%d", &T);
while (T --)
{
int n; scanf("%d", &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d", &a[i]);
}
sort(a + 1, a + 1 + n);
int idx = upper_bound(a + 1, a + 1 + n, 0) - a;
if (idx == n + 1)
{
printf("%d\n", n);
}
else
{
int mx = a[idx];
for (int i = 2; i < idx; i ++)
{
if (a[i] - a[i - 1] < mx)
{
mx = 0;
break;
}
}
if (mx)
{
printf("%d\n", idx);
}
else
{
printf("%d\n", idx - 1);
}
}
}
return 0;
}
代码 2——附上本人的垃圾三分
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define Pir pair<int, int>
#define fi first
#define se second
#define pb push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 1e5 + 10;
int n;
int a[N];
int check(int md)
{
int cnt = 0;
int last = -inf;
for (int i = 1; i <= md; i ++)
{
if (a[i] >= last)
{
cnt ++;
last = a[i] + a[md];
}
}
return cnt;
}
int main()
{
int T; scanf("%d", &T);
while (T --)
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++)
scanf("%d", &a[i]);
sort(a + 1, a + 1 + n);
int ans = 0;
int l = n + 1, r = n;
for (int i = 1; i <= n; i ++)
{
if (a[i] <= 0) ans ++;
else
{
l = i;
break;
}
}
int cnt = 80;
while (r - l > 1 && cnt --)
{
int lmd = l + (r - l) / 3;
int rmd = r - (r - l) / 3;
if (check(lmd) <= check(rmd))
l = lmd;
else
r = rmd;
}
/* cout << '\t' << ans << endl; */
/* for (int i = 1; i <= n; i ++) cout << a[i] << " "; */
/* cout << endl; */
/* cout << l << " " << r << endl; */
for (int i = l - 100; i <= l + 100; i ++)
{
if (i >= 1 && i <= n)
{
ans = max(ans, check(i));
}
}
printf("%d\n", ans);
}
return 0;
}
C. Parsa’s Humongous Tree(树形 dp)
题意
- 给我们一棵树,树的节点有有一个取值范围 [ l i , r i ] [l_i, r_i] [li,ri],现在让我们通过合理选择的节点的权值,来使这个树产生的贡献最大。
- 贡献最大指的是:所有存在边的两个点的差值的绝对值 的 和 最大。
思路
- 树形 dp,
- dp [i][0] 表示以 i 为根的子树选择 l i l_i li 所能产生的最大贡献。
- dp [i][i] 表示以 i 为根的字数选择 r i r_i ri 所能产生的最大贡献。
- 状态转移方程 看代码吧
- 证明为什么对每个节点,为什么一定都是要去
l
l
l 或
r
r
r, 不去中间值呢?
- 我们假设对于一个点 i 如果的取值是中间值 mid,l<mid <r,
- 假设此时在当前节点的子节点的权值比 mid 小的有 x 个,比 mid 大的子节点有 y 个,
- 我们假设 x > y 此时我们让 mid 的值增大,
①那么当 x 、y 的个数不变的时候,x 增加的贡献是比 y 减少的贡献多的,
②x、y 数量变化的时候,x 数量必然是变多,y 的数量必然是减小,因此 x 增加的贡献增加的更多了,y 减少的贡献减少的更少了,所有此时让 mid==r 时最优 - 同理 x < y, 时我们可以证明 mid == l 时取得最优值。
代码
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define Pir pair<int, int>
#define fi first
#define se second
#define pb push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 2e5 + 10;
int n, a[N][2];
vector<int> e[N];
ll dp[N][2];
void dfs(int u, int p)
{
ll sum0 = 0, sum1 = 0;
for (auto v : e[u])
{
if (v == p) continue;
dfs(v, u);
sum0 += max(dp[v][0] + abs(a[u][0] - a[v][0]), dp[v][1] + abs(a[u][0] - a[v][1]));
sum1 += max(dp[v][0] + abs(a[u][1] - a[v][0]), dp[v][1] + abs(a[u][1] - a[v][1]));
}
dp[u][0] = sum0;
dp[u][1] = sum1;
}
int main()
{
int T; scanf("%d", &T);
while (T --)
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++) scanf("%d %d", &a[i][0], &a[i][1]), e[i].clear(), dp[i][0] = dp[i][1] = 0;
int u, v;
for (int i = 1; i <= n - 1; i ++)
{
scanf("%d %d", &u, &v);
e[u].pb(v);
e[v].pb(u);
}
dfs(1, 0);
printf("%lld\n", max(dp[1][0], dp[1][1]));
}
return 0;
}
D. Kavi on Pairing Duty
思路
- 老 dp + 组合数学 的套路了,我们只需要用状态转移的思路去找规律就行了。
d
p
[
i
]
=
s
u
m
[
i
−
1
]
+
i
的
因
子
个
数
dp [i]=sum [i-1]+i 的因子个数
dp[i]=sum[i−1]+i 的因子个数
s
u
m
[
i
]
=
∑
j
=
1
j
<
=
i
d
p
[
j
]
sum[i]=\sum_{j=1}^{j<=i}dp[j]
sum[i]=j=1∑j<=idp[j]
2. dp[i] 表示长度为 i 的时候的方案数
3. 对于快速求 1~1e6 中每个数因子个数的话,我们可以用 欧拉筛 去快速求解
代码
```c
#include <bits/stdc++.h>
using namespace std;
#define db double
#define ll long long
#define Pir pair<int, int>
#define fi first
#define se second
#define pb push_back
#define m_p make_pair
#define inf 0x3f3f3f3f
#define INF 0x3f3f3f3f3f3f3f3f
/*==========ACMer===========*/
const int N = 1e6 + 10;
const ll mod = 998244353;
ll f[N], all[N], cnt[N];
int prim[N], vis[N], d[N], sum[N], len;
// sum[i] 表示i的最小质因子的次方
// d[i] 表示 i 的约数的个数 -> 这个是我们的答案
void eular_num(int n)
{
d[1] = 1; //1 特判
for(int i = 2;i <= n;i ++)
{
if(! vis[i])
{
prim[len ++] = i;
sum[i] = 1;
d[i] = 2; //质数的约数只有1和它本身
}
for(int j = 0; j < len && i * prim[j] <= n; j ++)
{
vis[i * prim[j]] = 1;
if(i % prim[j] == 0)
{
sum[i * prim[j]] = sum[i] + 1;
d[i * prim[j]] = d[i] / (sum[i] + 1) * (sum[i] + 2);
break;
}
sum[i * prim[j]] = 1;
d[i * prim[j]] = d[i] * 2;
}
}
}
void sov(int n)
{
sum[0] = 0;
f[1] = 1;
f[2] = 3;
f[3] = 6;
all[1] = all[0] + f[1];
all[2] = all[1] + f[2];
for (int i = 3; i <= n; i ++)
f[i] = (all[i - 1] + d[i]) % mod, all[i] = (all[i - 1] + f[i]) % mod;
printf("%lld\n", f[n]);
}
int main()
{
int n; scanf("%d", &n);
eular_num(n);
sov(n);
return 0;
}
/*
1 1
2 3
3 6
4 13
5 25
6 52
7 102
8 206
9 411
10 823
*/