深搜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!!!
一般还是用深搜来暴力枚举,复杂度都是指数级别的,所以不好搞,但是可以加剪枝和记忆化来优化。
还没做先想一会儿
题意:给m个数,要分解成n个数(变成两个数之和),求能变到多少个数。
这个题的优化还挺多的。
1.为了让数分得尽量多,肯定从小的开始分,所以先排序,这步算个贪心。
排序后的数列从小到大来分,是肯定满足单调性的,T就二分来搞,再用dfs验证前mid的数是否分的出来。
2.考虑取不到的情况:当剩余的数都小于要分出来的数,则回溯,这步是个可行性剪枝。
这步就要先维护一个前缀和,再加一个变量记没有取的数的和。
3.如果有重复的数就去重,也是个剪枝。
思路就这样了,代码还没打。
一周后终于鸽出来了……
主要问题是二分的时候的边界问题,这个之后再说吧。
看了一下,虽然没什么剪枝,不过应该有一定的思维难度,先做一做。
隔了好久还没调出来……
自底向上搜索,只要自己满足了条件,自己的子树上的梅花就都能撤掉。
叶节点所需梅花就是 。
对于其他节点遍历自己的字树的顺序对答案是有影响的。
例题:P2919 [USACO08NOV]Guarding the Farm S
这不就一求连通块的吗?竟然没一下打出来,看来我的代码能力还有待提高啊QWQ
淦,看来没我想的那么简单。
搞出来了,原来还要从大到小排序保证正确性。
例题:P6574 [BalticOI 2017] Cat in a tree
也是个贪心搜索
一下子还想不出来思路欸
写线段树的话也许空间会爆掉帕。
一开始读错题了,还以为是能发给 的同学。
对于接收的范围,可以对能接收到某个人的同学连边。
但是边最多会有 的边,就爆掉了。
考虑优化,对于一个人,可以向能接收到他的最近的人连边,这样子总共只有 条边。
可以证明到,因为向最近的连可以保证在dfs时不会漏其他点,时间复杂度 。
至于维护最近的人,可以使用stack,分别存左右连边。
然后就是怎么存做题的人数了。
我们发现,这个题保证 ,所以可以直接预处理当分数为 时的答题情况。
但是路哥的能力是能修改的。我们能够发现,不管路哥的能力值多少,其结果只是他会不会做这道题而已。
所以可以把记录答案的 数组开成二维的,用来存当路哥答或不答时的答题情况。
但是如果是所有人都可以修改呢?
dfs明显要死,所以就只能当成毒瘤数据结构做了,就写线段树了。
或者我们再想远一点,查询出某一时刻以总共做出的题数为关键字从大到小排序的第 个人编号是多少?另一种查询以能力值为关键字的第k大的人?
再多一点,再加上查询时指定 到 的区间?
线段树也死了,上平衡树。分块大法好
算了,这样子的话这题进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 绫小路的特别考试
以前的一道题。首先注意 的意义是 能接收 的人发的消息,而不是 能给其他人发消息。 做法是直接暴力让 和 内的所有点连指向 的边,查询就直接对每个点 dfs 一遍,如果遍历过了就不额外 dfs 了,均摊下来是 的。
最大的问题在于边数过多,其实画画图就能发现对于每个 只需要向左右两边最近的能收到他的消息的点连边就行了,因为这样相当于只保留了必要的边,不改变连通性,就把边数降到了 级。
但查询如果还是每次重做一遍 dfs 就是 的,而这题又强制在线。但是注意到学生能力 和每次查询的难度 都是 的,这启示我们从值域入手。注意虽然不能离线,但是可以预处理答案。做题人数显然和题目难度成正比,所以我们可以提前对学生按能力降序排序,然后枚举难度 双指针处理答案。可以这样做的原因是修改操作只会修改路哥一个人,所以我们这样做两遍,分别记录路哥是否做出这道题时的答案就行了。复杂度 ,瓶颈在于对学生排序,可以桶排优化,但为啥我写出来是错的。
这才叫题解
好想回到那个时候啊……
这个题过了,但是莫名的失落,彻底回不去了
难道就有时候能回得去?
再见了大家,再见了,我热爱的东西