D. Permutation Restoration
题意
给定一个长度为
n
n
n 的
b
[
]
b[]
b[] 数组,其中
b
i
=
⌊
i
a
i
⌋
b_i = \left\lfloor \frac{i}{a_i} \right\rfloor
bi=⌊aii⌋。
找到原来的全排列数组
a
[
]
a[]
a[] 并输出。
思路
由
b
i
=
⌊
i
a
i
⌋
b_i = \left\lfloor \frac{i}{a_i} \right\rfloor
bi=⌊aii⌋ 可知,
a
[
]
a[]
a[] 数组每个位置元素
a
i
a_i
ai 有取值范围:
i
b
i
+
1
<
a
i
≤
i
b
i
\frac {i}{b_i+1} < a_i \le \frac {i} {b_i}
bi+1i<ai≤bii。
每个位置选择区间中的一个元素,所有位置选择的元素组成全排列。
将区间与位置放到结构体中,先按照 区间右端点
再按照 左端点或者位置
从小到大排序。
遍历所有区间,每次找到第一个未使用的元素。
为什么要先按照右端点小到大排序呢?
如果有两个区间 [x, y] 和 [z, y+1],如果先处理后面的区间,那么如果后面区间中 元素y 是第一个未被使用的话,第一个区间就无法满足了,所以要先按照右端点排序,保证前面小范围的元素取过了再取大范围元素。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define endl '\n'
const int N = 500010, mod = 1e9+7;
int T, n, m;
int a[N], b[N];
struct node{
int i, l, r;
}t[N];
int mp[N];
bool cmp(node a, node b){
if(a.r != b.r) return a.r < b.r;
return a.l < b.l;
}
int ans[N];
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n;
for(int i=1;i<=n;i++){
cin >> b[i];
int l = i/(b[i] + 1) + 1;
int r;
if(b[i]) r = i/b[i];
else r = n;
t[i] = {i, l, r};
}
sort(t+1, t+n+1, cmp);
int pos = 1;
for(int i=1;i<=n;i++) mp[i] = 0;
for(int i=1;i<=n;i++)
{
int l = t[i].l, r = t[i].r;
while(mp[pos]) pos ++;
for(int j=max(pos, l);j<=r;j++)
{
if(!mp[j]){
mp[j] = 1;
ans[t[i].i] = j;
break;
}
}
}
for(int i=1;i<=n;i++) cout << ans[i] << " ";
cout << endl;
}
return 0;
}
但是还有一个问题,为什么不加上第二元素排序就超时了呢?
C. Schedule Management
题意
一共
n
n
n 个人,
m
m
m 项工作。
每个工作都有一个值
a
i
a_i
ai,指精通这项工作的人的编号。
对于一项工作,如果由精通这项工作的人来做会花费
1
1
1 小时,否则将会花费
2
2
2 小时。
问,这 m 项工作最少用多少小时能完成?
思路
二分答案
二分完成所有工作最少时间 mid,对于每个人来说:
- 如果他所精通的工作不超过 mid 个,那么先完成这些工作;
- 否则,累加未完成的工作数。
对于未完成的 x x x 项工作,每个工作花费两小时。
- 遍历每个人,看其剩余的时间最多能额外完成多少工作。
- 如果能额外完成的工作总数不少于 x x x 项,那么所有工作就能在 m i d mid mid 小时内完成,return 1;
- 否则 return 0;
注意每项工作只能交给一个人完成,所以如果一个人剩余 t 小时时间,那么其只能额外完成 ⌊ t 2 ⌋ \left\lfloor \frac{t}{2} \right\rfloor ⌊2t⌋ 项工作。所以不能简单地把剩余时间累加起来除 2 来判断能完成多少项工作。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N];
int cnt[N], Left[N];
bool check(int mid)
{
int x = 0;
for(int i=1;i<=n;i++)
{
if(cnt[i] > mid) Left[i] = 0, x += cnt[i]-mid;
else Left[i] = mid - cnt[i];
}
for(int i=1;i<=n;i++)
{
int t = Left[i]/2;
x -= t;
}
if(x <= 0) return 1;
return 0;
}
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n >> m;
for(int i=1;i<=n;i++) cnt[i] = 0;
for(int i=1;i<=m;i++){
int x; cin >> x;
cnt[x] ++;
}
int l = 1, r = m*2;
while(l < r)
{
int mid = l+r>>1;
if(check(mid)) r = mid;
else l = mid+1;
}
cout << l << endl;
}
return 0;
}
B. Permutation
题意
构造一个 n 个数的全排列
p
[
]
p[]
p[],使得满足
p
i
⋅
d
=
p
i
+
1
p_i \cdot d = p_{i + 1}
pi⋅d=pi+1 的位置最多,
d
d
d 为固定的正整数。
输出
d
d
d 和全排列
p
[
]
p[]
p[]。
题意
既然是排列,为了使得满足
p
i
⋅
d
=
p
i
+
1
p_i \cdot d = p_{i + 1}
pi⋅d=pi+1 的位置最多,那么 d 一定为 2。满足条件的位置一定翻倍增长。
所以就认为只有 1, 2, 4, 8 … 这样的数,而忽略了 3, 6, 12 这样的数,多 wa 了一发。。
像埃式筛那样翻倍标记输出即可。
Code
#include<bits/stdc++.h>
using namespace std;
#define Ios ios::sync_with_stdio(false),cin.tie(0)
const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], mp[N];
signed main(){
Ios;
cin >> T;
while(T--)
{
cin >> n;
cout << 2 << endl;
for(int i=1;i<=n;i++) mp[i] = 0;
for(int i=1;i<=n;i++)
{
if(mp[i]) continue;
for(int j=i;j<=n;j+=j) cout << j <<" ", mp[j] = 1;
}
cout << endl;
}
return 0;
}
A. Grass Field
题意
2*2 的 01矩阵,判断最多执行多少次下面的操作能使得所有元素变为 0:
- 选择一行和一列,将其所有 1 变为 0。
思路
注意是一行 和 一列,不是 或。
所以只要不全是 1 就能一次操作解决,全是 1 就两次。
经验
题目要看清啊。
第二题没看清输出的是啥wa了一发,然后理解清题意又wa了一发。
要读懂题啊。