【题解】Luogu DTOI Round 4
前言:
好久不打洛谷的 \(rated\) 赛了,结果一打 \(205,rk14\),看来是没有大佬来打。
放一下链接:https://www.luogu.com.cn/contest/96429
P8976 「DTOI-4」排列
题目分析:
这个题其实就是两个结论,反正我是这样想的:
- 只需要构造出一个等于 \(a\) 的部分,剩下的部分给 \(b\) 就一定是最优的,如果 \(a\) 大于能构造的最大值就无解,\(a\) 小于能构造的最小值就构造为最小值,剩下的 \(b\) 不够即无解
证明:我们构造为了使得 \(b\) 的部分有更大的可能满足条件,就必然要使得 \(a\) 的部分尽可能小,显然当这一部分恰好为 \(a\) 时就是最优的,因为如果大一点对于另一部分就会少一些。 - 具体构造使用调整法,即先将 \(a\) 的部分设置为 \([1,\frac{n}{2}]\),然后再将 \(b\) 的部分从大到小遍历,同时与 \(a\) 的部分的最大值和最小值比较,如果替换掉最小值后小于等于 \(a\) 则替换,否则如果替换为最大值时小于等于 \(a\),则证明当我们将这个数替换为最大值到最小值之间的某个数一定可以等于 \(a\),这样复杂度为 \(O(n)\)
证明:我们这样其实本质上是遍历了所有的可能只不过加了一个剪枝。我们如果可以替换最小值就替换,这样可以为我们以后减轻压力,不然可能构造不出来。因为我们的最大值到最小值一定是连续的,所以如果一边大于等于 \(a\),另一边小于等于 \(a\),所以中间一定有一个等于 \(a\),不然就无法过渡了,因为连续。
代码:
点击查看代码
#include<bits/stdc++.h>
#define int long long
const int N = 5e5+5;
int ans[N];
using namespace std;
int sum(int l,int r){ //等差数列求和
return (l + r) * (r - l + 1) / 2;
}
signed main(){
int t;scanf("%lld",&t);
while(t--){
int n,a,b;scanf("%lld%lld%lld",&n,&a,&b);
if(a < sum(1,n/2)) a = sum(1,n/2);
if(a + b > n * (n + 1) / 2 || a > sum(n/2+1,n)) printf("-1\n");
else{
for(int i=1; i<=n; i++) ans[i] = i;
int now = sum(1,n/2);
int r = n,l = 1;
while(now != a && l <= n/2 && r >= n / 2 + 1){
if(now + ans[r] - ans[l] <= a) now = now + ans[r] - ans[l],swap(ans[l],ans[r]),l++,r--;
else if(now + ans[r] - ans[n/2] > a) r--;
else{
for(;now != a && l <= n/2; l++){
if(now + ans[r] - ans[l] == a) swap(ans[l],ans[r]),now = now + ans[r] - ans[l];
}
break;
}
}
for(int i=1; i<=n; i++) printf("%lld ",ans[i]);
printf("\n");
}
}
return 0;
}
P8977 「DTOI-4」行走
题目分析:
这个题其实就是一个结论:
- 一定不会走权值为 \(-1\) 的点
证明:考虑若走了 \(-1\) 的点,根据小学的知识可知,我们以后无论走多少个 \(1\) 都无法弥补回这个 \(-1\) 的差距,所以不能走。
这个题我的做法其实本质上是暴力剪枝,但是仿佛稍微精细一点可以做到复杂度 \(O(n)\) [惊恐]
我们可以考虑按深度遍历,即对于当前假设有一些待选择的点构成的集合 \(V\),那么我们就去找 \(\forall x\in V\) 的 \(x\) 的儿子,显然如果其中某一个 \(x\) 的儿子是 \(1\) 我们就直接走这些 \(1\) 就是最优的,那么就把这些 \(1\) 的点加入点集中,而如果没有 \(1\) 那么说明只要不是 \(-1\) 都是可以达到最优解的,那么都加入点集中。而因为有字典序的限制,所以我们会预处理 \(flag\) 数组,其中 \(flag[i]\) 表示从 \(i\) 点开始向下走不经过 \(-1\) 的点能否走到 \(1\) 的点,那么如果我们当前点集所有点的儿子要么是 \(-1\) 要么 \(flag\) 为 \(false\),那么从 \(1\) 走到当前的点集中的点就是我们最优的路径。
那么就可以直接把这些路径用树上前缀和弄出来,再从 \(1\) 开始按字典序每次找最小的一条路径走就可以了。
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
struct edge{
int nxt,to;
edge(){}
edge(int _nxt,int _to){
nxt = _nxt,to = _to;
}
}e[2 * N];
set<int> g,h,ans;
int sum[N],fa[N],a[N],head[N],cnt;
bool flag[N];
void add_edge(int from,int to){
e[++cnt] = edge(head[from],to);
head[from] = cnt;
}
void dfs(int now,int fath){
fa[now] = fath;
for(int i = head[now]; i;i = e[i].nxt){
int to = e[i].to;
if(to == fath) continue;
dfs(to,now);
}
}
void dfs2(int now,int fath){
for(int i = head[now]; i;i = e[i].nxt){
int to = e[i].to;
if(to == fath) continue;
dfs2(to,now);
sum[now] += sum[to];
}
}
void get_ans(int now,int fath){
printf("%d ",now);
for(int i = head[now]; i;i = e[i].nxt){
int to = e[i].to;
if(to == fath) continue;
if(sum[to]) ans.insert(to);
}
if(ans.empty()) return;
int to = *ans.begin();ans.clear();
get_ans(to,now);
}
void pushup(int now){
while(now){
now = fa[now];
if(a[now] == -1) break;
else if(flag[now]) break;
else flag[now] = true;
}
}
int main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int n;scanf("%d",&n);
for(int i=1; i<=n; i++) scanf("%d",&a[i]);
for(int i=1; i<n; i++){
int from,to;scanf("%d%d",&from,&to);
add_edge(from,to);add_edge(to,from);
}
dfs(1,0);
for(int i=1; i<=n; i++) if(a[i] == 1) pushup(i);
if(!flag[1] && a[1] == 1){
printf("1\n");
return 0;
}
if(a[1] == -1 || !flag[1]) return 0;
g.insert(1);
while(!g.empty()){
bool tag = false;
for(int now : g){
for(int i = head[now]; i;i = e[i].nxt){
int to = e[i].to;
if(to == fa[now]) continue;
if(a[to] == 1) tag = true;
}
}
if(tag){
for(int now : g){
for(int i = head[now]; i; i = e[i].nxt){
int to = e[i].to;
if(to == fa[now]) continue;
if(a[to] == 1) h.insert(to);
}
}
}
else{
for(int now : g){
for(int i = head[now]; i; i = e[i].nxt){
int to = e[i].to;
if(to == fa[now]) continue;
if(a[to] != -1) h.insert(to);
}
}
}
tag = false;
for(int now : h) if(flag[now]) tag = true;
if(!tag){
for(int now : h) sum[now]++;
h.clear();g.clear();
}
else{
g.clear();
for(int now : h) if(flag[now]) g.insert(now);
h.clear();
}
}
dfs2(1,0);
get_ans(1,0);
return 0;
}
感觉代码实现很离谱,就将就看吧,对应我写的文字内容可能看起来舒服一些
P8978 「DTOI-4」中位数
有些晕,以后再写,咕咕咕
P8979 「DTOI-4」白的 Fibonacci
神仙的推式子,最后还要求一个卷积,咕咕咕