平常の刷题7

以后做题不写博客我就吃奥利给;

CF1537F

这题2200分是吧,很有意思比别的平台上的一些蓝提有难度多了;
首先这个题目向我们揭示了图的一个很重要的性质,一张图要么是二分图,要么不是二分图,一张图里面要么有鸡环要么没有鸡环,所以根据这点我们就可以去构造了;
然后我们分别考虑一下这张图中的鸡环和偶环;
在这里插入图片描述
然后我们就发现了这样一个神奇的性质,然后我们在再去搞一下,分类讨论一下;
1 只有鸡环的图,我们可以乱杀;
2 只有偶环的图,他显然是一幅二分图,二分图我们搞一下发现如下的性质:
对于一张二分图可以有两种操作:

左边选两个点,一个加上 xx,一个减去 xx;右边的也行;
可以给左右两边各任选一个点都加上 xx;
由操作1,每一边的点权都可以在和固定的情况下任意分配;
由操作2,两边需要改变的差量相等即可;

或许也可以不用这么想,脑子里不意识流的想一张二分图,如果说两边的德尔塔不一样的话那么最后边显然会有一个val抵消不掉,那么结果就出来了;
3 既有鸡环又有偶环的图;
我们对这张图进行01染色,最后总有两个点是矛盾的,把这两个点放在鸡环上,让其中的一个点变成目标状态,然后另一个点就可以将其那啥了;

CF1491D

这道题其实很简单,但是思维不要限制的太死了不然结果就是死都做不出来,对于多种操作难以解决的,我们可以采用证明的方法消掉其中的一种操作,可以使用分解的方法拆分操作,两个变量想不通就换两个变量想一下不要一棵树上吊死,其实题目真的没什么关键是脑子要开窍;

P1850

期望DP,方程显然,最优子结构极其明显,但是值得注意的是,邻接矩阵储存图的时候要注意重边,还有就是floyd有可能爆int的。。。。。。。还有就是DP转移的时候边边角角细节一定要多多去注意一下,不然就嗝屁了;

P3960

这个题目很脑残,妈的;
80分暴力还是挺好想的;
正解有两种做法;
1无脑动态开点,动态开点可以适用于有大量的不需要用到的空间的情况能将空间复杂度变成对数级别,1E5的数据范围下几乎是不成问题的;
2曾经想到过这种做法,但是因为一个细节就没有继续想下去了,思路还可以,没有很狭隘,至少能和正解搭到点边,正解很神奇啊,方法及其巧妙。
upd20dayslater
忘了;

P7114

非常狗屎一道题目,贼卡常还;
其实内容真的没什么,内容完全可以根据部分分给出的思路去做,层层递进,发现题目内容的一层层性质,然后才有机会解出答案;
本题是计数题,一些重要的信息提前给处理好,看看能不能连带;
循环技术的时候寻找一下规律,或者直接总结一下通式,这样就能省下一层的复杂度;
然后就是码力一定要非常好,思路一定要非常清晰,这样才能打的又快又好;
梳理一下本题的思路;
首先想到一下直接枚举一下AB,三次方复杂度,发现可以先处理出那个前缀,于是我们可以省下好多时间,然后关于每个子串里面的出现奇数次数的东西,我们可以提前预处理好,这个就是莫队的思想;
然后就可以了;然后我们继续优化一下,发现i>1的时候res的dlt只会有两种情况,我们应该是从奇数次这个关键词想到可以这么去做的;
调和级数复杂度;

P7076

非常水的一道题,就是要注意一下特判;

P7077

非常好的一道题,很有思维含量,因为这个题解都讲得不明不白所以想了我一个晚上,想到了很多很有意思的东西;
首先第一个是运算的topo性,人类在发明四则运算的时候定义了运算的优先级,相应也就产生了运算的topo性,这个名词是我自己瞎造的,为什么说是topo呢,举一个典型的例子,一个有向无环图的管道系统,我们网后面的管子里灌水,我们会发现水不会倒流回去,所以我们在进行运算的时候,我们最开始加上去的值,一定会影响后面时刻的数列,但是后面加上去的不会影响前面时刻的数列;
所以对于这种难以维护的东西,我们不能想着造出个数据结构去维护,而是可以想一想别的模型,能否套上这个题目里面去做。
然后计算的问题解决了。显然我们先要把所有数的乘积给算出来先,然后所有初始值都要乘以这个mul,对于乘的问题我们就这么解决了。然后还有一个运算+,如果说我们把所有的操作给转化成是最基础的操作,也就是没有递归,形成一个树形结构,那么我们显然只需要只搞叶子节点,倒序搞一遍就行了,关键是现在不能倒序搞一遍,因为我们 展开之后,基础操作的数量将会非常大。那么单独考虑一下每个函数被调用的次数,用 f [ i ] f[i] f[i] 表示函数 i i i 被调用的次数,显然,我们设置一个递归出口 f [ 0 ] = 1 f[0]=1 f[0]=1 ,接下来讲不太清楚到底是什么内容,只能上个代码:

LL dfs2(int u) {
    if (~f[u]) return f[u];
    f[u] = 0;
    for (int i = 0; i < H[u].size(); i++) {
        int v = H[u][i].first, w = H[u][i].second;
        f[u] = (f[u] + w * dfs2(v)) % mod;
    }
    return f[u];
}

就是说,只有3号类型的函数会往下连边,所以我们只要搞清楚每个3号函数分别被调用了几遍,就能知道最基础的函数一共被调用了几遍,非常妙,我们之前一步算出来了一个后面的乘积,作为是一个影响值,算出来了之后,我们存在了边上而并没有存在点上,是因为我们把它所有操作展开之后,同一个函数会被调用很多遍,如果存在点上确实是一件非常不妥的事情。在建立模型的过程中,我们应该想清楚边和点的意义分别是什么,然后再搞,其实这个题目还是一个抽象建模的过程,非常抽象,很多概念也非常抽象,我觉得最难的一步就是将我们第一步所说的一个树型结构转变为一个dag,树和dag其实很有相通之处,你树的叶子节点里面连条有向边就是一个典型的dag,之前的难点就在于叶子节点太多了,考虑到大部分都是重复的节点,那么我们就可以考虑一个dag,考虑把那个后面的乘积给存在边上,这样似乎就会好很多。
体会一下这题背后的东西还是很烧脑的;

#include<iostream>
#include<cstdio>
#include<vector>
#include<algorithm>
#include<cstring>
using namespace std;
#define int long long 
const int INF = 0x3f3f3f3f;
const int mod = 998244353;
const int N = 1e5 + 5;
int a[N], op[N], b[N], c[N];
int d[N], f[N];
vector < pair<int ,int > > g[N], h[N];
int dfs(int u) {
	if (~d[u]) return d[u];
	d[u] = op[u] == 2 ? c[u] : 1;
	int s = 1;
	for(int i=g[u].size()-1;i>=0;i--) {
		int v = g[u][i].first;
		g[u][i].second = s;
		d[u] = d[u] * dfs(v) % mod;
		s = s * d[v] % mod; 
	}
	return d[u];
}
int dfs2(int u) {
	if (~f[u]) return f[u];
	f[u] = 0;
	for(int i=0; i < h[u].size();i++) {
		int v = h[u][i].first, w = h[u][i].second;
		f[u] = (f[u] + w * dfs2(v)) % mod; 
	}
	return f[u];
}
int n,m,q;
signed main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
	scanf("%lld",&m);
	for(int i=1;i<=m;i++) {
		scanf("%lld",&op[i]);
		if (op[i] == 1) {
			scanf("%lld%lld",&b[i],&c[i]);
		} else if (op[i] == 2) {
			scanf("%lld",&c[i]);
		} else {
			int t;
			scanf("%lld",&t);
			while (t -- ) {
				int v;
				scanf("%lld",&v);
				g[i].push_back({v,0ll});
			}
		}
	}
	scanf("%lld",&q);
	while(q -- ) {
		int v;
		scanf("%lld",&v);
		g[0].push_back({v,0ll});
	}
	memset(d,-1,sizeof d);
	memset(f,-1,sizeof f);
	dfs(0);
	for(int i=1;i<=n;i++) {
		a[i] = a[i] * d[0] % mod;
	}
	for(int u=0;u<=m;u++) {
		for(int i=0;i<g[u].size();i++) {
			int v = g[u][i].first,	w = g[u][i].second;
			h[v].push_back({u,w});
		}
	}
	f[0] = 1;
	for(int i=1;i<=m;i++) {
		if(op[i] == 1 ){
			a[b[i]] = (a[b[i]] + dfs2(i) * c[i] ) % mod;
		}
	}
	for(int i=1;i<=n;i++) {
		printf("%lld%c",a[i]," \n"[i==n]);
	}
	return 0;
}

这个便是代码了;

P7075

用了两个小时才切掉,非常烦人的一个大模拟练练手,只要在草稿纸上大体的把步骤列出来,然后写完之后检查一下代码的一些小细节对不对大概就八九不离十了,break不要乱用;

P2467

因为另一道题目来复盘这道题目寻找灵感;
是DP;非常精妙的一个最优子结构,现在来分析一下这个题目的做法;
首先最好想到的一个做法肯定是全排列,但是全排列那个函数我用不来,写一遍好了;

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int n, a[4010], ans, mod;
int main()
{
	cin >> n >> mod;
	for(int i=1;i<=n;i++) a[i] = i;
	do{
		bool flag = 1;
		for(int i=2;i<n;i++) flag &= ((a[i-1] < a[i]) != (a[i] < a[i+1]));
		ans += flag;
	}while(next_permutation(a+1,a+1+n));
	cout << ans % mod << endl;
	return 0;
}

然后有一种方法拿40分,是状压DP,这我是没有想到的,如果真的拿不到满分可以考虑用DP拿部分分,无非就是记录一下哪些数已经用过了哪些数没有用过;

我在想的时候想到了区间DP,可是没有完全想出来,因为区间DP难以表示在你那个序列里面有什么是用过的有什么是没有用过的,所以这种方法就被我叉掉了;

这里涉及到的就是信息的表示和存储问题,如果说我们不能用状压来表示信息,那么我们可以考虑一下用别的方法来存储信息,比如说 i i i 表示 1 − i 1 - i 1i 所有的数都被我们给用过一次了,这样不就好了嘛;
接下来上正解的代码;

#include<iostream>
#include<cstdio>
using namespace std;
const int N = 4210;
int n, mod, c[N][N], f[N];
int main()
{
	scanf("%d%d",&n,&mod);
	for(int i=0;i<=n;i++) {
		c[i][0] = 1;
		for(int j=1;j<=i;j++) {
			c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod; 
		}
	}
	f[0] = f[1] = 1;
	for(int i=2;i<=n;i++) {
		for(int j=1;j<i;j+=2) {
			( f[i] += 1ll*f[j]*f[i-j-1]%mod*c[i-1][j]%mod ) %= mod;
		}
	}
	printf("%d\n",f[n]*2%mod);

	return 0;
} 

直接看一下那个状态转移方程,发现这个东西非常妙, f [ i ] f[i] f[i] 表示的是选 i i i 个数时候产生的相应的排列的数量,我们发现对于选取的这些数,不管它有多大,一定可以排成一个离散化的序列,这个序列对应的排列方法也是 f [ i ] f[i] f[i] ,很妙,然后对于这些数里面,我们用组合数来求出有序的离散序列的数量,然后用最大的数字来分割,这样子刚好很巧妙的避免了不合法情况的尴尬,我们只要去找一下有没有东西怎么插都合法就能想到这条路上了。
然后还有一个点,就是最后的乘以2,如何证明这之间一定是两倍关系的,我们用集合的方法。
对于每一种排列中的每个具体元素,都有唯一的 n − a i n-ai nai 与其对应,形成了另一种形状的排列,每个都是单射,于是两个集合里面元素的数量相等;

P5689

果然我的直觉非常准确啊,这题和上面一个题目的做法基本上不存在什么明显的区别,唯一的可以拿出来的讲的不同就是这题把原先的一个序列换成了是一棵树,无非是迭代方式的改变;
具体的状态表示都很上面一提一模一样,然后组合数也是一样的,无非是选出一个更小的数字出来做为堆顶,然后这题就被我们解决了;

P5687

部分分非常水,64分居然能直接暴力得到,还他妈T3。。。
从这道题可以看出来其实我的思维还是比较混乱,不过做出来了还是好的。这题暴力简单是因为边的数量非常少,但是要是想要拿满分的话,难就难在边很多,然后之前做过一道题目,是完全图里面找最小生成树,方法就是贪心,每次贪一堆出来;
这题也是差不多的,但是区别在于上次做的那道题是完全图,合法性不用怎么考虑但是这题不是完全图,要考虑生成一个矩形的情况;
我们直接将所有的边进行排序,横边竖边一起排,然后从小到大的去选上每一行或者每一列,然后每一行或者每一列第一个选的,不需要考虑不合法,因为那些还没串在一起,然后对于接下来的每一个,选进去的时候考虑一下另外一种形状的边一共有几条,然后就好了,一直选到 n ∗ m − 1 n*m-1 nm1 为止;
然后关于做法的正确性我们可以给出证明,如果存在更优解,那么一定是用我们之前舍弃了的更好的边去替换一个垃圾一点的边,但是实际上这样不行,因为替换了之后就直接形成一个环了;
所以这一定是最优解;
题解也是这样的,里面有一句话说的很好就是:

那么我们可以发现64pts的做法事实上非常浪费,因为其实只有n+m条不同的边,那么我们可以只对这n+m条边排序即可。

这句话揭示出了我们可以这么做的本质;

P5020

经典老水题。证明挺好想的;就是 ∣ = |= = 的细节要注意一下,这个点光看样例还发现不了,然后我们就可以乱杀了;

P1156

大水题,美利坚来的蓝题真的都很水。。。跟联赛的蓝题相比实在是太逊了。
提供非AC代码,少了一步但是我懒得改了;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define int long long
const int N = 1e3 + 100;
int d, g, t[N], h[N], l[N];
int f[N+1000][N+1000];
signed main()
{
	scanf("%lld%lld",&d,&g);
	
	for(int i=1;i<=g;i++) {
		scanf("%lld%lld%lld",&t[i],&l[i],&h[i]);
	}
	memset(f,-0x3f,sizeof f);
	
	f[0][10] = 0;
	
	for(int i=1;i<=g;i++) {
		for(int j=0;j<N;j++ ){
			if (j + t[i] - t[i-1] - l[i] >= 0 ) 
			f[i][j] = max(f[i-1][j + t[i] - t[i-1]]+ h[i],f[i-1][j + t[i] - t[i-1] - l[i]] );
			else f[i][j] = f[i-1][j+t[i] - t[i-1]]+ h[i]; 
//			if(j <= 10)		
//			printf("f[%lld][%lld]:%lld\n",i,j,f[i][j]);  
		}
	}
	
	for(int i=1;i<=g;i++) {
		for(int j=0;j<N;j++) {
			if(f[i][j] >= d) {
				printf("%lld\n",t[i]);
				return 0;
			}
		}
	}
	
	int res = 0;
	
	for(int i=1;i<=g;i++)	res += l[i];
	
	printf("%lld\n",res);
	
	return 0;
}

HHHOJ612

这题是个非常好的计数题,因为我不会;
首先我们考虑只有AB的情况,这种从小到大的思想方法非常重要;
首先他说删AB,AB太抽象了,于是我们采用反转的方式,将奇数位或者说是偶数位的AB给翻转一下,这样就行了,我对我没想到这一点的错误表示非常后悔和遗憾,然后翻转之后什么东西能够删完就是显而易见的东西了,然后我们再搞一下C,C可以代替任意A或者是B,然后自己看题解吧,没什么了,就是第一个AB的情况特别难想到,翻转一下,还要我继续去思考一下这里面的内涵到底是什么;

首先考虑不存在 C 的情况。此时,若把奇数位上的 A 变成 B、B 变成 A ,就可以发现,当且仅当 A 和 B 的个数相同时,这个字符串可以删空。

同时,由于改变后的字符串与原字符串一一对应,所以只要求出改变后的字符串的个数,就可以求出答 案。

再考虑存在 C,由于 C 可以视为 A 或 B 中的任意一种,因此只要满足 A 和 B 的个数之差个数小于等于 C 的个数,就可以删空。
这可以容斥,即求出 A 和 B 的个数之差大于 C 的个数的方案数,然后用总方案数将其减去。则此时必然存在 A 或 B 中的某一种有 i 个(i>n2),而剩余 n−i 个位置可以任意填另一种字母和 C 。即此时的方案数为 Cin×2n−i。
因此预处理阶乘及阶乘逆元,枚举 i 统计答案即可。

原来题解内容;
upd1daylater
昨晚回去又仔细想了一下这道题,为什么可以用这种方法去做,其实很奇妙。
将这个原有信息进行翻转的题目还有另外一道,就是求最大正方形的升级版,还有求最大矩形的升级版,都是要将奇数或者是偶数位上的东西进行翻转从而更加方便的转化为弱化版的问题,为什么说可以这么做呢;
以最大正方形那题为例子,我们想想,给我们一个01相间的正方形,和只有1或者是0的正方形,他们所含有的信息是不一样的,01相间的不仅含有0的个数1的个数,更要保证他们的位置关系是相间的,而不是说合并在一起的那么一个形状,但是只有0或者是1的正方形,你只需要保证全是1或者全是0就好了,我们在DP的过程中能维护的信息是有限的,所以我们一定要寻找到一种方法,使得我们需要尽可能少的信息,就可以推出尽可能多的结论,这个是一个算法的大方向,它是不会变的,就是用手中有的信息和少量的信息推出多的信息;这个过程的途径就是各种算法,和循环和运算。
那回到我们这个正方形的题目,因为我们无法维护,所以我们不妨将题目换一种问法,就变成你喜欢的求只有01的正方形的问题,怎么实现这种方法,我们就发现可以翻转,通过数学证明实现信息上的等价的转换,然后自己快乐去;
那么回到这个题目,我们发现直接求的方法确实非常的不简单,因为考虑到的要素确实非常多,比如说AB的数量和排列,难以总结出规律,这时候我们通过数学证明,然后将信息变得简洁明了,然后就这道题就寄了;
以上就是一个完整的解释过程;

HHHOJ717

这题非常的有意思啊,当你做出来的时候不难发现这题的模型其实十分的简洁明了,就是一个嗦点,嗦点之后的模型显而易见是一个简单图,然后染色即可;
但是出于我做题时心太急了,想了个一知半解就去写,结果就是写出来一堆狗屁,逻辑非常混乱,有些步骤我甚至难以解释为什么应该那么去做,自己都搞不明白,怎么可能把这题给写出来呢,从前到后叉了自己两次,写到后面自己都要哭出来,然后稍微停了一下才想到有这种模型;
这题出的确实是非常的好,从LCP到并查集到图论,涵盖了非常多的内容,如果我真正的考场上也碰到这种题目但是想不出来的时候不妨去看一下大样例,不难发现里面都是0或者是1,这么去想的话能想到图论的模型其实也可以算得上是有据可依而不至于很突然;
还是要感叹一下,很好的一道题呵,够抽象;
O ( N 2 ) O(N^2) O(N2) 的时间复杂度,对应到图论题上面的点,刚好差不多就是这个数量,然后嗦点的话其实也差不多,可以考虑一下;

HHHOJ718

这题我他妈的题目看错了, n ∗ m n*m nm,看成是 n , m n,m n,m 了,我就在想满分情况他妈的怎么可能写得出来呢?遍历一遍就他妈超时几百倍了,我写的时候也写错了,不然直接A2题起飞;
没啥特别的内容无非就是维护一下联通块,怎么说呢,因为大部分的点,他们所具备的信息只有坐标不同,也就是说他们的信息具有相似性,因此再抽象出来一个集合把这些点搞进去;
看到有55分的拿tarjan去嗦这题,虽然我不知道为什么这么做会被卡掉,但是有简单的方法的时候为什么要自己给自己找麻烦呢,毕竟这个图要找的不是强连通分量也不是双联通分量只是联通分量,联通就行了,搜一遍是不是更好的一种选择呢;
下面那个C题想到斜率了,但是我不会李超树,所以截止于此了呀;
D题的话看下下面这张图就够了;

在这里插入图片描述

CF1602B

做的时候我也不知道怎么想的,然后就发现n次操作就能草过这题。然后就A了,具体细节就是,acm赛制很卡时间卡空间,别他妈没事瞎勾八乱开longlong就行了;
这个结论可以由水样例和那个 1 e 9 1e9 1e9 的伞兵数据范围得出;
然后我们发现其实有这么多个数,每个数的出现次数必然只有一种,那么不同出现次数的必然至多是n种,接下来有两种情况,第一种是n种出现次数,比如6 6 7 7 7 8 8 8 8 ,下一轮就是2 2 3 3 3 4 4 4 4,是吧,搞不动了,然后对于另一种情况,接下来的出现次数只会变成是n-1种甚至是更少,然后就这道题就寄了;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std; 
const int N = 2010;
int a[N][N];
int vis[N];
int T, n, q;
signed main()
{
	scanf("%d",&T);
	
	while(T -- ) {
		
		memset(a,0,sizeof a);
		scanf("%d",&n);
		memset(vis,0,sizeof vis);
		
		for(int i=1;i<=n;i++) {
			scanf("%d",&a[1][i]);
			vis[a[1][i]] ++ ;
		}
		
//		puts("VIS");
//		for(int j=1;j<=n;j++) {
//			printf("%lld ",vis[j]);
//		}
//		puts("");
		
		for(int i=2;i<=n+2;i++) {
			for(int j=1;j<=n;j++) {
				a[i][j] = vis[a[i-1][j]] ;
//				printf("%lld ",a[i][j]);
			}
//			puts("");
			for(int j=1;j<=n;j++) vis[j] = 0;
			for(int j=1;j<=n;j++) {
				vis[a[i][j]] ++ ;
			}
//			puts("VIS");
//			for(int j=1;j<=n;j++) {
//			printf("%lld ",vis[j]);
//			}
//			puts("");
		}
		
		scanf("%d",&q);
		int x, k;
		
		while(q-- ) {
			scanf("%d%d",&x,&k);
			k++;
			if(k>=n+2) {
				printf("%d\n",a[n+2][x]);
			}else{
				printf("%d\n",a[k][x]);
			}
		}
		
	}
	return 0;
}
/*
2
7
2 1 1 4 3 1 2
4
3 0
1 1
2 2
6 1
2
1 1
2
1 0
2 1000000000
*/

HHHOJ722

这题非常精妙,精妙之处在于他不是正向去做的,而是反向去做的;
虽然我们想到这个方法,但并不妨碍我们去思考为什么可以想得到这个方法;
首先我们发现这题的询问是可以离线下来的,没问题,然后我们发现有关这道题目的信息的一些特点,首先,最远的距离,有一个定理提示我们,这一定是树的直径的某个端点,也就是说,找最远的点,我们只要把它那棵树的直径的端点拿出来看一下就好了。有关树的直径,这个信息非常有特点,两棵树的直径非常好合并,但是这难以拆解,一棵树断掉一条边,你没法在简单的几个操作中获取下一棵树的直径,是吧。这启示我们,直径和gcd,max,之类的信息很像,他是一个单向的操作而非是一个双向的操作。所以说,双向的操作,所需要的信息数量并不一定相等;
那么我们就可以反着来,然后用并查集草一下,lca来处理一下查询距离的操作就可以了;
这题虽然看起来很难,可是做起来才发现其实并不是那么的不可理喻或者说是无法理解,这启示我们考试时候第一题或者第二题如果出现了这种阴间题目,一定不要慌,仔细考虑一下某些小性质说不定就能解出来了;

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
#define debug puts("FUCK")
const int N = 2e5 + 100;
const int M = 4e5 + 100;
const int L = 20;
int f[N][L];
int vis[N];
struct option {
	int opt, x;
}op[N];
struct edge {
	int u,v;
}eg[N];
int fa[N], c[N][3], ass[N], len;
int e[M] , ne[M] , h[N] , idx;
void add(int a,int b) {
	e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
int dio[N], dep[N], n, m;
void dfs(int u,int fa) {
//	printf("u:%d\n",u);

	dep[u] = dep[fa] + 1;
	f[u][0] = fa;
	
	for(int j=1;j<=len;j++) {
		f[u][j] = f[f[u][j-1]][j-1];
//		printf("%d    %d\n",f[u][j-1],f[f[u][j-1]][j-1]);
	}
	for(int i=h[u];~i;i=ne[i]) {
		int j = e[i];	if (j == fa) continue;
		dfs(j,u);
	}
	return;
}
int lca (int x,int y) {
	if (dep[x] > dep[y]) swap(x,y);
	for (int i=len;i>=0;i--) {
		if ( dep[f[y][i]] >= dep[x] ) y = f[y][i]; 
	}
	if (x == y) return x;
	for (int i=len;i>=0;i--) {
		if(f[x][i] != f[y][i]) x = f[x][i] , y = f[y][i];
	}
	return f[x][0];
}
int dist (int u,int v) {
	return dep[u] + dep[v] - 2*dep[lca(u,v)];
}
int get (int x) {
	if ( x == fa[x] ) return x;
	return fa[x] = get (fa[x]);
}
void fuck (int u,int v) {
	if (dep[u] < dep[v]) swap(u,v);

	
//	printf("u:%d   v:%d   getu:%d   getv:%d\n",u,v,get(u),get(v));
	int tmp[6] = {c[get(u)][0],c[get(v)][0],c[get(u)][1],c[get(v)][1]};
	int sum = 0,a,b; 
	for(int i=0;i<=3;i++) {
		for(int j=0;j<=3;j++) {
			a = tmp[i], b = tmp[j];
			int ne_dist = dist (a,b);
///			printf("a:%d   b:%d   dist:%d\n",a,b,ne_dist);
			if (ne_dist > sum) {
				sum = ne_dist, c[get(v)][0] = a, c[get(v)][1] = b; 
				dio[get(v)] = sum;
  			}
		}
	}
	
//	printf("sum:%d   c1:%d   c2:%d\n",sum,c[get(v)][0],c[get(v)][1]);
	fa[get(u)] = get(v);	
}
int main()
{
	memset(h,-1,sizeof h);
	
	scanf("%d%d",&n,&m);
	
	len = log(n) / log(2) + 1;
//	printf("len:%d\n",len);
	
	for(int i=1;i<=n;i++) c[i][0] = c[i][1] = i;
	
	for(int i=1;i<=n;i++) fa[i] = i;
	
	
	for(int i=1;i<=n-1;i++) {
		scanf("%d%d",&eg[i].u,&eg[i].v);
		add(eg[i].u,eg[i].v), add(eg[i].v,eg[i].u);
	}
	
	dep[0] = -1;
	
	dfs(1, 0);
/*	
	for(int i=1;i<=n;i++) {
		printf("%d   %d   %d\n",f[i][0],f[i][1],f[i][2]);
	}

	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) {
			printf("i:%d   j:%d    val:%d\n",i,j,lca(i,j));
		} 
	}
*/	
	for(int i=1;i<=m;i++) {
		scanf("%d%d",&op[i].opt,&op[i].x);
		
		if(op[i].opt == 1) {
			vis[op[i].x] = true;
		}
		
	}
	
	for(int i=1;i<=n-1;i++) {
		if(!vis[i]) {
			int u = eg[i].u, v = eg[i].v;
			fuck(u,v);
		}
	}
	puts(""); puts("");
	for(int i=m;i>=1;i--) {
		
		if (op[i].opt == 1) {
			int x = op[i].x;
			int u = eg[x].u , v = eg[x].v; 
			fuck(u,v);
			
		} else {
			int x = op[i].x;
			ass[i] = max ( dist( c[get(x)][0],x) ,dist(c[get(x)][1],x) );
//			printf("%d   %d   %d    %d\n",i,op[i].x,get(op[i].x),dio[get(op[i].x)]);
		}
	}
	
//	debug;	
	
	for(int i=1;i<=m;i++) {
		if (op[i].opt == 2) {
			printf("%d\n",ass[i]);
		}
	}
	
	return 0; 
}
/*
14 14
4 7
4 1
3 1
3 6
1 2
2 5
8 5
9 5
13 8
14 8
11 9
10 9
10 12

1 9
2 5
1 10
2 10
1 2
2 8
1 5
2 7
1 8
2 1
1 13
2 4
1 4
2 6
output:4 6 5 1 2 1 0
*/

这题他妈的调了我三个小时,一方面说明我的习惯还是太烂了,局部变量拿来比较大小的连个初始值都懒得赋值,还有就是lca打错了,还有就是对于代码里面具体内容的理解实在是不清楚,很模糊,变量经常用错;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值