对于清北学堂2018国庆刷题班的学习总结

(10.1)

例题1: 最多因子数(Link

给定一个区间([L, R]),要求求出在区间内拥有最大因子数的数。

定义(F(X))(X)的因子个数函数。首先我们要知道,对于任意一个数(Data[i])都有一个基本的分解性质如下:(color{red}{Data[i] = Prime_{1}^{m_{1}}Prime_{2}^{m_{2}}...Prime_{n}^{m_{n}}}),那么我们可以得到:(F(Data[i]) = (m_{1} + 1) * (m_{2} + 1)...(m_{n} + 1)),那么我们就将搜索的过程由因数分解转化成了质因数相乘。那么我们在搜索的时候就只需要搜索质因数就可以了。然后我们只要记录一下质因数的个数就可以了。
下面说一下剪枝:
我们知道过程大体如下:

  1. 搜索每一个质数,这里我们可以按照递增顺序。
  2. 枚举这个质数的指数。那么如果我们当前的枚举的质数算上指数的乘积为X, 然后下一次枚举的时候的指数为(Y), 如果(X * Y >) 区间右端点 那么我们可以直接(return), 这个当然是很显然易见的。
  3. 如果当前枚举的这些质数算上指数的乘积在L和R之间,那么代表有可能更新答案,而如果R - L < X我们也可以直接(return)了,因为我们接下来的搜索一定搜不到在L和R之间的数,所以我们直接跳出。

例题2 : 生日蛋糕(Link

现在你要造一个由(M)层圆柱体组成的总体积为(N pi)生日蛋糕,并且要求从上向下满足圆柱体高度和半径递增。要求最小化除去下表面的蛋糕外表面积。

肯定想到搜索。从下往上直接深搜,参数有正在搜索的层数(Now),现在搜到的总侧面积(S),现在搜到的总体积(V)每次枚举高度(H[i])和半径(R[i])。这里要记得公式:(V = pi R^2 H, ~S_ ext {侧} = 2 pi R H, ~ S_ ext {底} = pi R^2)。期望得分(20)

inline void Search(int Now, int S, int V) {
	//参数: Now : 正在搜索的层数, S : 总侧面积, V : 总体积。 
	if(Now == M + 1) {
		if(V == N) Ans = min(Ans, S + R[1] * R[1]) ;
		return ;
	}
	for (int i = 1 ; i <= H[Now - 1] - 1 ; i ++) // 枚举 H
	for (int j = 1 ; j <= R[Now - 1] - 1 ; j ++) // 枚举 R 
			int A = 2 * i * j, B = i * j * j ;
			H[Now] = i ; R[Now] = j ;
			Search(Now + 1, S + A, V + B) ;
			H[Now] = R[Now] = 0 ;
		}
}

考虑剪枝。

(1.)我们知道,最小并且不考虑合不合法情况下,每一层都要有1的高度和半径.因此每次枚举的时候最小值一定是从(M - Now + 1)开始。(上下界剪枝

(2.)第二,每一次枚举的顺序显然可以从大到小枚举。(搜索顺序优化

(3.)第三,几个最常用的剪枝。如(S > Ans || V > N)我们显然(return~;) 然后我们在搜索的时候进行一个判断。当前层再往上的最小情况显然是从上往下(1, 2, 3 ....),然后我们相加,这就是从(Now)层开始的最小情况。如果(S)(V)加上这个最小情况依然大于(Ans)那么就可以(return)。同理,最大情况显然是(...Now -2, Now -1, Now)那么如果这些情况的和依然小于(N),那么(return)。(可行性剪枝

(4.)


(10.2)

(Morning)


(Problme) (A) : (distance)

给你一个有n个点,m条边的无向图(G = (V, E)),满足此图中不存在长度大于等于(3)的环,且没有重边自环。给定两个点(u,v)的距离(d(u, v))为两点之间最短路的点数。求
(min_{uin{V}}max_{vin{V}}d(u, v))

嗯,我们首先可以很显然地从没有大于等于三的环看出这确实是一个树。然后我们去理解(min_{uin{V}}max_{vin{V}}d(u, v))的意思。其实也就是说我们现在有一个最短路的函数为(d(u,v))表示(u)(v)之间的最短路。然后对于一个给定的(u)节点,我们要找到一个(v)使得这个(d(u, v))最大,那么我们假定上面这个函数为F[u],然后再把固定的u变成变量,然后求最小值,就是这个玩意。而我们发现这实际上也就是要我们求关于这个树的直径的一些东西。然后我们画几个图稍加证明便可以得到答案就是数的直径(*2)的上取整。那么求树的直径就比较简单了,也就是随便选一个点,跑两遍最长路就完了。在树上也就是两个(BFS),比较懒的写(SPFA)就好了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define MAXN 100010
#define Inf 0x7fffffff
#define LL long long
using namespace std ;
int Read(){
	int X = 0 ; char ch = getchar() ;
	while(ch > '9' || ch < '0') ch = getchar() ;
	while(ch >= '0' && ch <= '9')
	X = (X << 1) + (X << 3) + (ch ^ 48), ch = getchar() ;
	return X ;
 }
 int N, M, Num, Sum ;
 struct NODE{
 	int From, To, Next ;
 }Edge[MAXN << 1] ; 
int Head[MAXN], Total ;
inline void Add(int F, int T){
	Total ++ ;
	Edge[Total].From = F ;
	Edge[Total].To = T ;
	Edge[Total].Next = Head[F] ;
	Head[F] = Total ;
} 
inline Dfs(int Now, int Fa, int Step){
	//搜索最长路啦 
	if(Step > Sum) Sum = Step, Num = Now ;
	for(int i = Head[Now]; i; i = Edge[i].Next)
	if(Edge[i].To != Fa) Dfs(Edge[i].To, Now, Step + 1) ;
 }
 int main(){
	freopen("distance.in", "r", stdin) ;
	freopen("distance.out", "w", stdout) ;
	N = Read() ; M = Read() ;
	for(int i = 1; i <= M; i ++){
		int F = Read(), T = Read() ;
 		Add(F, T) ; Add(T, F) ;
	}
	Dfs(1, 0, 1) ; Dfs(Num, 0, 1) ;
	//连着搜索两次 然后输出上取整就完了。 
	if(Sum % 2 == 0) printf("%d", Sum >> 1 | 1) ;
	else printf("%d", (Sum + 1) >> 1) ;
	fclose(stdin) ; fclose(stdout) ;
	return 0 ;
 }

(Problem) (B) : sweet(Link

求一个(n)(m)边的带权无向图上的一个由(S)(T)的路径使该路径上的最大权值边上的权值与最小权值边的权值的比值最小。

这个题的边数m还是比较少的,只有5000左右。所以我们考虑将所有的边(V)按照权值(w)排序。到(V_{1})(V_{2})(V_{3})(V_{4}),然后我们从小到大枚举(S)(T)上面的(V_{k}),然后我们要从(V_{k})(V_{k + 1})...(V_{m})中找到一条从S到T的最大值最小的路径。
我们现在要在一个加权无向图中求一棵最大值最小的生成树,那么我们想到最小瓶颈路,那么我们已经排完了序,那么我们就直接(Kruskal)就行了。当我们加入某条权值为(V_{Now})的边的时候发现S和T第一次在一个并查集里面了,那么这个最大值最小的边就是(V_{Now})
(frac{w(V_{Now})}{w(V_{k})})


(Problem) (C)

天天爱跑步(Link


(Afternoon~ &~Night)


区间(DP)

例题1

首先你有M个矩阵大小分别为
([N_{0} imes N{1}] , [N_{1} imes N_{2}]...[N_{M - 1} imes N_{M}]),要求你求出这些矩阵乘积的最小次数。

我们设(F[L][R])为第L个矩阵和第R个矩阵的乘积所需要的最小的代价。那么我们知道当我们计算(F[L][R])的时候我们中间一定有一个断点(K),然后将整个([L,R])区间分为([L, K])([K + 1, R]),然后我们相加就是答案之一,然后我们还要计算所有东西相乘所要求的代价,而矩阵的相乘起来的列数和行数也就是最左边的列数和最右边的行数。那么代价就是(N_[L - 1] imes N_[K] imes N_{R})。那么我们得到了状态转移方程式。
(F[L][R] = min_{L <= K <= R}(F[L][K] + F[K + 1][R]) + N_[L - 1] imes N_[K] imes N_{R})

但是要注意代码不可以这么书写:

for(int L = 1; L <= N; L ++)
	for(int R = L; R <= N; R ++)
		for(int K = L; k <= R; K ++)
			F[L][R] = ......

在这种代码里面(L)是从小到大枚举的。那么当你枚举(L)的时候,任何以比(L)大的数为左端点的(F)都没有求出。所以(F[K + 1][R])是没有求出的。所以这种求法是错误的。然后我们应该在最外面枚举区间长度,里面枚举端点,在里面枚举(K)

for(int Len = 2; Len <= N; Len ++)
	for(int L = 0, R = L + Len; L <= N, R <= N; L ++, R ++)
		for(int K = L; K <= R; K ++)
			F[L][R] = ......

树形(DP)

你现在有一个(N)个点的树,要求你求树上每一个点到其他所有点的路径的和的和。

数位(DP)

状压(DP)

现在有N个点的坐标分别为((X_{1}, Y_{1}), (X_{2}, X{2})...(X_{N}, Y_{N}))要求从(1)号点出发了,走完所有的点的最小距离为多少。

我们设(F[i][j])表示走过了i这个集合的所有的点并且最后停在j号点的最小距离。换句话说,我们用一个(01)串表示状态,对于每一个(i)点,如果这个串是(1)表示走了,反之没走。那么(F[i][j])表示从1走到j并且状态为(i)的最小距离。那么我们就可以实现状态转移了。


(10.3)

(Morning)


10.3 Morning

标签(空格分隔): Test


T1: Problme

有一个大小为(N imes M)棋盘上有K个小葱分别在第(X[i], Y[i])点上,面向(D[i]),战斗力为(F[i])。其中(D[i])({0, 1, 2, 3})分别表示上下左右。如果下一步将走出棋盘,那么小葱们会花费一秒钟的时间向后转弯,而每一次移动需要花费一秒。如果有一秒几个小葱碰到了一个点上,这几个小葱会打架,然后最后只有战斗力最大的能够存活,其它的小葱不会再移动。数据保证(F[i])和初始的(X[i],Y[i])。问(T)秒之后所有的小葱分别在什么位置。

这个题由于数据比较水,所以我们是可以直接暴力的。然后大体思路如下:首先我们要判断这个棋盘的边缘判断。如果不考虑其它因素,那么代码大概如下:

        if(Edge[i].X == 1 && Edge[i].D == 2){
			Edge[i].D = 3 ; continue ;
		}// 左
		else if(Edge[i].X == N && Edge[i].D == 3){
			Edge[i].D = 2 ; continue ;
		}// 右
		else if(Edge[i].Y == 1 && Edge[i].D == 0){
			Edge[i].D = 1 ; continue ;
		}// 上
		else if(Edge[i].Y == N && Edge[i].D == 1){
			Edge[i].D = 0 ; continue ;
		}/ 下

这很简单。然后我们考虑没撞到的情况,是要向前走一步的。这个也很简单。但是我们要考虑到这个打架的问题,那么我们要利用一个二维数组进行记录当前节点有哪根葱占着。假设当前的(X[j], Y[j])是被谁(U)占着了。注意这里的U是已经移动过的小葱。因为我们对于每一根葱的枚举不可能同时进行,我们在进行枚举的时候虽然有一定顺序,但也不要忘了他们的移动是并列的同步进行。也就是说之前的一个节点(U)移动过之后到达了点(X[j], Y[j]),然后我们当前枚举到了小葱i,那么我们就要用到(Map[a][b])表示当前棋盘上的点((a, b))是被拿一根葱占着。然后我们就比较(i)的战斗力和(Map[X[j]][Y[j]])的战斗力。
如果当前点i被干掉了,那么我们记录一下i的死亡位置(Ans),在这里我们可以使用(STL)中的对数函数(pair<int, int> Ans[MAXN])来记录横纵坐标。然后设置一个(Flag[i]) (=) (0)表示i这个点的存在消失,下一个(T--)的时候进行的枚举以及打架过程便不再对i进行。而如果i赢了,那么(Map[X[j]][Y[j]])执行同样的操作,然后我们更改(Map[X[j]][Y[j]]) (=) (i)就可以了。

T3

令F[i]为从第1个点走到第i个点的方案数。那么F[1] = 1 ;
i的转移方案可以从[1,i]之间的任意一个j点转移过来,那么从j到i的方案数为从i到j的边数也就是(A[j]) & (A[i]),然后我们再乘上从1到j的方案数。那么总少可以得出一个DP方程:(F[i] =sum A_{i})&(A_{j} * F[j])
但是这样的时间复杂度大概在(O(N^{2}))左右,并不能过掉所有的数据。
然后可以考虑玄学优化。干到(O(200N))


(Afternonn ~ & ~ Night)


10.3 Evening

标签:Note


Problem 3

(N)个点(M)条边的带权无向图,询问那些边一定在最小生成树上,那些边一定不在最小生成树上。

我们将所有的边分为三类:一定在,可能在,一定不在。
那么我们首先建出一个最小生成树。然后我们知道在这个树上的边要么是一定在,要么是可能在。那么我们考虑一个最小生成树之外的边(V1),然后我们知道这个边会使这个最小生成树形成一个环。我们找到这个环上面的最大的边(V2),那么(V1 >= V2), 我们考虑相等的情况,可以发现,如果用(V1)去替换(V2),其实没有什么区别,所以(V1)(V2)都是可能在。然后如果(V1) (>) (V2)便是一定不在了。

算法流程:

  1. 随意构造一颗最小生成树。
  2. 按边权顺序枚举剩下的边。
  3. 与树上构成的环边进行比较判断。
  4. 用并查集维护做到(O(N))

Problme 5

(N)个点(M)条边的带权无向图,有(Q)个询问,问每次删除一条边后的生成树大小。(访问相互独立)其中(N) (<=) (50000), (M) (<=) (100000)

当然首先还是构造一个最小生成树,然后我们判断要删除的边是不是在树上,如果不在,大小不变。如果在树上,我们就要找到一个树外最小的一个边来替换这个树上的被删边,那么就和上面的题一样可以用并查集维护答案了。

算法流程:

  1. 构建最小生成树
  2. 如果被删边不在树上,不管。
  3. 如果在树上,使用并查集维护答案。

Problem 8(Link

(A)国:每一个人有一个友善值(Data[) (]),当两个人((Data[i]) ^ (Data[j])) (%) (2) (==) (1)的时候,(i), (j)是朋友
(B)国:不同于A国,当两个人的((Data[i]) ^ (Data[j])) (%) (2) (==) (0)的时候或者(i) (or) (j)。然后给你一些(A)国人与(B)国的一些人的朋友关系,问最大朋友圈的人数。

最大团问题是典型的(NPC)问题。(NPC)问题基本都是只能暴力,但是有一个意外:二分图上面的NPC问题。而二分图上面的最大团就是其补图的最大独立集。看到了NPC问题并且数据范围又不算小,那么就压考虑二分图匹配。
至于怎么二分图。首先考虑A国,我们知道当两个人((Data[i]) ^ (Data[j])) (%) (2) (==) (1)的时候,(i), (j)是朋友,那么最大团就只能有两个。因为如果i和j是朋友而第三个人k是奇数,那么k只能和其中之一为朋友,但和另一个不是。所以A的最大团只能有两个人,那么由于数据范围比较小,我们可以直接枚举。
那么B国的人呢?首先考虑第一个条件,我们知道了(B)国的所有偶数友善值的人都是朋友,所有奇数友善值的人都是朋友,那么我们将(B)图建成一个类似于二分图的形态,奇数在一边,偶数在一边,那么所有的奇数连起来,所有的偶数连起来。那么我们考虑第二个条件。因为奇偶两边都满了,所以最后一个限制只能在奇偶之间发挥作用。那么我们可以考虑匹配。但是这并不是一个二分图呀。所以我们考虑建补图,然后匹配后我们假设中间有(K)条边,奇数组有(Q)个数,偶数组有(P)个数,然后我们依照最大流最小割原理可以得到原图的最大团就是(P) (+) (Q) (-) (K)


(10.4)

(Morning)


10.4 Morning

标签(空格分隔): Test


(T1) (:) (r)

给一个数据组数T以及T组形如1926-08-17的日期,如果这个日期和1926-08-17的差值为质数或者0,输出"Niubi",否则输出"Haixing"。

(这个题一看就不可做) 模拟。完了。
总之呢,方法其一就是将两个日期中比较小的置为操作日期。然后就把较小的日期一步一步加到大的日期上面去。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 100010
#define Inf 0x7fffff
#define LL long long
using namespace std ;
int Read(){
	int X = 0 ; char ch = getchar() ;
	while(ch > '9' || ch < '0') ch = getchar() ;
	while(ch >= '0' && ch <= '9')
	X = (X << 1) + (X << 3) + (ch ^ 48), ch = getchar() ;
	return X ;
}
int Is_Prime(int Now){
	if(Now == 0) return true ;
	if(Now == 1) return false ;
	if(Now == 2) return true ;
	for(int i = 2; i * i <= Now; i ++)
		if(Now % i == 0) return false ;
	return true ;
}
int Get_Day(int Now, int Y){
	if(Now == 1) return 31 ;
	if(Now == 3) return 31 ; if(Now == 4) return 30 ;
	if(Now == 5) return 31 ; if(Now == 6) return 30 ;
	if(Now == 7) return 31 ; if(Now == 8) return 31 ;
	if(Now == 9) return 30 ; if(Now == 10) return 31 ;
	if(Now == 11) return 30 ; if(Now == 12) return 31 ;
	if(Y % 4 != 0) return 28 ;
	if(Y % 100 == 0 && Y % 400 != 0)
		return 28 ;
	return 29 ;
}
int T, Year, Month, Day ;
char Opt ; int Cnt ;
int main(){
	T = Read() ;
	while(T --){
		Cnt = 0 ;
		scanf("%d-%d-%d", & Year, & Month, & Day) ;
		int GreatY = 1926 ;
		int GreatM = 8 ;
		int GreatD = 17 ;
		if(Year > GreatY || (Year == GreatY && Month > GreatM) || (Year == GreatY && Month == GreatM && Day > GreatD))
			swap(Year, GreatY), swap(Month, GreatM), swap(Day, GreatD) ;
		while(Year < GreatY || Month < GreatM || Day < GreatD){
			Cnt ++ ; Day ++ ;
			if(Day > Get_Day(Month, Year))
				Day = 1, Month ++ ;
			if(Month > 12)
				Month = 1; Year ++ ;
		}
		cout << Cnt << " " ;
		if(Is_Prime(Cnt)) puts("Niubi") ;
		else puts("Haixing") ;
	}
	return 0 ;
}

(T2) (:) (q)

给定一个大小为2N的圈子,其中有N对人是基佬,要求任意两个基佬的位置不相邻,求方案数。

我们可以很快知道对于一个有N个人的圈子可以有的排列顺序为(N!),然后现在还有一些是可以经过令挖一些排序转转圈得来的,那么我们推算可以得到有重复的圈子种数是(2N!),然后没有重复段圈子总类数就是((2N - 1) !)。那么我们想要从总的圈子种数里面减去一些不合法的种类。那么我们首先考虑至少有一对基佬坐在一起的情况。那么我们可以将这一对基佬看成一个,那么就剩下了((2N - 1))个人,那么情况就有((2N - 2)!)种,然后这两个基佬可以互换位置,那么就是(2 * (2 * N - 2)!)种。
那么我们现在初步考虑的总方程好像是这样的:
(sum_{i = 0} ^ {N} C_{N}^{i} 2 ^ {i} imes(2 * N - i - 1)!)种。但是我们要明白一个重复删减的问题。当我们考虑([V1,V2])坐在一起的情况的时候,并没有限制其他人。那么假如在这里有一种情况是([E1, E2])也坐在了一起,那么当我们枚举到了([E1, E2])坐在一起的时候,也会出现一种([V1, V2])也坐在一起的情况。而事实上,这两种情况是一样的,但是我们删减了两遍。所以我们要再加上一遍这个情况。其实这个也就是容斥原理。那么最后我们可以得出总方程为:

(sum_{i = 0} ^ {N} C_{N}^{i} imes 2 ^ {i} imes (2 imes N - i - 1)! imes (-1) ^ {i})

其中(C_{N}^{i})是从中选出i个数有多少种方案。最后的-1就是为了加上多减的哪些方案数。然后关于组合数的递推式子是这样的:(C_{N}^{i} = C_{N}^{i - 1} * frac{N - i}{i}),当然,在这之中还要用到逆元。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAXN 100010
#define Inf 0x7fffffff
#define LL long long
#define Mod 10000007
using namespace std ;
int Read(){
	int X = 0 ; char ch = getchar() ;
	while(ch > '9' || ch < '0') ch = getchar() ;
	while(ch >= '0' && ch <= '9')
	X = (X << 1) + (X << 3) + (ch ^ 48), ch = getchar() ;
	return X ;
}
int N, Ans ;
int Times(int A, int B){
	int Ans = 1 ;
	while(B){
		if(B % 2 == 1) Ans = Ans * A % Mod ;
		A = A * A % Mod ;
		B >>= 1 ;
	}
	return Ans ;
}
int main(){
	N = Read() ;
	if(N == 1){
		puts("0") ; return 0 ;
	}
	int Erh = 1, Cnh = 1, Nh2 = 1 ;
	for(int i = 1; i <= 2 * N - 1; i ++)
		Nh2 *= i ;
	for(int i = 1; i <= N; i ++){
		int A = Erh * Cnh % Mod * Nh2 % Mod ;
		if(A % 2 == 1) Ans -= A ;
		else Ans += A ; Ans %= Mod ;
		if(i == N) break ; Erh <<= 1 ;
		if(Erh >= Mod) Erh -= Mod ;
		Cnh = Cnh * (N - i) % Mod * Times(i + 1, Mod - 2) % Mod ;
		Nh2 = Nh2 * Times(2 * N - i - 1, Mod - 2) % Mod ;		
	}
	printf("%d
", Ans) ; return 0 ;
}

(T3) (:) (y)

给定两个长度分别为(N), (M)(S_{1})(S_{2})(01)串,下面有(K)个操作分为两类:

  1. 询问(S_{2})(S_{1})的区间为([L,R])的子串中出现了几次。(可重叠)。
  2. (S_{1})([L,R])区间的(0)(1)互换。

某线段树。嗯。


(Afternoon ~ & ~ Night)


丢失......


(10.5)

(Morning)


10.5 Morning

标签(空格分隔): Test


(T1)(travel)

(N)个城市围成一圈。第一次到达第(i)个城市时,得到(A[i])块钱((A[i])可能为负数),从城市(i)到达下一个城市时(只能从(i)(i+1)或者从(n)(1)),需要花费(B[i])元。一开始(LYK)(c)元钱,并且从任意一个城市出发沿环走一圈并回到这个城市,要求旅途过程中保证钱非负。问从多少个点出发是可行的。

首先我们知道这个(B[i])并没有什么卵用,由于(B[i])非负,所以它完全是可以和(A[i])合并的。那么首先我们可以设(V[i] = A[i] - B[i])。然后复制一下N把圈变成一行,
(1.) 第一种方法:直接暴力模拟,从每一个i开始跑一圈。复杂(O(N^2))可以过(60)%

$2. (第二种方法:我们考虑对于一个)i(,以)i(为起点造出来一个前缀和,那么我们需要考虑的就是所有的前缀和)Sum[j](中最小的那个,看它加上)C(是不是小于零。但是比较郁闷的就是这个前缀和的起点并不是固定的。因为这里你设的起点是i。那么我们要做的就是做一个把)i + 1(之后的前缀和做一个区间修改。那么考虑用线段树。时间复杂度为)O(NlogN)(可以)A$掉。

(3.) 还是第二种方法前缀和,但是我们考虑到一个傻逼的事实。这个用线段树实现的区间修改给我的答案决定于哪个(j)有任何作用么?...没有。所以果断把刚才最小的前缀和修改掉下一个(i)进行判断就完了。

(T2) : (pit)

(LYK)有一个(N imes N)的棋盘。它恰好有(2 imes N)颗黑棋和(N)颗白棋,将其全部放到棋盘上,有这几个要求:
(1.) 每个位置最多放一个棋
(2.) 每行每列都恰好有(2)颗黑棋
(3.) 每行每列都恰好有(1)颗白棋
求方案数 % (P)的大小。
对于(20)%的数据(n<=4)
对于(40)%的数据(n<=6)
对于(60)%的数据(n<=10)
对于(80)%的数据(n<=50)
对于(90)%的数据(n<=200)
对于(100)%的数据(3<=n<=1000)(2<=p<=10^9+9)

首先,你需要一个(Oeis)(滑稽)。全分的做法太难了,这里只给出一个公式:(a[n]=(n-1)*(2*(n^3-2*n^2+n+1)*a[n-1]/(n-2)+((n^2-2*n+2)*)
((n+1)*a[n-2] +(2*n^2-6*n+1)*n*a[n-3]+(n-3))
(*(a[n-4]*(n^3-5*n^2+3)-(n-4)*(n-1)*(n+1))
(*a[n-5])))/(2*n)))
看,是不是很简单(逃
我们一点一点来,先争取拿到一点部分分再说。我们知道第(N)的答案位数小于(10N),可以高精度把(N)项的答案算出来再对P取模。
我们在知道棋子得一行一行放的情况下,先进行最暴力的Dp。先不计代价地设一个(Dp[i])然后后面跟着一堆状态。我们进行行的枚举的时候,是强制一行有一个白棋,两个黑棋的,所以我们只要考虑列的情况。然后我们知道所有的情况是这样的:
(1.) 0白 (0)
(2.) 0白 (1)
(3.) 0白 (2)
(4.) 1白 (0)
(5.) 1白 (1)
(6.) 1白 (3)
一共有六种情况,所以我们只要考虑六种情况各出现了多少次就可以了,所以我们得出来了一个(7)维的(DP[i][a][b][c][d][e][f])。时间复杂度为(O(N^7))然后我们考虑消维。
我们知道对于(1)白和(0)白,是分别独立的。我们可以知道:
那么我们知道(a + b + c + d + e + f = N)则可以消掉f的状态。变到(O(N^6))
我们知道一行放了(1)个白棋,我们枚举到第(i)行的时候自然有(i)个白棋。所以(d + e + f = i) 所以我们又消掉了一维,然后(O(N^5))
我们还知道对于黑棋现在一共有(2 * i)
所以(b + 2 * c + e + 2 * f = 2 * i) 然后我们变成了(O(N^4))的复杂度。这样是可以过掉(n <= 50)的数据,大概是可以得(80)%的分。
而至于满分做法。
...
...
去他妈的吧。

(T3) : (holyshit)

你现在有(N)个数(A_{i}),要求找出两端互不相交的区间,使得不存在任何一个数在这两段区间中总共出现的次数超过1次。求最大化这个区间长度。

并查集树 + (Dp)
...
...
(Holyshit),去他妈的吧。


(Afternoon ~ & ~ Night)


(10.5) (Night)

标签(空格分隔): Note


例题2 : 斗地主(Link

题面太长不再搬运。

其实只要考虑到一个顺子优先的事这个题就完了。因为顺子在题目里面是个很特殊的存在,因为它可以连续快速的干掉大量的牌,所以我们有限搜索三种顺子,然后我们再进行其他的暴力搜索,顺序大致如下:
(1.) 顺子
(2.) 四带二
(3.) 三带二
(4.) 三带一
(5.) 散牌

例题3 : 阿狸和桃子的游戏(Link

现在有两个人

例题3 : 图论好题

例题4 : 找路径

给定一个[N, M]

例题(5) : (HDU) (4903)

有一张N个点的完全无向图,第i个点的编号是i, 每条边的边权在([1, L])之间的这个正整数。。问存在多少个图使得最短路([1, N])的最短路为(K)

好了那么关于这道题
...
...
去他妈的吧。

例题6 : 赴京赶考

有两个数列(A)(B)。对于一个(N imes M)上的环形网络上的一个数(C[i][j]),如果(A[i] == B[j])(C[i][j] = 0)否则(C[i][j] = 1)。每次要进行一次行走。而从一个点((X_{i},Y_{i}))可以走到它的上下左右((X_{j}, Y[j]))四点,而其代价就是(C[X_{i}][Y_{i}]) ^ (C[X_{j}][Y_{j}]).现在有Q次询问,每次问从((X_{Start}, Y_{Start}))((X_{End}, Y_{End}))的最短路。

我们首先会发现一个很神奇的事情:行走跟(A[i])是没有什么关系的。因当为(A[i] == B[j])(C[i][j] = 0)否则(C[i][j] = 1),转化一下就是(C[i][j] = A[i]) ^ (B[j]),那么(C[X_{i}][Y_{i}]) ^ (C[X_{j}][Y_{j}])也就等于(B[i]) ^ (B[j])。所以我们就把二维转化成了一维。然后就好做了。

例题7 : (Bzoj) (3917)

求一个最小的(N),使得(N + i)包含数码(B_{i})。其中(B_{i}in{[0,9]})(0 <= i <= 10 ^ {5})。(也就是输入的(B_{i})的下标从i开始)

这个题实在是很恶心,没有什么思路。那么首先我们扩展一下体面为:N + i是一个数字集合。
...
...
去他妈的吧。傻逼智商题。


(10.6)

(Morning)


丢失......


(Afternoon~& ~Night)


(10.6) (Evening)

标签(空格分隔): Note


例题1 : 解方程

现在有一个多项式方程大概模式为(A_{0} + A_{1}X + A_{2}X ^ 2...A_{N}X ^ N)要求求出这个方程在[1, M]之内的整数解。其中(Nin{[0, 100]}), (Min{[0, 1000000]}),(|A_{i}|<[10 ^ {10000}])

先看暴力方法。首先对所有的(A_{i})对一个数取模,然后枚举(1) ~ (M),再计算看加起来是不是等于0。复杂度(O(NM))并不能过掉。
我们假设这个方程为(F(X)),那么我们知道(F(X + P)) = (F(X)) % (P).所以(F(X)) % (P e 0)时,(F(X)) % (KP e 0)那么(F(X + KP))就并不需要枚举。所以我们枚举所有的(0) ~ (P - 1),判断是否可行,然后如果有不可行的,那么我们就可以用它去排除X比较大的时候的答案。
但是由于有%的出现,所以有很大的可能会失败。所以最好多选几个P。时间复杂度为(O(PN))。可以过掉。

例题2 : 智商锁。

构造一个点数不超过(100)的无自环无重边的无向图使得生成树个数在膜$$998244353(的情况下等于)K$。

首先我们要知道怎么求出一张图的生成树个数。这个方法叫做基尔霍夫矩阵法。若存在边((i, j)),则有(M[i][j] = M[j][i] = - 1),否则(M[i][j] = M[j][i] = 0)。$M[i][i] = (点)i$的度数。删除该矩阵的第一行和第一列,其行列式即为生成树个数。
上面那一堆玩意就是基尔霍夫矩阵,虽然我也不知道这到底是一个他妈的什么狗东西,我也不会证明。
我们随机(10^12)个图,假如这个图近似随机,一个数被覆盖的概率为(1-(1-1/10^9)^(10^12))。所有数都被覆盖的概率为一个数被覆盖的概率的(10^9)方。(近似)这个值是十分趋向于(1)的。
问题转换为如何构造(10^{12})个随机的图。
考虑将其分成(4)份,每份都是(12)个点每条边有(0.8)概率出现的随机图,令这(4)张图的生成树个数为(F_{A}, F_{B}, F_{C}, F_{D}),如果我们将这(4)张图用(3)条边连接起来,就是(F_{A} imes F_{B} imes F_{C} imes F_{D})个生成树。
而这个值显然是可以(Meet) (In) (The) (Middle)的。
由于每张图近似随机,可以看做生成树个数是(0) ~ (998244352)中的一个随机数。

例题3 : Bzoj 1045

(N)个人围成一圈,第(i)个人手里有(A_{i})个糖果。每次每个人可以给旁边的人一颗糖果,代价为(1)
为最少多少代价能使所有人拥有的糖果数量一样多。
(N <= 10w)

我们知道每个人最终的糖果数都是(sum_{i = 1}^{i <= N}A_{i} / N)。我们知道两个小朋友之间只会是一个小朋友不断地给另外一个小朋友糖果,因为不可能存在一种方案是两个人之间不断地(Gay♂)(Gay♂)去。我们用(X[i])表示第i个小朋友给了第(i - 1)个小朋友多少糖果,当然,(X[i])是可能为负数的,那么答案也就是(sum X[i])。并且我们还可以得到下面一个式子(A[i] - X[i] + X[i+1] = sum_{i = 1}^{i <= N}A_{i} / N)这个式子很好理解。当前这些个小朋友的糖果减去自己给去别人的糖果,加上别然给自己的糖果就是最后得到的糖果总数。我们用(Fin)表示(sum_{i = 1}^{i <= N}A_{i} / N),那么我们就可以用上面的等式将答案进行化简。最后得到:
(Ans = |X[1]-0|+|X[1]-C[1]|+|X[1]-C[2]|+…+|X[1]-C[n-1]|),其中(C[i] = A[i] - Fin)表示现在和最后的差值。

例题4 : 异或矩阵

在一个(N imes M)(01)矩阵中,有(K)个格子已经确定是(0)还是(1),要求确定其它格子的值,使得这个矩阵每行/列的异或和都为(1)。求方案总数对(1e9+7)取模后的值。
(N,M<=1000,K < max(N,M))

我们不妨设(N >= M),且最后一行没有数已经填了。
只要我们填了前(N-1)行,且满足这些行的异或和为(1),此时要使每一列的异或和都为(1),最后一行填的数的方案是唯一的。
只要当(N,M)同奇偶时,最后一行的异或和一定为(1)
对于每一行,只要有(m-1)个数确定了,那么剩下的一个位置的值是唯一的。
假设第(i)行有(x)个位置没被填,则它对答案的贡献为(2^(x-1))次,注意判断无解的情况。


(10.7)

(STL)的几个比较实用的东西:

(set)(lower)_ (bound(A + 1, A + N + 1, K))寻找在整个序列中不小于K的数。(upper)_(bunder(A + 1, A + N + 1, K))寻找整个序列中大于(K)的数。

(nth_element)找第(K)
(nth)_(element(A +1, A + K, A + N + 1)),其中(A[K])即为第(K)小。

(reverse(A + 1, A + N + 1)) : 区间全部翻转。
(next)_(permutation) : 寻找下一个排列

(min)_(element(A + 1, A + N + 1)) : 寻找最小值所在的位置。

(binary)_(search(A + 1, A + N + 1, K)) : 从(1) - (N)中二分查找(K)
(swap)可以交换长度相同的数组。


其他总结

听课笔记
存图: 如果数据为(1 <= N, M <= 100000) 那么可以使用前向星。但是如果碰到了(1 <= N <= 100)之类的数据范围可以考虑使用邻接矩阵。
最短路:
(Dijkstra){
​ 朴素: (O(N^{2} + M))
​ STL堆优化: (O((N + M)logM))
​ 手写堆优化:(O((N + M)logN))
​ 斐波那契堆:(O(NlogN + M))
}
(Spfa) : (O(VE))(O(NM))

出现负权边:只能用(Spfa)
没有负权边:最好用堆优化(Dijsktra)

生成树:
最小生成树{
(Kruskal)(O(MlogM))
(Prim): (O((M + N)logM)) 不一定要掌握
}
次小生成树:{
​ 首先:一个有(N)个点的完全图,生成树个数为(N^{N- 2})(由矩阵树定理(Matrix) (Tree) (Theory)
​ 先求最小生成树。枚举删掉最小生成树上的哪一条边。然后全图还剩下(M - 1)条边,然后接着跑最小生成树。那么时间复杂度为(O(NM))。大概可以拿(60)分。然后满分做法是先加进来一条边,然后删去一条原来的最小生成树上的最大的那条边。而我们先加边会导致形成一个环包含加进来的这个边的两个端点,那么为了使它再次形成一棵树,我们就要再这个还上删掉一个边,那么我们就可以用倍增(LCA)进行删边。这样大概可以的全分了。
}

强连通分量:
有向图:
无向图:{
​ 点双连通分量:割点
​ 边双联通分量:桥
}

目的: 把有向有环图变成(DAG)进行(DP)

(DAG上 + DP + Topu)
将图进行拓扑排序使边有序,那么我们就可以进行(p).

原文地址:https://www.cnblogs.com/sue_shallow/p/QBXTNATIONAL.html