K. Kingdom’s Power
题意
给定一个树,可以从根节点发出无数个士兵。
但是每个时刻只能让树中的一个士兵移动。士兵每移动一个节点该节点就被占领。
问,占领所有节点至少需要多少时刻?
思路
如果只从根节点发出一个士兵跑完所有节点的话,因为最后遍历所有节点之后不必回来,所以其遍历的顺序一定是先遍历深度较小的子树,再遍历深度较大的子树,最终停留在深度最深的地方。
那么就应该预处理出从每个节点能够到达的最深深度,将每个节点的所有子节点按照深度从小到大的顺序遍历。
但是这道题可以从根节点无数次发出士兵,所以在上面的前提下,还应该考虑到下一个子节点时,是从上一个子节点所在子树的最长链末端回来,还是从根节点重新发出一个士兵。
可以用 pre[] 数组记录从根节点 u 遍历的上一个子节点所在子树的最长链末端 t,然后到达一个点x时,取 dep[x]
和 dep[t] - dep[u] + 1
的较小值,作为到达该节点的花费,答案累加。
(在树中,子节点和其祖先节点的距离是确定的,为其深度之差)
void dfs2(int x)
{
pre[x] = x;
for(auto tx : e[x])
{
sum += min(dep[tx], dep[pre[x]] - dep[x] + 1);
dfs2(tx);
pre[x] = pre[tx];
}
}
首先将当前节点的 pre[x]
设为当前值,因为第一次遍历的节点不能从上一次遍历的子节点最底端转移,只能从其父节点过来。
在递归过一个子树的所有节点之后,将最后一个节点一层一层传递上来,最后传递到该子树的根节点,供下次遍历的子节点判断。
而这个传递的过程,就是在递归到下一节点 tx
之后,令 pre[x] = pre[tx]
。此时,pre[tx]
存储的已经是到达最底端的节点编号,将其赋值为该子树根节点 x 的 pre[]
数组。
太妙了~
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define ll long long
#define endl '\n'
const int N = 2000010, mod = 1e9+7;
int T, n, m, k;
ll a[N], dep[N], maxa[N];
ll val[N], sum;
int pre[N];
vector<int> e[N];
bool cmp(int x, int y){
return maxa[x] < maxa[y];
}
void dfs1(int x)
{
for(auto tx : e[x])
{
dep[tx] = dep[x] + 1;
maxa[tx] = dep[tx];
dfs1(tx);
maxa[x] = max(maxa[x], maxa[tx]);
}
}
ll dfs2(int x)
{
pre[x] = x;
for(auto tx : e[x])
{
sum += min(dep[tx], dep[pre[x]] - dep[x] + 1);
dfs2(tx);
pre[x] = pre[tx];
}
}
signed main(){
Ios;
cin >> T;
for(int cs=1;cs<=T;cs ++)
{
cin >> n;
for(int i=1;i<=n;i++)
e[i].clear(), dep[i] = maxa[i] = val[i] = pre[i] = 0;
for(int i=2;i<=n;i++)
{
int x; cin >> x;
e[x].pb(i);
}
dfs1(1);
for(int i=1;i<=n;i++)
sort(e[i].begin(), e[i].end(), cmp);
sum = 0;
dfs2(1);
printf("Case #%d: %lld\n", cs, sum);
}
return 0;
}
还有一种做法,没看懂,放一下吧
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define mem(a,b) memset(a,b,sizeof a)
#define ll long long
#define PII pair<int,int>
#define pb push_back
#define fi first
#define se second
#define endl '\n'
map<int,int> mp;
const int N = 2000010, mod = 1e9+7;
int T, n, m, k;
ll a[N], dep[N], maxa[N];
ll val[N], sum;
vector<int> e[N];
bool cmp(int x, int y){
return maxa[x] < maxa[y];
}
void dfs1(int x)
{
for(auto tx : e[x])
{
dep[tx] = dep[x] + 1;
maxa[tx] = dep[tx];
dfs1(tx);
maxa[x] = max(maxa[x], maxa[tx]);
}
}
ll dfs2(int x, int dis) //返回值是自底向上的最短距离,dis表示从上个点到该点的最小步数
{
val[x] = dis;
if(!e[x].size()) return 1;
ll mina = dis;
for(int i = 0; i < e[x].size(); i++)
{
int tx = e[x][i];
mina = min(dep[x], dfs2(tx, mina + 1));
}
return mina + 1;
}
signed main(){
// Ios;
cin >> T;
for(int cs=1;cs<=T;cs ++)
{
cin >> n;
for(int i=1;i<=n;i++) e[i].clear(), dep[i] = maxa[i] = val[i] = 0;
for(int i=2;i<=n;i++)
{
int x; cin >> x;
e[x].pb(i);
}
dfs1(1);
for(int i=1;i<=n;i++)
sort(e[i].begin(), e[i].end(), cmp);
sum = 0;
dfs2(1, 0);
for(int i=1;i<=n;i++)
{
cout << val[i] << " ";
if(!e[i].size()) sum += val[i];
}
cout << endl;
// printf("Case #%d: %lld\n", cs, sum);
}
return 0;
}
G. Good Number
题意
给定两个数 n 和 k,判断 1~n 中有多少数 x 满足:
x
x
x %
⌊
x
k
⌋
\lfloor \sqrt[k]{x} \rfloor
⌊kx⌋ = 0.
(
1
≤
n
,
k
≤
1
0
9
)
(1 \le n,k \le 10^9)
(1≤n,k≤109)
思路
因为是取根号后下取整,所以如果 x 满足,x 是 t 的倍数并且
t
k
≤
x
<
t
k
+
1
t^k ≤ x<t^{k+1}
tk≤x<tk+1,那么 x 就是合法。
所以可以遍历 t,遍历
t
k
t^k
tk 到
t
k
+
1
−
1
t^{k+1}-1
tk+1−1 中不超过 n 的 t 的倍数,个数每次+1。
但是一点点遍历倍数太慢了,有没有更快的方法得到区间 [
t
k
t^k
tk,
t
k
+
1
−
1
t^{k+1}-1
tk+1−1] 中 t 的倍数的个数呢?
两端点对于 t 的商相减。因为
t
k
t^k
tk 一定是 t 的 倍数,所以区间中 t 的倍数个数为
r
t
−
l
t
+
1
\frac rt - \frac lt + 1
tr−tl+1。
此外要注意,当 k 很大的时候,t^k 会爆 longlong。
但是注意到,当 k = 32 时,2^32 就已经超过 n 最大值 1e9 了,当 t 遍历到 1 的时候右端点就超过 n 了,所以当 k≥32 时,1~n 中的所有数都是满足的。直接特判。
Code:
#include<bits/stdc++.h>
using namespace std;
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int ans;
int qmi(int x, int y)
{
int ans = 1;
while(y)
{
if(y & 1) ans = ans*x;
y >>= 1;
x = x*x;
}
return ans;
}
void pd(int n, int k)
{
ans = 0;
for(int i=1;;i++)
{
int x = qmi(i, k), y = min(n, qmi(i+1, k)-1);
if(x > n) break;
ans += y/i - x/i + 1;
}
}
signed main(){
scanf("%lld", &T);
int k;
for(int cs = 1;cs<=T;cs++)
{
scanf("%lld%lld", &n, &k);
if(k==1 || k >= 32){ //特判一下较大数
printf("Case #%lld: %lld\n", cs, n);
continue;
}
pd(n, k);
printf("Case #%lld: %lld\n", cs, ans);
}
return 0;
}
经验
- 大数要知道特判掉;
- O(1) 求得一个区间中 x 倍数的个数;
- 复习一遍快速幂。
E. Exam Results
题意
有 n 位同学,每位同学都有较高成绩
a
i
a_i
ai 和 较低成绩
b
i
b_i
bi。
对于一次考试,每位同学都会得到两种成绩中的一种。
假设所有同学中最高分数为
x
x
x,那么得分不低于
x
⋅
p
%
x \cdot p\%
x⋅p% 的同学都会通过考试。
问,在所有得分方案中,通过考试的同学最多有多少个?
思路
将所有成绩从大到小排序后,遍历所有成绩作为最高成绩,然后尺取找到最后一个不低于其 %p 成绩的位置。
因为对于最高成绩从大到小遍历,其 %p 成绩的位置也是逐渐递减的。所以可以用一个指针不断后移,省掉一重循环,复杂度 O(n)。
设当前遍历的最高成绩的位置 i
,idx
为找到的最后一个不低于 %p 的位置。
- 如果
idx+1
位置的成绩也是满足不低于 %p 的,那么idx
就可以 +1,不断后移。 - 对于新加进来的位置,如果该同学没有在区间中,那么 cnt++;
- 将能够加进来的位置都加进来后,ans 和 cnt 取 max;
- 接下来该遍历到下一个位置,那么就把当前位置从区间中去掉。如果该同学在区间中出现次数为1,那么去掉后区间中将没有该同学,所以要将 cnt–。
那么如何通过一个位置(成绩)来判断是哪位同学的成绩呢?
一开始想的是 map 标记成绩,但是成绩不是唯一的啊。
后面看题解发现,可以对每个成绩设置结构体,其元素为具体值和同学编号。 这样排过序之后也可以找到每个成绩是哪位同学的。
坑点:
所有的同学都应该有成绩,所以我们所枚举的最高成绩应该不小于所有同学较低成绩的最大值!
总复杂度O(nlogn)。
Code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1000010, mod = 1e9+7;
int T, n, m, p;
struct node{
ll x;
int pos;
}a[N];
int f[N];
bool cmp(node a, node b){
return a.x > b.x;
}
signed main(){
scanf("%d", &T);
for(int cs=1;cs<=T;cs++)
{
scanf("%d%d", &n, &p);
int mina = 0;
for(int i=1;i<=n;i++){
int x, y; scanf("%d%d", &x, &y);
a[i].x = x, a[i].pos = i;
a[n+i].x = y, a[n+i].pos = i;
f[i] = 0;
mina = max(mina, y); //
}
n *= 2;
sort(a+1, a+n+1, cmp);
int idx = 0, cnt = 0, ans = 0;
for(int i=1;i<=n;i++)
{
if(a[i].x < mina) break; //
while(idx < n && a[idx+1].x*100ll >= a[i].x*p)
{
idx++;
f[a[idx].pos] ++;
if(f[a[idx].pos] == 1) cnt++;
}
ans = max(ans, cnt);
f[a[i].pos]--;
if(f[a[i].pos] == 0) cnt--;
}
printf("Case #%d: %d\n", cs, ans);
}
return 0;
}
经验
很好的一道题。一开始看到这题的时候一点思路都没有,总想着这两个成绩取一个该咋取。。
尺取的题目最近也没怎么做,算是一个很好的复习吧。