深搜DFS

在图论中用这个来遍历图,形如以下:

void dfs(ll id,ll fa){
    for(int i=head[id];i;i=e[i].next){
        ll v=e[i].to,w=e[i].val;
        if(v==fa) return;
        //…………计算东西 
        dfs(v,id);
    }
}

于是我又犯了一个经典错误:

if(v==fa) return;

dfs时for里别return,是continue!!!

一般还是用深搜来暴力枚举,复杂度都是指数级别的,所以不好搞,但是可以加剪枝和记忆化来优化。

例题:P2329 [SCOI2005]栅栏

还没做先想一会儿

题意:给m个数,要分解成n个数(变成两个数之和),求能变到多少个数。

这个题的优化还挺多的。

1.为了让数分得尽量多,肯定从小的开始分,所以先排序,这步算个贪心。

排序后的数列从小到大来分,是肯定满足单调性的,T就二分来搞,再用dfs验证前mid的数是否分的出来。

2.考虑取不到的情况:当剩余的数都小于要分出来的数,则回溯,这步是个可行性剪枝。

这步就要先维护一个前缀和,再加一个变量记没有取的数的和。

3.如果有重复的数就去重,也是个剪枝。

思路就这样了,代码还没打。

一周后终于鸽出来了……

主要问题是二分的时候的边界问题,这个之后再说吧。

例题:P5521 [yLOI2019] 梅深不见冬

看了一下,虽然没什么剪枝,不过应该有一定的思维难度,先做一做。

隔了好久还没调出来……

自底向上搜索,只要自己满足了条件,自己的子树上的梅花就都能撤掉。

叶节点所需梅花就是 w

对于其他节点遍历自己的字树的顺序对答案是有影响的。

例题:P2919 [USACO08NOV]Guarding the Farm S

这不就一求连通块的吗?竟然没一下打出来,看来我的代码能力还有待提高啊QWQ

淦,看来没我想的那么简单。

搞出来了,原来还要从大到小排序保证正确性。

 例题:P6574 [BalticOI 2017] Cat in a tree

也是个贪心搜索

一下子还想不出来思路欸

例题:P4964 绫小路的特别考试

写线段树的话也许空间会爆掉帕。

一开始读错题了,还以为是能发给 [idi,i+di] 的同学。

对于接收的范围,可以对能接收到某个人的同学连边。

但是边最多会有 n2 的边,就爆掉了。

考虑优化,对于一个人,可以向能接收到他的最近的人连边,这样子总共只有 2n 条边。

可以证明到,因为向最近的连可以保证在dfs时不会漏其他点,时间复杂度 O(n)

至于维护最近的人,可以使用stack,分别存左右连边。

然后就是怎么存做题的人数了。

我们发现,这个题保证 wi,di,xn,所以可以直接预处理当分数为 Val 时的答题情况。

但是路哥的能力是能修改的。我们能够发现,不管路哥的能力值多少,其结果只是他会不会做这道题而已。

所以可以把记录答案的 ans 数组开成二维的,用来存当路哥答或不答时的答题情况。

但是如果是所有人都可以修改呢?

dfs明显要死,所以就只能当成毒瘤数据结构做了,就写线段树了。

或者我们再想远一点,查询出某一时刻以总共做出的题数为关键字从大到小排序的第 k 个人编号是多少?另一种查询以能力值为关键字的第k大的人?

再多一点,再加上查询时指定 lr 的区间?

线段树也死了,上平衡树。分块大法好

算了,这样子的话这题进Ynoi得了。

#include<bits/stdc++.h>
using namespace std;
#define N 2145140
#define M 1919810
#define ll long long
unsigned long long seed;
int n, m, c, mfq, mind, maxd, k, w[2000001], d[2000001];


inline int randInt() { seed = 99999989 * seed + 1000000007; return seed >> 33; }




void generate(){

for (int i = 1; i <= n; i++) { w[i] = randInt() % n; }

for (int i = 1; i <= n; i++) { d[i] = randInt() % (maxd - mind + 1) + mind; }

}


void getOperation(int lastans, int &opt, int &x){

if ((0ll + randInt() + lastans) % mfq) { opt = 1; } else { opt = 2; }

x = (0ll + randInt() + lastans) % n;

}

ll ans[2][N]; //提前处理出答题情况,离线询问,同时存下路哥答和不答时的情况

ll AK; //路哥的能力值

ll top,s[N],l[N],r[N]; //对于每个人,向最近的能接收到他的人连边,用stack维护,用并查集的操作连边

ll vis[N],tot;

struct gakuse{

ll val,id;

}a[N];

bool cmp(gakuse x,gakuse y){

return x.val>y.val;

}

void dfs(ll u){

if(vis[u]||!u) return;

vis[u]=1;

dfs(l[u]);

dfs(r[u]);

++tot;

}

int main(){

//原生成器

scanf("%d %d %d", &n, &m, &c);

scanf("%llu %d %d %d %d", &seed, &mind, &maxd, &mfq, &k);

generate();

for (int i = 1; i <= k; i++){

int p, t;

scanf("%d %d", &p, &t);

d[p] = t;

}

AK=w[c];

for(int i=1;i<=n;++i)

a[i].id=i,a[i].val=w[i];

top=0;

//cout<<"QWQ"<<'\n';

for(int i=1;i<=n;++i){

while(top&&s[top]+d[s[top]]<i) --top;

if(top) l[i]=s[top];

s[++top]=i;

}

top=0;

memset(s,0,sizeof(s));

for(int i=n;i>=1;--i){

while(top&&s[top]-d[s[top]]>i) --top;

if(top) r[i]=s[top];

s[++top]=i;

}

sort(a+1,a+n+1,cmp);

//枚举题目可能的难度值

for(int Val=n-1,i=1;Val>=0;--Val){

while(i<=n&&a[i].valVal){ //每个人只用dfs一次

if(a[i].id!=c) dfs(a[i].id);

++i;

}

ans[0][Val]=tot; //路哥没做

}

tot=0;

memset(vis,0,sizeof(vis));

dfs(c);

for(int Val=n-1,i=1;Val>=0;--Val){

while(i<=n&&a[i].valVal){

dfs(a[i].id);

++i;

}

ans[1][Val]=tot; //路哥做了

}

//原生成器

int lastans = 0, finalans = 0;

for (int i = 1; i <= m; i++){

int opt, x;

getOperation(lastans, opt, x);

if (opt == 1){

ll res;

if(x>AK) res=ans[0][x];

else res=ans[1][x];

finalans = (finalans * 233ll + res) % 998244353;

lastans = res;

}

else AK=x;

}

printf("%d\n", finalans);

return 0;

} 

Besides,这题怎么这么多ctjer啊,甚至最优解都是一位珂爱的ctjer。

 

Update on 2024.11.24晚

你看看你以前是坨什么史

我都不想说你了

连个题解都看不懂,单调栈都不会

来看看现在写出来的是什么:

P4964 绫小路的特别考试

以前的一道题。首先注意 di 的意义是 i接收 [idi,i+di] 的人发的消息,而不是 i 能给其他人消息。O(n2) 做法是直接暴力让 i[idi,i+di] 内的所有点连指向 i 的边,查询就直接对每个点 dfs 一遍,如果遍历过了就不额外 dfs 了,均摊下来是 O(n2) 的。

最大的问题在于边数过多,其实画画图就能发现对于每个 i 只需要向左右两边最近的能收到他的消息的点连边就行了,因为这样相当于只保留了必要的边,不改变连通性,就把边数降到了 O(n) 级。

但查询如果还是每次重做一遍 dfs 就是 O(nm) 的,而这题又强制在线。但是注意到学生能力 wi 和每次查询的难度 x 都是 <n 的,这启示我们从值域入手。注意虽然不能离线,但是可以预处理答案。做题人数显然和题目难度成正比,所以我们可以提前对学生按能力降序排序,然后枚举难度 x 双指针处理答案。可以这样做的原因是修改操作只会修改路哥一个人,所以我们这样做两遍,分别记录路哥是否做出这道题时的答案就行了。复杂度 O(nlogn),瓶颈在于对学生排序,可以桶排优化,但为啥我写出来是错的。

 

这才叫题解

好想回到那个时候啊……

这个题过了,但是莫名的失落,彻底回不去了

难道就有时候能回得去?

再见了大家,再见了,我热爱的东西

posted @   和蜀玩  阅读(21)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示