保研机试模板整理

版权声明:希望我的博客可以为别人带去知识与便利,让那些像我曾经一样迷茫的小伙伴不再迷茫~ https://blog.csdn.net/qq_34374664/article/details/80412575

最短路spfa、dij、floyd + 记录路径

floyd:

思路:用一个二维数组path[][]来记录。这里有两种方法,1 用path[ i ][ j ]记录 j 的前驱顶点 ;2 用path[ i ][ j ]记录 i 的后面的点。

提醒:需要注意的是path的初始化

#include <cstdio>
#include <cstring>
#include <stack>
#include <algorithm>
#define INF 1000000+10
using namespace std;
int Map[500][500]; 
int pre[500][500];//记录当前顶点的 前一个顶点 
int n, m;
void init()
{
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			Map[i][j] = i==j ? 0 : INF;
			pre[i][j] = j;//初始化 
		}
	}
} 
void getMap()
{
	int a, b, c;
	while(m--)
	{
		scanf("%d%d%d", &a, &b, &c);
		if(Map[a][b] > c)
		Map[a][b] = c;
	} 
}
void floyd()
{
	int i, j, k;
	for(k = 1; k <= n; k++)
	{
		for(i = 1; i <= n; i++)
		{
			for(j = 1; j <= n; j++)
			{
				if(Map[i][j] > Map[i][k] + Map[k][j])
				{
					Map[i][j] = Map[i][k] + Map[k][j];
					pre[i][j] = pre[i][k];//记录 
				}
			} 
		} 
	} 
}
int main()
{
	int s, e;
	while(scanf("%d %d", &n, &m), n||m)
	{
		init();
		getMap();
		floyd();
		while(scanf("%d%d", &s, &e), s!=-1||e!=-1)
		{
			if(s == e)
			{
				printf("从%d到%d的最优路线 : %d\n", s, e, s);
				printf("最小花费 : %d\n", 0);
				continue;
			}
			printf("从%d到%d的最优路线 : %d", s, e, s);
			int now = pre[s][e];
			while(1)
			{
				printf("-->%d", now);
				if(now == e)
				break;
				now = pre[now][e];
			}
			printf("\n");
			printf("最小花费 : %d\n", Map[s][e]);
		}
	}
	return 0;
}
	for(int i = 1; i <= n; i++)
	{
		for(int j = 1; j <= n; j++)
		{
			Map[i][j] = i==j ? 0 : INF;
			pre[i][j] = j;//初始化 
		}
	}
} 
void getMap()
{
	int a, b, c;
	while(m--)
	{
		scanf("%d%d%d", &a, &b, &c);
		if(Map[a][b] > c)
		Map[a][b] = c;
	} 
}
void floyd()
{
	int i, j, k;
	for(k = 1; k <= n; k++)
	{
		for(i = 1; i <= n; i++)
		{
			for(j = 1; j <= n; j++)
			{
				if(Map[i][j] > Map[i][k] + Map[k][j])
				{
					Map[i][j] = Map[i][k] + Map[k][j];
					pre[i][j] = pre[i][k];//记录 
				}
			} 
		} 
	} 
}
int main()
{
	int s, e;
	while(scanf("%d %d", &n, &m), n||m)
	{
		init();
		getMap();
		floyd();
		while(scanf("%d%d", &s, &e), s!=-1||e!=-1)
		{
			if(s == e)
			{
				printf("从%d到%d的最优路线 : %d\n", s, e, s);
				printf("最小花费 : %d\n", 0);
				continue;
			}
			printf("从%d到%d的最优路线 : %d", s, e, s);
			int now = pre[s][e];
			while(1)
			{
				printf("-->%d", now);
				if(now == e)
				break;
				now = pre[now][e];
			}
			printf("\n");
			printf("最小花费 : %d\n", Map[s][e]);
		}
	}
	return 0;
}

spfa 和 dij

HDU 1224

题意:给你n个城市,每个城市都有一个风景值,再给你m条路,每条路连接两个城市,只能从序号小的到序号大的,问你从1号到n+1号能经过的风景值得和最大为多少,并且输出路径。

import java.util.Arrays;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
import java.util.Vector;

class Main
{
	static final int maxn = 1000;
	static final int INF = 0x3f3f3f3f; 
	static int[] dis = new int[maxn];
	static int[] book = new int[maxn];
	static int[] pre = new int[maxn];
	static int[] cost = new int[maxn];
	static int[] res = new int[maxn];
	static class node implements Comparable<node>
	{
		int to, w;
		node(int tt, int ww)
		{
			this.to = tt;
			this.w = ww;
		}
		@Override
		public int compareTo(node o) 
		{
			// TODO Auto-generated method stub
			return this.w - o.w;
		}
	}
	static Vector<node>[] v = new Vector[maxn];
	static void spfa()
	{
		Arrays.fill(book, 0);
		Arrays.fill(pre, -1);
		Arrays.fill(dis, 0);
		Queue<Integer> q = new LinkedList<Integer>();
		q.add(1);
		dis[1] = 0;
		while(!q.isEmpty())
		{
			int u = q.poll();
			book[u] = 0;
			for(int i = 0; i < v[u].size(); i++)
			{
				int to = v[u].get(i).to;
				if(dis[to] < dis[u] + cost[to])
				{
					dis[to] = dis[u] + cost[to];
					pre[to] = u;
					if(book[to] == 0)
					{
						book[to] = 1;
						q.add(to);
					}
				}
			}
		}
	}
	static void dij()
	{
		Arrays.fill(dis, 0);
		Arrays.fill(pre, -1);
		Queue<node> pq = new PriorityQueue<node>();
		pq.add(new node(1, 0));
		while(!pq.isEmpty())
		{
			int u = pq.poll().to;
			for(int i = 0; i < v[u].size(); i++)
			{
				int to = v[u].get(i).to;
				if(dis[to] < dis[u] + cost[to])
				{
					pre[to] = u;
					dis[to] = dis[u] + cost[to];
					pq.add(new node(to, dis[to]));
				}
			}
		}
	}
	public static void main(String[] args) 
	{
		Scanner sc = new Scanner(System.in);
		for(int i = 0; i < maxn; i++) 
			v[i] = new Vector<node>();
		int _, n, m, ca = 1;
		_ = sc.nextInt();
		while(_-- > 0)
		{
			for(int i = 0; i < maxn; i++)
			{
				v[i].clear();
				cost[i] = 0;
				
			}
			n = sc.nextInt();
			for(int i = 1; i <= n; i++)
				cost[i] = sc.nextInt();
			m = sc.nextInt();
			for(int i = 1; i <= m; i++)
			{
				int u = sc.nextInt();
				int to = sc.nextInt();
				if(u > to) 
				{
					int tmp = u;
					u = to;
					to = tmp;
				}
				v[u].add(new node(to, 0));
			}
			//spfa();
			dij();
		    System.out.println("CASE " + (ca++) + "#");  
            System.out.println("points : " + dis[n + 1]);  
            System.out.print("circuit : ");  
			int cnt = 0;
			int p = n + 1;
			while(p != -1)
			{
				res[cnt++] = p;
				p = pre[p];
			}
			for(int i = cnt-1; i > 0; i--)
				System.out.print(res[i] + "->");
			System.out.println(1);
			if(_ != 0)
				System.out.println();
		}
	}
}

次短路

题意:给你一个有向图,问你他的次短路长度(与最短路至少有一条边不同即可)

思路:如果最短路有多条,那答案就是最短路,否则就是次短路

次短路思路: 
        把求最短路时更新最短路的那部分改一下。 
        dis1,dis2数组分别记录到该点的最短路和次短路 
        分三种情况: 
            1.若该点最短路+下一条边比到下个点的最短路短,则更新下个点的最短路,同时更新次短路为原最短路 
            2.若该点次短路+下一条边比到下个点的次短路短,则更新下个点的次短路 
            3.若该点最短路+下一条边比到下个点的最短路长同时比下个点的次短路短,则更新下个点的次短路 

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
const ll INF = 0x3f3f3f3f3f3f3f3f;
int n, m, k, head[maxn];
ll cnt[maxn];
ll dis1[maxn], dis2[maxn], dis[maxn];
bool book[maxn];
 
struct node
{
    int v, w, next;
}edge[maxn];
 
void addEdge(int u, int v, int w)
{
    edge[k].v = v;
    edge[k].w = w;
    edge[k].next = head[u];
    head[u] = k++;
}
 
void spfa(int u)
{
    for(int i = 1; i <= n; i++) dis1[i] = INF;
    for(int i = 1; i <= n; i++) dis2[i] = INF;
    memset(book, 0, sizeof(book));
    queue<int> q;
    q.push(u);
    dis1[u] = 0;
    book[u] = 1;
    while(!q.empty())
    {
        u = q.front(); q.pop();
        book[u] = 0;
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].v;
            int w = edge[i].w;
            if(dis1[v] > dis1[u]+w)
            {
                dis2[v] = dis1[v];
                dis1[v] = dis1[u]+w;
                if(!book[v]) book[v] = 1, q.push(v);
            }
            if(dis2[v] > dis2[u]+w)
            {
                dis2[v] = dis2[u]+w;
                if(!book[v]) book[v] = 1, q.push(v);
            }
            if(dis1[v] < dis1[u]+w && dis2[v] > dis1[u]+w)
            {
                dis2[v] = dis1[u]+w;
                if(!book[v]) book[v] = 1, q.push(v);
            }
        }
    }
}
 
void spfa2(int u)
{
    for(int i = 1; i <= n; i++) dis[i] = INF;
    memset(book, 0, sizeof(book));
    queue<int> q;
    q.push(u);
    book[u] = cnt[u] = 1;
    dis[u] = 0;
    while(!q.empty())
    {
        u = q.front(); q.pop();
        book[u] = 0;
        for(int i = head[u]; i != -1; i = edge[i].next)
        {
            int v = edge[i].v;
            int w = edge[i].w;
            if(dis[u]+w < dis[v])
            {
                dis[v] = dis[u]+w;
                if(!book[v]) book[v] = 1, q.push(v);
                cnt[v] = cnt[u];
            }
            else if(dis[u]+w == dis[v])
            {
                cnt[v] += cnt[u];
            }
        }
    }
}
 
int main(void)
{
    int t;
    cin >> t;
    while(t--)
    {
        k = 0;
        memset(cnt, 0, sizeof(cnt));
        memset(head, -1, sizeof(head));
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= m; i++)
        {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            addEdge(u, v, w);
            addEdge(v, u, w);
        }
        spfa(1);
        spfa2(1);
        if(cnt[n] > 1) printf("%lld\n", dis1[n]);
        else printf("%lld\n", dis2[n]);
    }
    return 0;
}

其他例题:

路径最大权值最小

博弈

SG函数

sg 即Graph Game,把博弈游戏抽象成有向无环图
(1) 有向无环图
(2) 玩家1先移动,起点是x0
(3) 两个玩家轮流移动
(4) 对于顶点x, 玩家能够移动到的顶点集记为F(x).
(5) 不能移动的玩家会输掉游戏
首先定义mex(minimal excludant)运算,这是施加于一个集合的运算,表示最小的不属于这个集合的非负整数。例如mex{0,1,2,4}=3、 mex{2,3,5}=0、mex{}=0。
定义: 一个图的Sprague-Grundy函数(X,F)是定义在X上的非负函数g(x),并且满足:

       g(x) = mex{g(y) : y∈F(x)}

假设游戏 Gi的SG函数是gi, i=1,…,n, 则G = G1 + … + Gn 的 SG函数是g(x1,…,xn) = g1(x1)^…^gn(xn).

g(x) > 0 必胜, g(x) == 0 必输

例题:

  这是一个二人游戏,一共有3堆石子,数量分别是m, n, p个,两人轮流走, 每走一步可以选择任意一堆石子,然后取走f个, f只能是菲波那契数列中的元素(即每次只能取1,2,3,5,8…等数量), 最先取光所有石子的人为胜者,假设双方都使用最优策略,请判断先手的人会赢还是后手的人会赢,  如果先手的人能赢,请输出“Fibo”,否则请输出“Nacci”,每个实例的输出占一行。

#include <iostream>  
#include <algorithm>  
#include <cstring>  
#include <cstdio>  
using namespace std;  
const int maxn = 1e3 + 5;  
int f[maxn], sg[maxn], book[maxn];  
void init()  
{  
    f[1] = 1;  
    f[2] = 2;  
    for(int i = 3; i <= maxn; i++)  
    f[i] = f[i-1] + f[i-2];  
}  
void get_sg()  
{  
    for(int i = 1; i <= maxn; i++)  //从1枚举所有状态
    {  
        memset(book, 0, sizeof(book));  //计算mex的
        for(int j = 1; f[j] <= i; j++)   //枚举这个状态所有可能到达的状态
        {  
            book[sg[i-f[j]]] = 1;   // 计算能到达的状态的sg是否出现过
        }  
        for(int j = 0; book[j]; j++)   //计算mex
            sg[i] = j + 1;  
    }  
}  
int main()  
{  
    int m, n, p;  
    init();  
    get_sg();  
    while(cin >> m >> n >> p, m+n+p)  
    {  
        puts(sg[n]^sg[m]^sg[p] ? "Fibo" : "Nacci");  
    }  
    return 0;  
}  

尼姆博弈

尼姆博奕 先取走赢跟先取走输 都是抑或和 = 0 输了 只是先取走输要特判都是1的时候

#include <cstdio>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
using namespace std;  
int main()  
{  
    int t, n;  
    scanf("%d" ,&t);  
    while(t--)  
    {  
        scanf("%d", &n);  
        int a, ans = 0, flag = 0;  
        for(int i = 1; i <= n; i++)  
        {  
            scanf("%d", &a);  
            ans ^= a;  
            if(a > 1)  //特判都是1 的时候  
               flag = 1;  
        }  
  
        if(!flag)  
        {  
            if(n%2) printf("Brother\n");  
            else printf("John\n");  
            continue;  
        }  
        if(ans == 0)  
            printf("Brother\n");  
        else  
            printf("John\n");  

威佐夫博弈

题目:有两堆石子,数量任意,可以不同。游戏开始由两个人轮流取石子。游戏规定,每次有两种不同的取法,一是可以在任意的一堆中取走任意多的石子;二是可以在两堆中同时取走相同数量的石子。最后把石子全部取完者为胜者。现在给出初始的两堆石子的数目,如果轮到你先取,假设双方都采取最好的策略,问最后你是胜者还是败者。如果你胜,你第1次怎样取子? 

思路:威佐夫博弈,遇到奇异局势则必败第k个奇异局势(a(k), b(k)),a(k)是前面没有出现过的最小自然数(a(k)=(int)(k*(sqrt(5.0)+1)/2),b(k)=a(k)+k,采用适当的方法,这里不再证明,接下来只要判断就行了

同时从两堆中取相同数量的石头或者从一堆中取一定数量的石头,可以将非奇异局势变为奇异局势

#include <cstdio>  
#include <cmath>  
#include <algorithm>  
using namespace std;  
  
double g = (sqrt(5.0) + 1) / 2;  // 黄金分割数1.618...  
  
int main()  
{  
    int a, b;  
    while(scanf("%d %d", &a, &b) == 2) {  
        if(a == 0 && b == 0)  
            break;  
  
        int k = b - a;  
        if(a == (int)(g * k))    // 只要判断a即可,因为b=a+k是恒成立的  
        {  
            printf("0\n");  
        }  
        else  
        {  
            printf("1\n");  
            // 将非奇异局势变为奇异局势  
            for(int i=1; i<=a; i++) // 同时从两堆中取相同数量的石头,注意这里是从1到a枚举  
            {  
                int x = a - i, y = b - i;  
                int k = y - x;  
                if(x == (int)(g * k))  
                    printf("%d %d\n", x, y);  
            }  
            for(int i=b-1; i>=0; i--)   // 从一堆中取一定数量的石头,这里是从b-1往下枚举到0  
            {  
                int x = a, y = i;  
                if(x > y)  
                    swap(x, y);  
  
                int k = y - x;  
                if(x == (int)(g * k))  
                    printf("%d %d\n", x, y);  
            }  
        }  
    }  
    return 0;  
}  

巴什博弈

有三个数字n,p,q,表示一堆硬币一共有n枚,从这个硬币堆里取硬币,一次最少取p枚,最多q枚,如果剩下少于p枚就要一次取完。两人轮流取,直到堆里的硬币取完,最后一次取硬币的算输。对于每一行的三个数字,给出先取的人是否有必胜策略,如果有回答WIN,否则回答LOST

思路:
可以分成三种情况。
(1)如果n%(p+q)==0,那么A必胜。取胜步骤如下:
A第一次取q,接下去B取m,A就取p+q-m,那么最后剩下的就是p个硬币留给B取,B必败。
(2)如果n=(p+q)*k+s,(1<=s<=p),那么B必胜。取胜步骤如下:
A取一个m,那么B就取p+q-m,那么最后剩下的就是s个银币留给A取,A必败。
(3)如果n=(p+q)*k+s,(p<s<=q),那么A必胜。取胜步骤如下:
A第一次取一个t,(1<= s-t <=p),那么B取一个m,A取p+q-m,最后就剩下s-t个硬币留给B取,B得一次性取完,B必败。
(4)如果n=(p+q)*k+s,(q<s<=p+q),那么A必胜。取胜步骤如下:
先取q,这样剩下了 s < p,然后b取m,a跟着取p+q-m,最后剩下的m一定是b拿,所以a赢

简而言之就是:谁面对余数在1-p,谁就一定输,面对余数>p都可以胜利,因为他可以一步变成余数小于p的,让对面面对余数在1-p

 

#include <iostream>  
#include <cstdio>  
using namespace std;  
int main()  
{  
    int n, p, q;  
    while(~scanf("%d%d%d",&n, &p, &q))  
    {  
        if(n % (p+q) <= p && n % (p+q) >= 1)  
            printf("LOST\n");  
        else  
            printf("WIN\n");  
    }  
    return 0;  
}  

Tarjan算法

算法流程是: 开始先随机选一个点,沿着这个点进行遍历, dfn[]代表这个点出现的顺序,low[]代表强联通分量里编号最小的编号,以这个编号最小的节点作为联通分量的“起点”,每个节点low[]与dfn[]初始化时相同的,回溯时候更新low[u]=min(low[u],low[v])的值,如果对于一个子节点出现过(在栈里),说明转了一圈,在一个环里,那么就更新 low[u] = min(low[u], dfn[v]),最后栈里在每个dfn[x] == low[x]的关键点上面的非关键点都是与他一个联通分量的(因为之前说了我们让dfn最小的作为环的“起点”)。

缩点裸题

题目大意:N(2<N<100)各学校之间有单向的网络,每个学校得到一套软件后,可以通过单向网络向周边的学校传输,问题1:初始至少需要向多少个学校发放软件,使得网络内所有的学校最终都能得到软件。2,至少需要添加几条传输线路(边),使任意向一个学校发放软件后,经过若干次传送,网络内所有的学校最终都能得到软件。 

也就是,给定一个有向图,求:

1) 至少要选几个顶点,才能做到从这些顶点出发,可以到达全部顶点

2) 至少要加多少条边,才能使得从任何一个顶点出发,都能到达全部顶点

—        顶点数<= 100

解题思路:

—        1. 求出所有强连通分量

—        2. 每个强连通分量缩成一点,则形成一个有向无环图DAG。

—        3. DAG上面有多少个入度为0的顶点,问题1的答案就是多少

在DAG上要加几条边,才能使得DAG变成强连通的,问题2的答案就是多少

加边的方法:

要为每个入度为0的点添加入边,为每个出度为0的点添加出边

假定有 n 个入度为0的点,m个出度为0的点,如何加边?

把所有入度为0的点编号 0,1,2,3,4 ....N -1

每次为一个编号为i的入度0点可达的出度0点,添加一条出边,连到编号为(i+1)%N 的那个出度0点,

这需要加n条边

若 m <= n,则

加了这n条边后,已经没有入度0点,则问题解决,一共加了n条边

若 m > n,则还有m-n个入度0点,则从这些点以外任取一点,和这些点都连上边,即可,这还需加m-n条边。

所以,max(m,n)就是第二个问题的解

此外:当只有一个强连通分支的时候,就是缩点后只有一个点,虽然入度出度为0的都有一个,但是实际上不需要增加清单的项了,所以答案是1,0;

#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <vector>  
#include <stack>  
#include <algorithm>  
using namespace std;  
const int maxn = 1e3 + 5;  
int n, low[maxn], dfn[maxn], id[maxn], scc_cnt, dfs_cnt;  
int in[maxn], out[maxn];  
vector<int> v[maxn];  
stack<int> s;  
void init()  
{  
    memset(low, 0, sizeof(low));  
    memset(id, 0, sizeof(id));  
    memset(dfn, 0, sizeof(dfn));  
    memset(in, 0, sizeof(in));  
    memset(out, 0, sizeof(out));  
    scc_cnt = dfs_cnt = 0;  
    for(int i = 0; i < maxn; i++)  
        v[i].clear();  
    while(!s.empty())  
        s.pop();  
}  
void tarjan(int x)  
{  
    dfn[x] = low[x] = ++dfs_cnt;  
    s.push(x);  
    for(int i = 0; i < v[x].size(); i++)  
    {  
        int to = v[x][i];  
        if(!dfn[to])  
        {  
            tarjan(to);  
            low[x] = min(low[x], low[to]);  //回溯赋值
        }  
        else if(!id[to])   //这里是id == 0的在栈里,因为下面id赋值的都已经出栈了
            low[x] = min(low[x], dfn[to]);  //从这里把所有联通分量里的节点赋值成起点
    }  
    if(low[x] == dfn[x])  //从后往前回溯的时候,碰到一个关键节点说明他上面的都是一个联通分量
    {  
        scc_cnt++;  
        while(1)  
        {  
            int u = s.top();  
            s.pop();  
            id[u] = scc_cnt;  
            if(x == u) break;  
        }  
    }  
}  
void scc()  
{  
    for(int i = 1; i <= n ; i++)  
        if(!dfn[i])  
            tarjan(i);  
}  
int main()  
{  
    while(~scanf("%d", &n))  
    {  
        init();  
        for(int i = 1; i <= n; i++)  
        {  
            int x;  
            while(scanf("%d", &x), x)  
                v[i].push_back(x);  
        }  
        scc();  
        if(scc_cnt == 1)  
        {  
            printf("1\n0\n");  
            continue;  
        }  
        for(int i = 1; i <= n; i++)  
        {  
            for(int j = 0; j < v[i].size(); j++)  
            {  
                int to = v[i][j];  
                if(id[i] != id[to])  
                {  
                    in[id[to]]++;  
                    out[id[i]]++;  
                }  
            }  
        }  
        int in_cnt = 0, out_cnt = 0;  
        for(int i = 1; i <= scc_cnt; i++)  
        {  
            if(!in[i]) in_cnt++;  
            if(!out[i]) out_cnt++;  
        }  
        printf("%d\n%d\n", in_cnt, max(in_cnt, out_cnt));  
    }  
    return 0;  
}  

强联通缩点的应用

题目大意:

给你N个炸弹,对应已知其坐标和爆炸范围,以及引爆这个炸弹需要的花费,对应如果引爆了炸弹a,没有引爆炸弹b,但是b炸弹在a炸弹的作用范围之内,那么b炸弹也会被引爆,问将所有炸弹都引爆需要的最小花费。

思路:

1、经典的最小点基的模型。我们首先O(n^2)预处理哪些炸弹可以被哪些炸弹引爆,得到一个有向图。

2、如果图中有有向环的话,我们可以将这一个有向环看成一个点,因为环内任意一个炸弹都能引爆这个环内所有的炸弹,所以我们使用Tarjan/Kosaraju之类的强连通算法缩点染色,使得图变成一个DAG(有向无环)图。

3、如果当前图变成了一个DAG图,那么度为0的节点一定是需要引爆的炸弹,因为这个节点中的炸弹不可能通过其他炸弹来引爆,只能通过直接引爆来达到引爆的目的,所以我们都将问题锁定在度为0的关键节点上来讨论,也就是所谓的最小点基问题。然后我们再简单分析一下,如果我们将所有度为0的节点都引爆了,那么度不为0的节点也一定会跟着被引爆,所以那么我们此时只需要将度为0的节点中找到一个对应的最小花费即可。

4、综上所述,我们Tarjan强联通缩点染色之后,找到度为0的节点,并且在其中找到花费最小的炸弹,累加即可。

#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
#include <cmath>  
#include <vector>  
#include <stack>  
typedef long long ll;  
using namespace std;  
const int maxn = 1e3 + 5;  
ll x[maxn], y[maxn], r[maxn];  
int n, low[maxn], dfn[maxn], id[maxn], scc_cnt, dfs_cnt, mincost[maxn], val[maxn], in[maxn];  
vector<int> v[maxn];  
stack<int> s;  
void init()  
{  
    memset(low, 0, sizeof(low));  
    memset(id, 0, sizeof(id));  
    memset(dfn, 0, sizeof(dfn));  
    memset(in, 0, sizeof(in));  
    memset(mincost, 0x3f3f3f3f, sizeof(mincost));  
    scc_cnt = dfs_cnt = 0;  
    for(int i = 0; i < maxn; i++)  
        v[i].clear();  
    while(!s.empty())  
        s.pop();  
}  
void tarjan(int x)  
{  
    dfn[x] = low[x] = ++ dfs_cnt;  
    s.push(x);  
    for(int i = 0; i < v[x].size(); i++)  
    {  
        int to = v[x][i];  
        if(!dfn[to])  
        {  
            tarjan(to);  
            low[x] = min(low[x], low[to]);  
        }  
        else if(!id[to])  
            low[x] = min(low[x], dfn[to]);  
    }  
    if(low[x] == dfn[x])  
    {  
        scc_cnt++;  
        while(1)  
        {  
            int u = s.top();  
            s.pop();  
            id[u] = scc_cnt;  
            mincost[scc_cnt] = min(mincost[scc_cnt], val[u]);  
            if(x == u) break;  
        }  
    }  
}  
void scc()  
{  
    for(int i = 1; i <= n; i++)  
        if(!dfn[i])  
            tarjan(i);  
}  
int check(int i, int j)  
{  
    if((x[i]-x[j]) * (x[i]-x[j]) + (y[i]-y[j]) * (y[i]-y[j]) <= r[i]*r[i])  
    {  
  
        return 1;  
    }  
    else  
        return 0;  
}  
int main()  
{  
    int _, ca = 1;  
    cin >> _;  
    while(_--)  
    {  
        scanf("%d", &n);  
        init();  
        for(int i = 1; i <= n; i++)  
        {  
            scanf("%lld%lld%lld%d", &x[i], &y[i], &r[i], &val[i]);  
        }  
        for(int i = 1; i <= n; i++)  
        {  
            for(int j = 1; j <= n; j++)  
            {  
                if(check(i, j) && i != j)  
                    v[i].push_back(j);  
            }  
        }  
        scc();  
        int ans = 0;  
        for(int i = 1; i <= n; i++)  
        {  
            for(int j = 0; j < v[i].size(); j++)  
            {  
                int to = v[i][j];  
                if(id[i] != id[to])  
                {  
                    in[id[to]]++;  
                }  
            }  
        }  
        for(int i = 1; i <= scc_cnt; i++)  
        {  
            if(in[i] == 0)  
                ans += mincost[i];  
        }  
        printf("Case #%d: %d\n",ca++, ans);  
    }  
    return 0;  
}  

最小生成树

prime

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <queue>  
using namespace std;  
const int maxn = 2e4 + 10;  //分开const  
const int maxm = 1e7 + 10;  
const int inf = 0x3f3f3f3f;  
int n, head[maxn], book[maxn], k, sum, dis[maxn];  
struct node  
{  
    int v, w, pre;  
    node(int vv, int ww) : v(vv), w(ww){}  
    node() {}  
    bool operator < (const node &a) const  
    {  
        return w > a.w;  
    }  
}edge[maxm];  
void addedge(int u, int v, int w)  
{  
    edge[k].v = v;  
    edge[k].w = w;  
    edge[k].pre = head[u];  
    head[u] = k++;  
}  
void prim(int u)  
{  
    for(int i = 1; i <= n; i++) dis[i] = inf;  
    dis[u] = 0;  
    priority_queue<node> pq;  
    pq.push(node(u,dis[u]));  
    while(!pq.empty())  
    {  
        u = pq.top().v;  
        pq.pop();  
        if(book[u]) continue;   //如果是1直接跳出,不加这个会超时  
        book[u] = 1;         
        sum += dis[u];  
        for(int i = head[u]; i != -1; i = edge[i].pre)  
        {  
            int v = edge[i].v, w = edge[i].w;  
            if(dis[v] > w && !book[v])  
            {  
                dis[v] = w;  
                pq.push(node(v,dis[v]));  
            }  
        }  
    }  
}  
int main()  
{  
    while(~scanf("%d",&n),n)  
    {  
        char str[maxn][8];  
        memset(book,0,sizeof(book));  
        memset(head,-1,sizeof(head));  
        k = 1, sum = 0;  
        for(int i = 1; i <= n; i++)  
        {  
            scanf("%s",str[i]);  
            for(int j = i; j >= 1; j--)  
            {  
                int ans = 0;  
                for(int l = 0; l < 7; l++)  
                {  
                    if(str[i][l] != str[j][l])  
                        ans++;  
                }  
                addedge(i,j,ans);  
                addedge(j,i,ans);  
            }  
        }  
        prim(1);  
        printf("The highest possible quality is 1/%d.\n", sum);  
    }  
    return 0;  
}  

Krusal

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<algorithm>  
using namespace std;  
const int maxn = 1e5+5;  
int n, m, r, k, pre[maxn];  
  
struct node  
{  
    int u, v, w;  
    node() {}  
    node(int uu, int vv, int ww): u(uu), v(vv), w(ww) {}  
    bool operator < (const node &a) const  
    {  
        return w < a.w;  
    }  
}edge[maxn];  
  
int Find(int x)  
{  
    int r = x;  
    while(pre[r] != r) r = pre[r];  
    int i = x, j;  
    while(i != r)  
    {  
        j = pre[i];  
        pre[i] = r;  
        i = j;  
    }  
    return r;  
}  
  
bool join(int x, int y)  
{  
    int a = Find(x);  
    int b = Find(y);  
    if(a != b)  
    {  
        pre[b] = a;  
        return true;  
    }  
    return false;  
}  
  
int main(void)  
{  
    int t;  
    cin >> t;  
    while(t--)  
    {  
        k = 0;  
        scanf("%d%d%d", &n, &m, &r);  
        for(int i = 0; i < m+n; i++) pre[i] = i;  
        int R = r;  
        while(R--)  
        {  
            int u, v, w;  
            scanf("%d%d%d", &u, &v, &w);  
            v += n;  
            w = -w;  
            edge[k++] = node(u, v, w);  
        }  
        sort(edge, edge+r);  
        int ans = (n+m)*10000, count = 0;  
        for(int i = 0; i < r; i++)  
        {  
            if(join(edge[i].u, edge[i].v))  
            {  
                count++;  
                ans += edge[i].w;  
            }  
            if(count == n+m-1) break;  
        }  
        printf("%d\n", ans);  
    }  
    return 0;  
}  

匈牙利裸题

题意:

一些学生之间是朋友关系(关系不能传递),现在要将一堆学生分成两堆,使得在同一堆的学生之间没有朋友关系。如果不可以输出“No”,可以的话输出最多可以分出几对小盆友(最大匹配)。

思路:bfs判断二分图, 然后匈牙利。。。最后答案/2,因为是双向二分图

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <algorithm>  
#include <queue>  
using namespace std;  
const int maxn = 2e3 + 5;  
vector<int> v[maxn];  
int n, m, match[maxn], book[maxn];  
int judge()  
{  
    queue<int> q;  
    memset(book, -1, sizeof(book));  
    q.push(1);  
    book[1] = 0;  
    while(!q.empty())  
    {  
        int u = q.front();  
        q.pop();  
        for(int i = 0; i < v[u].size(); i++)  
        {  
            int to = v[u][i];  
            if(book[to] == -1)  
            {  
                book[to] = !book[u];  
                q.push(to);  
            }  
            else if(book[to] == book[u])  
                return 0;  
        }  
    }  
    return 1;  
}  
int Find(int x)  
{  
    for(int i = 0; i < v[x].size(); i++)  
    {  
        int to = v[x][i];  
        if(book[to]) continue;  
        book[to] = 1;  
        if(match[to] == 0 || Find(match[to]))  
        {  
            match[to] = x;  
            return 1;  
        }  
    }  
    return 0;  
}  
int main()  
{  
    while(~scanf("%d%d", &n, &m))  
    {  
        int x, y;  
        for(int i = 0; i <= n; i++)  
            v[i].clear();  
        memset(match, 0, sizeof(match));  
        for(int i = 1; i <= m; i++)  
        {  
            scanf("%d%d", &x, &y);  
            v[x].push_back(y);  
            v[y].push_back(x);  
        }  
        if(!judge())  
        {  
            printf("No\n");  
            continue;  
        }  
        int ans = 0;  
        for(int i = 1; i <= n; i++)  
        {  
            memset(book, 0, sizeof(book));  
            ans += Find(i);  
        }  
        printf("%d\n", ans/2);  
    }  
    return 0;  
}  

线段树

区间更新区间查询

 给你N个数,Q个操作,操作有两种,‘Q a b ’是询问a~b这段数的和,‘C a b c’是把a~b这段数都加上c。

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <algorithm>  
#define lch rt*2,l,m  
#define rch rt*2+1,m+1,r  
using namespace std;  
typedef long long ll;  
const int maxn = 1e6 + 5;  
ll tree[maxn*4], mark[maxn*4];  
void pushup(int rt)   //向上传递
{  
    tree[rt] = tree[rt*2+1] + tree[rt*2];   //这里是等于  
}  
void pushdown(int rt, int l, int r)   //向下传递
{  
    int m = (l+r)/2;  
    mark[rt*2] += mark[rt];  //这里都是+=  
    tree[rt*2] += mark[rt]*(m-l+1);  
    mark[rt*2+1] += mark[rt];  
    tree[rt*2+1] += mark[rt]*(r-m);  
    mark[rt] = 0;  //不要忘记最后给mark变成0  
}  
void build(int rt, int l, int r)  
{  
    if(l == r)  
    {  
        scanf("%lld", &tree[rt]);  
        return;  
    }  
    int m = (l+r)/2;  
    build(lch);  
    build(rch);  
    pushup(rt);  //更新  
}  
void update(int rt, int l, int r, int i, int j, int val)   
{  
    if(l >= i && r <= j)   //一定要去见覆盖  
    {  
        tree[rt] += (r-l+1)*val; //这里是 +=  
        mark[rt] += val;  
        return;  
    }  
    if(mark[rt]) pushdown(rt,l,r);  
    int m = (l+r)/2;  
    if(i <= m) update(lch,i,j, val);  
    if(j > m) update(rch,i,j, val);  
    pushup(rt);   //这里要更新  
}  
ll query(int rt, int l, int r, int i, int j)  
{  
    if(l >= i && r <= j)  //区间覆盖  
    {  
        return tree[rt];  
    }  
    if(mark[rt]) pushdown(rt,l,r);  
    int m = (l+r)/2;  
    ll ans = 0;  
    if(i <= m) ans += query(lch,i,j);  
    if(j > m) ans += query(rch,i,j);  
    return ans;  
}  
int main()  
{  
    int n, m;  
    while(~scanf("%d%d", &n, &m))  
    {  
        memset(mark, 0, sizeof(mark));  
        build(1,1,n);  
        char cmd;  
        int i, j, val;  
        while(m--)  
        {  
            scanf(" %c", &cmd);  
            if(cmd == 'Q')  
            {  
                scanf("%d%d", &i, &j);  
                printf("%lld\n", query(1,1,n,i,j));  
            }  
            else  
            {  
                scanf("%d%d%d", &i, &j, &val);  
                update(1, 1, n, i, j, val);  
            }  
        }  
    }  
    return 0;  
}  

DP

求最大子矩阵

总的来说就是 n2枚举所有列的组合, 然后求出每一行在这两列之间的和(这里随着j的移动求就好了,o1复杂度,不比再一个for从头枚举), 答案就是 最大子段和了, 求一段连续的区间,让他们和最大, 只不过这里区间每个数代表的是一段数的和, 巧妙的将n5复杂度的算法通过枚举变成了n3了~

#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
using namespace std;  
const int maxn = 505;  
int a[maxn][maxn], sum[maxn];  
int main()  
{  
    int m, n;  
    scanf("%d%d", &m, &n);  
    for(int i = 1; i <= n; i++)  
        for(int j = 1; j <= m; j++)  
            scanf("%d", &a[i][j]);  
    int cur = 0, ans = 0;  
    for(int i = 1; i <= m; i++)  
    {  
        for(int j = i; j <= m; j++)  
        {  
            cur = 0;  
            for(int k = 1; k <= n; k++)  
            {  
                sum[k] = i == j ? a[k][j] : sum[k]+a[k][j];  
                cur += sum[k];  
                if(cur < 0) cur = 0;  //这里要求负数输出0,所以ans在cur后面
                ans = max(ans, cur);  
            }  
        }  
    }  
    printf("%d\n", ans);  
    return 0;  
}  

最大子段和

int maxsubsum (int a[])  
{  
    S = 0;  
    maxsum  = 0;  
    cursum = 0;  
    for(int i = 0 ; i < len ; i++)  
    {  
        cursum += a[i];  
        if(cursum < 0)   {cursum = 0;  S = i + 1;}      //  如果小于0  起始点就在他的后面一位  
        if(maxsum < cursum)  {maxsum = cursum;  s = S ; e = i;}   //  s e用来记录起始点与末尾点  
  
    }  
    return maxsum;  
}  

最长公共子序列

//最长公共子序列(可不连续)  
#include <iostream>  
#include <cstdio>  
#include <cstring>  
using namespace std;  
int dp[105][105];  //记录当前字母“前面”的最长子序列的长度  
char a[100], b[100];  
int path[150];  
int main()  
{  
    while(cin >> a >> b)  
    {  
        int len1 = strlen(a);  
        int len2 = strlen(b);  
        for(int i = 1; i <= len1; i++)  //i,j从一开始  
            for(int j = 1; j <= len2; j++)  
        {  
            if(a[i-1] == b[j-1])        //前一个相同,当前的就是前面dp+1;  
                dp[i][j] = dp[i-1][j-1] + 1;  
            else  
                dp[i][j] = max(dp[i-1][j],dp[i][j-1]);  
        }  
        cout << dp[len1][len2] << endl;  
}

最长公共子串

#include <iostream>  
#include <cstdio>  
#include <cstring>  
using namespace std;  
int dp[105][105];  //记录当前字母“前面”的最长子序列的长度  
char a[100], b[100];  
int main()  
{  
    while(cin >> a >> b)  
    {  
        int max1 = 0, temp;  
        memset(dp,0,sizeof(dp));  
        int len1 = strlen(a), len2 = strlen(b);  
        for(int i = 1; i <= len1; i++)  
            for(int j = 1; j <= len2; j++)  
            {  
                 if(a[i-1] == b[j-1])  dp[i][j] = dp[i-1][j-1] + 1;  
                 else dp[i][j] = 0;  
                 if(max1 < dp[i][j])  
                 {  
                    max1 = dp[i][j]; //纪录dp[][]中的最大值  
                    temp = i;//纪录最长公共子串的末端在str1中的位置(也可以纪录在str2中的位置)  
                 }  
            }  
        for(int i = temp - max1; i < temp; i++)  
            cout << a[i];  
            cout << endl;  
    }  
  
}  

LIS

方法1(n^2):设f(i)表示L中以ai为末元素的最长递增子序列的长度。则有如下的递推方程:

这个递推方程的意思是,在求以ai为末元素的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j<i且aj<ai。如果这样的元素存在,那么对所有aj,都有一个以aj为末元素的最长递增子序列的长度f(j),把其中最大的f(j)选出来,那么f(i)就等于最大的f(j)加上1,即以ai为末元素的最长递增子序列,等于以使f(j)最大的那个aj为末元素的递增子序列最末再加上ai;如果这样的元素不存在,那么ai自身构成一个长度为1的以ai为末元素的递增子序列。

方法2(nlogn):在第1种算法中,在计算每一个f(i)时,都要找出最大的f(j)(j<i)来,由于f(j)没有顺序,只能顺序查找满足aj<ai最大的f(j),如果能将让f(j)有序,就可以使用二分查找,这样算法的时间复杂度就可能降到O(nlogn)。于是想到用一个数组B来存储“子序列的”最大增子序列的最末元素,即有B[f(j)] = aj在计算f(i)时,在数组B中用二分查找法找到满足j<i且B[f(j)]=aj<ai的最大的j,并将B[f[j]+1]置为ai,如果j不是最后一个位置,那么说明a[i]比a[j+1]小,所以把a[j+1]换成a[i],使lis可能性更大。

#include<iostream>  
#include<cstdio>  
#include<cstring>  
using namespace std;  
const int maxn = 1005;  
int a[maxn], b[maxn], dp[maxn];  
int main(void)  
{  
    int n;  
    while(cin >> n)  
    {  
        for(int i = 0; i < n; i++) scanf("%d", &a[i]), dp[i] = 1;  
        int Len = 1, l, m, r;  
        b[1] = a[0];  
        for(int i = 1; i < n; i++)  
        {  
            l = 1, r = Len;  
            while(l <= r)  
            {  
                m = (l+r)/2;  
                if(b[m] < a[i]) l = m+1;  
                else r = m-1;  
            }  
            b[l] = a[i];  
            if(l > Len) Len++;  
        }  
        printf("%d\n", Len);  
    }  
    return 0;  
}  

输出路径

#include<iostream>  
#include<cstdio>  
using namespace std;  
const int maxn = 10005;  
int a[maxn], dp[maxn], pre[maxn], path[maxn],  ans, e;  
int main(void)  
{  
    int n;  
    while(cin >> n)  
    {  
        for(int i = 0; i < n; i++) scanf("%d", &a[i]), dp[i] = 1, pre[i] = -1;  
        ans = 1;    //初始最大长度为1,结尾在a[0]  
        e = 0;  
        for(int i = 1; i < n; i++)  
            for(int j = 0; j < i; j++)  
                if(a[j] < a[i] && dp[j]+1 > dp[i])  
                {  
                    dp[i] = dp[j]+1;  
                    pre[i] = j;     //记录每个点i的上一个最长序列,存在已他自己为下标的pre里  
                    if(dp[i] > ans) ans = dp[i], e = i;    //因为是>,而不是>=所以最后一个元素肯定是所有里面最前面的  
                }  
        printf("%d\n", ans);  
        //路径要逆推回去  
        for(int i = 0, k = ans; i < ans; i++)  
        {  
            path[k--] = a[e];   //从后往前推,最后一个节点为e;把他的值a【e】输出了  
            e = pre[e];     //pre【e】存了上一个他的节点,也就是他之前最长的长度的最后一个字母  
        }  
        for(int i = 1; i <= ans; i++)  
        {  
            if(i-1) printf(" ");  
            printf("%d", path[i]);  
        }  
        printf("\n");  
    }  
    return 0;  
}  

LICS

定义状态

F[i][j]表示以a串的前i个整数与b串的前j个整数且以b[j]为结尾构成的LCIS的长度。

状态转移方程:

①F[i][j] = F[i-1][j] (a[i] != b[j])

②F[i][j] = max(F[i-1][k]+1) (1 <= k <= j-1 && b[j] > b[k])

现在我们来说为什么会是这样的状态转移方程呢?

对于①,因为F[i][j]是以b[j]为结尾的LCIS,如果F[i][j]>0那么就说明a[1]..a[i]中必然有一个整数a[k]等于b[j],因为a[k]!=a[i],那么a[i]对F[i][j]没有贡献,于是我们不考虑它照样能得出F[i][j]的最优值。所以在a[i]!=b[j]的情况下必然有F[i][j]=F[i-1][j]。

对于②,前提是a[i] == b[j],我们需要去找一个最长的且能让b[j]接在其末尾的LCIS。之前最长的LCIS在哪呢?首先我们要去找的F数组的第一维必然是i-1。因为i已经拿去和b[j]配对去了,不能用了。并且也不能是i-2,因为i-1必然比i-2更优。第二维呢?那就需要枚举b[1]...b[j-1]了,因为你不知道这里面哪个最长且哪个小于b[j]。这里还有一个问题,可不可能不配对呢?也就是在a[i]==b[j]的情况下,需不需要考虑F[i][j]=F[i-1][j]的决策呢?答案是不需要。因为如果b[j]不和a[i]配对,那就是和之前的a[1]...a[j-1]配对(假设F[i-1][j]>0,等于0不考虑),这样必然没有和a[i]配对优越。(为什么必然呢?因为b[j]和a[i]配对之后的转移是max(F[i-1][k])+1,而和之前的i`配对则是max(F[i`-1][k])+1。

以上的代码的时间复杂度是O(n^3),那我们怎么去优化呢?通过思考发现,第三层循环找最大值是否可以优化呢?我们能否直接把枚举最大的f[i-1][k]值直接算出来呢?假设存在这么一个序列a[i] == b[j],我们继续看状态转移方程②,会发现b[j] > b[k],即当a[i] == b[j]时,可以推出a[i] > b[k],那么有了这个表达式我们可以做什么呢?可以发现,我们可以维护一个MAX值来储存最大的f[i-1][k]值。即只要有a[i] > a[j]的地方,那么我们就可以更新最大值,所以,当a[i] == b[j]的时候,f[i][j] = MAX+1,即可


可以发现,其实上面的代码有些地方与0/1背包很相似,即每次用到的只是上一层循环用到的值,即f[i-1][j],那么我们可以像优化0/1背包问题利用滚动数组来优化空间。如果是求最长公共下降子序列呢?很明显嘛,把状态定义改动一下,即f[i][j]表示以a串的前i个整数与b串的前j个整数且以b[j]为结尾构成的LCDS的长度,具体实现的时候只要把a[i] > b[j]改为a[i] < b[j]就可以啦。

#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
using namespace std;  
const int maxn = 5e2 + 5;  
int dp[maxn], a[maxn], b[maxn], n, m;  
int main()  
{  
    int t;  
    cin >> t;  
    while(t--)  
    {  
        memset(dp, 0, sizeof(dp));  
        scanf("%d", &n);  
        for(int i = 1; i <= n; i++)  
            scanf("%d", &a[i]);  
        scanf("%d", &m);  
        for(int i = 1; i <= m; i++)  
            scanf("%d", &b[i]);  
        for(int i = 1; i <= n; i++)  
        {  
            int maxx = 0;  
            for(int j = 1; j <= m; j++)  
            {  
                if(a[i] > b[j]) maxx = max(maxx, dp[j]);  
                if(a[i] == b[j]) dp[j] = maxx + 1;  
            }  
        }  
        int ans = 0;  
        for(int i = 1; i <= m; i++)  
            ans = max(ans, dp[i]);  
        if(t != 0)  
            printf("%d\n\n", ans);  
        else  
            printf("%d\n", ans);  
    }  
    return 0;  
}  

记录路径

#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
using namespace std;  
const int maxn = 505;  
int n, m, t, a[maxn], b[maxn], dp[maxn][maxn], pathx[maxn][maxn], pathy[maxn][maxn], cur, ans;  
void Printf(int x, int y)  
{  
    if(dp[x][y] == 0)  
        return;  
    if(pathx[x][y] != -1 && pathy[x][y] != -1)  
    {  
        int tx = pathx[x][y];  
        int ty = pathy[x][y];  
        Printf(tx, ty);  
        if(dp[x][y] != dp[tx][ty] && y != 0)  
        {  
            cur++;  
            if(cur < ans)  
                printf("%d ", b[y]);  
            else  
                printf("%d\n", b[y]);  
        }  
    }  
}  
int main()  
{  
    cin >> t;  
    while(t--)  
    {  
        scanf("%d", &n);  
        for(int i = 1; i <= n; i++) scanf("%d", &a[i]);  
        scanf("%d", &m);  
        for(int i = 1; i <= m; i++) scanf("%d", &b[i]);  
        memset(dp, 0, sizeof(dp));  
        memset(pathx, -1, sizeof(pathx));  
        memset(pathy, -1, sizeof(pathy));  
        int tmpx = 0, tmpy = 0;  
        for(int i = 1; i <= n; i++)  
        {  
            tmpx = 0, tmpy = 0;  
            int maxx = 0;  
            for(int j = 1; j <= m; j++)  
            {  
                dp[i][j] = dp[i-1][j];  
                pathx[i][j] = i-1;  
                pathy[i][j] = j;  
                if(a[i] > b[j] && maxx < dp[i-1][j]) maxx = dp[i-1][j], tmpx = i-1, tmpy = j;  
                if(a[i] == b[j]) dp[i][j] = maxx+1, pathx[i][j] = tmpx, pathy[i][j] = tmpy;  
            }  
        }  
        ans = 0;  
        int flag = -1;  
        for(int i = 1; i <= m; i++)  
        {  
            if(ans < dp[n][i])  
            {  
                flag = i;  
                ans = dp[n][i];  
            }  
        }  
        printf("%d\n", ans);  
        tmpx = n, tmpy = flag;  
        cur = 0;  
        if(tmpy > 0)  
            Printf(tmpx, tmpy);  
        if(t != 0)  
            puts("");  
    }  
  
    return 0;  
}  

矩阵取数(多线程DP)

设有N*N的方格图(N<=10,我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。如下图所示(见样例):

 某人从图的左上角的A 点出发,可以向下行走,也可以向右走,直到到达右下角的B点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。

n^4

#include <iostream>  
#include <cstdio>  
#include <cstring>  
using namespace std;  
const int maxn = 15;  
int dp[maxn][maxn][maxn][maxn], table[maxn][maxn];  
int main()  
{  
    int n, x, y, v;  
    scanf("%d", &n);  
    while(scanf("%d%d%d", &x,&y,&v), x+y+v)  
    {  
        table[x][y] = v;  
    }  
    for(int x1 = 1; x1 <= n; x1++)  
    for(int y1 = 1; y1 <= n; y1++)  
    for(int x2 = 1; x2 <= n; x2++)  
    for(int y2 = 1; y2 <= n; y2++)  
    {  
        dp[x1][y1][x2][y2] = max(max(dp[x1-1][y1][x2-1][y2], dp[x1-1][y1][x2][y2-1]),max(dp[x1][y1-1][x2-1][y2], dp[x1][y1-1][x2][y2-1]))+table[x1][y1];  
        if(x1 != x2 && y1 != y2) dp[x1][y1][x2][y2] += table[x2][y2];  
    }  
    cout << dp[n][n][n][n] << endl;  
    return 0;  
}  

n^3

思路:多线程dp,dp[i][j][k],i代表步数,其实我觉得是做过的行与列的总和比较好,j代表第一遍走了i步,他在第几列,k代表第二边走了i步他在第几列。。。根据步数还有列数可以推出行数,三元函数是为了让他们不重复

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <algorithm>  
using namespace std;  
const int maxn = 205;  
int dp[maxn*2][maxn][maxn], a[maxn][maxn];  
int main()  
{  
    int n, m;  
    scanf("%d%d", &m, &n);  
    for(int i = 1; i <= n; i++)  
        for(int j = 1; j <= m; j++)  
            scanf("%d", &a[i][j]);  
//    dp[0][0][0] = a[1][1];  
    int ans = 0;  
    for(int i = 2; i <= m+n; i++)  
    {  
        for(int j = 1; j <= min(i,m); j++)  
            for(int k = 1; k <= min(i,m); k++)  
            {  
                dp[i][j][k] = dp[i-1][j][k];  
                if(j) dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k]);  
                if(k) dp[i][j][k] = max(dp[i][j][k], dp[i-1][j][k-1]);  
                if(k && j) dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-1][k-1]);  
                dp[i][j][k] = dp[i][j][k] + (j == k ? a[i-j][j] : a[i-j][j] + a[i-k][k]);  
                ans = max(ans, dp[i][j][k]);  
            }  
    }  
    printf("%d\n", ans);  
    return 0;  
}  

背包模板

hdu2546 普通01背包,只是重量跟价值相同罢了,完全背包就只是把里面那个逆循环正过来就行

#include <iostream>  
#include <cstdio>  
#include <algorithm>  
#include <cstring>  
using namespace std;  
const int maxn = 1e3 + 5;  
int dp[maxn],a[maxn];  
int main()  
{  
    int n, m, sum, max1;  
    while(~scanf("%d",&n) && n)  
    {  
        memset(dp,0,sizeof(dp));  
        for(int i = 1; i <= n; i++)  
            scanf("%d",&a[i]);  
        scanf("%d",&m);  
        if(m < 5) {printf("%d\n",m); continue;}  
        sort(a+1,a+1+n);  
        m -= 5;  
        for(int i = 1; i < n; i++)  
            for(int j = m; j >= a[i]; j--)  
            {  
                dp[j] = max(dp[j],dp[j-a[i]]+a[i]);  
            }  
        printf("%d\n",m-dp[m]+5-a[n]);  
    }  
    return 0;  
}  

hdu 2191 多重背包模板(普通的)

#include <iostream>  
#include <cstring>  
#include <algorithm>  
#include <cstdio>  
using namespace std;  
const int maxn = 105;  
int dp[maxn], v[maxn], w[maxn], c[maxn];  
int main()  
{  
    int C, n, m;  
    scanf("%d",&C);  
    while(C--)  
    {  
        scanf("%d%d",&n,&m);  
        for(int i = 1; i <= m; i++)  
            scanf("%d%d%d",&v[i],&w[i],&c[i]);  
        memset(dp,0,sizeof(dp));  
        for(int i = 1; i <= n; i++)  
        {  
            for(int k = 1; k <= c[i]; k++)    //有c[i]个就循环c[i]次呗  
            {  
                for(int j = n; j >= v[i]; j--)  
                {  
                    dp[j] = max(dp[j],dp[j-v[i]] + w[i]);  
                }  
            }  
        }  
        printf("%d\n",dp[n]);  
    }  
    return 0;  
}  

二进制多重背包

#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<string>  
using namespace std;  
int main()  
{  
    int C;  
    int n,m;  
    int i,j,k;  
    int p[600],h[600],c[600];//价格,重量,袋数  
    int w[600],v[600];//重新分配  
    int dp[10005];  
    int index;  
    scanf("%d",&C);  
    while(C--)  
    {  
        memset(dp,0,sizeof(dp));  
        scanf("%d%d",&n,&m);  
        index = 1;  
        for(i = 1; i <= m; i++)  
        {  
            scanf("%d%d%d",&p[i],&h[i],&c[i]);  
            //利用二进制分解法,拆解物品,转化成01背包  
            for(j = 1; j <= c[i]; j <<= 1)  
            {  
                v[index] = j*p[i];  
                w[index++] = j*h[i];  
                c[i] -= j;  
            }  
            //不能正好分解的有剩余的部分单独作为一个物品。  
            if(c[i]>0)  
            {  
                v[index] = c[i]*p[i];  
                w[index++] = c[i]*h[i];  
            }  
        }  
        //01背包  
        for(i = 1; i < index; i++)  
        {  
            for(j = n; j >=v[i]; j--)  
            {  
                dp[j]=max(dp[j],dp[j-v[i]]+w[i]);  
            }  
        }  
        printf("%d\n",dp[n]);  
    }  
    return 0;  
}  

并查集

就是用并查集求出有几个集合,每个集合的值是多少,裸题,别把sum[Find(x)] += sum[Find(y)]写成 sum[x] += sum[y] 了  

#include <iostream>  
#include <cstdio>  
#include <cstring>  
#include <algorithm>  
using namespace std;  
const int maxn = 1e3 + 5;  
int n, m, pre[maxn], sum[maxn], sum2[maxn], book[maxn][maxn];  
int Find(int x)  
{  
    return pre[x] == x ? x : pre[x] = Find(pre[x]);  
}  
void join(int x, int y)  
{  
    if(Find(y) != Find(x))  
        sum[Find(x)] += sum[Find(y)];  
    pre[Find(y)] = Find(x);  
  
}  
int main()  
{  
    int t, ca = 1;  
    scanf("%d", &t);  
    while(t--)  
    {  
//        memset(book, 0, sizeof(book));  
        scanf("%d%d", &n, &m);  
        for(int i = 1; i <= n; i++)  
        {  
            scanf("%d", &sum[i]);  
            pre[i] = i;  
        }  
        int u, v;  
        while(m--)  
        {  
            scanf("%d%d", &u, &v);  
            join(u, v);  
        }  
        int ans = 0, index = 1;  
        for(int i = 1; i <= n; i++)  
            if(pre[i] == i)  
                ans++, sum2[index++] = sum[i];  
        printf("Case %d: %d\n", ca++, ans);  
        sort(sum2+1,sum2+index);  
        for(int i = 1; i <= ans; i++)  
        {  
            printf("%d%c", sum2[i], i == ans ? '\n' : ' ');  
        }  
    }  
    return 0;  
}  

字符串

马拉车

原理解释

#include <cstdio>  
#include <iostream>  
#include <cstring>  
using namespace std;  
const int maxn = 100005;  
char str[maxn];  
char temp[maxn*2];  
int l[maxn*2];  
void malache(char *str)  
{  
    int len = strlen(str);  
    temp[0]='¥';  
    temp[1]='#';  
    for(int i = 0; i < len ; i++)  
    {  
        temp[(i+1)*2] = str[i];  
        temp[(i+1)*2+1] = '#';  
    }  
    int mx = 0, po = 0,ans = 0;  
    for(int i = 0;i <= 2*len+1; i++)  
    {  
        if(i < mx)   l[i] = min(l[2*po-i],mx-i);  
        else l[i] = 1;  
        while(temp[i-l[i]] == temp[i+l[i]])  l[i]++;  
        if(l[i]+i > mx)   {po = i; mx = l[i]+i;}  
        ans = max(ans , l[i]);  
    }  
    cout << ans - 1 <<endl;  
}  
int main()  
{  
    int t;  
    cin >> t;  
    while(t--)  
    {  
        memset(temp,0,sizeof(temp));  
        cin >> str;  
        malache(str);  
    }  
    return 0;  
}  

kmp

原理解释

例题

对主串做next数组

#include<bits/stdc++.h>  
using namespace std;  
const int maxn = 1005;  
char s[maxn], t[maxn];  
int Next[maxn], ans;  
  
void makeNext(void)  
{  
    int len = strlen(s);  
    Next[0] = Next[1] = 0;  
    for(int i = 1; i < len; i++)  
    {  
        int j = Next[i];  
        while(j && s[i] != s[j]) j = Next[j];  
        Next[i+1] = s[i]==s[j] ? j+1 : 0;  
    }  
}  
  
void kmp(void)  
{  
    int len1 = strlen(s);  
    int len2 = strlen(t);  
    int i, j = 0;  
    for(int i = 0; i < len1; i++)  
    {  
        while(j && s[i] != t[j]) j = Next[j];  
        if(s[i] == t[j]) j++;  
        if(j == len2) ans++, j = 0;  
    }  
}  
  
int main(void)  
{  
    while(~scanf(" %s", s))  
    {  
        if(strlen(s) == 1 && s[0] == '#') break;  
        scanf(" %s", t);  
        ans = 0;  
        makeNext();  
        kmp();  
        printf("%d\n", ans);  
    }  
    return 0;  
}  

对子串做next数组

#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e6+5;
int n,m,p,next[N],a[N],b[N];
void getnext()
{
    int i=0,j=-1;
    next[i]=j;
    while(i<m)
    {
        if(j==-1||b[i]==b[j])
        {
            i++;
            j++;
            next[i]=j;
        }
        else
            j=next[j];
    }
}
int kmp()
{
    int res=0;
    getnext();
    for(int k=0;k<p;++k){//进行p次匹配 
        int i=k,j=0;
        while(i<n)
        {
            if(j==-1||a[i]==b[j])
            {
                i+=p;//在母串上每隔p个位置匹配一次 
                j++;
                if(j==m)
                	res++;
            }
            else
                j=next[j];
        }
    }
    return res;
}
int main()
{
    int T;
    scanf("%d",&T);
    for(int kase=1;kase<=T;kase++)
    {
        scanf("%d%d%d",&n,&m,&p);
        memset(next,0,sizeof(next));
        memset(b,0,sizeof(b));//注意要清空b数组,这里WA了两发,迷之耽误一个小时 
        memset(a,0,sizeof(a));
        for(int i=0;i<n;i++)scanf("%d",&a[i]);
        for(int i=0;i<m;i++)scanf("%d",&b[i]);
        printf("Case #%d: %d\n",kase,kmp());
    }
    return 0;
}

扩展KMP

点击打开链接

const int maxn=100010;   //字符串长度最大值
int next[maxn],ex[maxn]; //ex数组即为extend数组
//预处理计算next数组
void GETNEXT(char *str)
{
    int i=0,j,po,len=strlen(str);
    next[0]=len;//初始化next[0]
    while(str[i]==str[i+1]&&i+1<len)//计算next[1]
    i++;
    next[1]=i;
    po=1;//初始化po的位置
    for(i=2;i<len;i++)
    {
        if(next[i-po]+i<next[po]+po)//第一种情况,可以直接得到next[i]的值
        next[i]=next[i-po];
        else//第二种情况,要继续匹配才能得到next[i]的值
        {
            j=next[po]+po-i;
            if(j<0)j=0;//如果i>po+next[po],则要从头开始匹配
            while(i+j<len&&str[j]==str[j+i])//计算next[i]
            j++;
            next[i]=j;
            po=i;//更新po的位置
        }
    }
}
//计算extend数组
void EXKMP(char *s1,char *s2)
{
    int i=0,j,po,len=strlen(s1),l2=strlen(s2);
    GETNEXT(s2);//计算子串的next数组
    while(s1[i]==s2[i]&&i<l2&&i<len)//计算ex[0]
    i++;
    ex[0]=i;
    po=0;//初始化po的位置
    for(i=1;i<len;i++)
    {
        if(next[i-po]+i<ex[po]+po)//第一种情况,直接可以得到ex[i]的值
        ex[i]=next[i-po];
        else//第二种情况,要继续匹配才能得到ex[i]的值
        {
            j=ex[po]+po-i;
            if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
            while(i+j<len&&j<l2&&s1[j+i]==s2[j])//计算ex[i]
            j++;
            ex[i]=j;
            po=i;//更新po的位置
        }
    }
}

字典树

#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
const int maxn = 1e6 + 7;  
int id, ch[maxn][30], cnt[maxn];  
char str[15];  
void Insert(char *s)  
{  
    int rt = 0;  
    int len = strlen(s);  
    for(int i = 0; i < len; i++)  
    {  
        if(!ch[rt][s[i]-'a'])  
        {  
            memset(ch[id], 0, sizeof(ch[id]));  
            cnt[id] = 0;  
            ch[rt][s[i]-'a'] = id++;  
        }  
        rt = ch[rt][s[i]-'a'];  
        cnt[rt]++;  
    }  
}  
int match(char *s)  
{  
    int rt = 0;  
    int len = strlen(s);  
    for(int i = 0; i < len; i++)  
    {  
        if(!ch[rt][s[i]-'a'])  
            return 0;  
        rt = ch[rt][s[i]-'a'];  
    }  
    return cnt[rt];  
}  
int main()  
{  
    id = 1;  
    memset(ch[0], 0, sizeof(ch[0]));  
    while(gets(str))  
    {  
        if(!strlen(str)) break;  
        Insert(str);  
    }  
    while(gets(str) != NULL)  
        printf("%d\n", match(str));  
    return 0;  
}  

矩阵快速幂

模板

#include <iostream>  
#include <cstdio>  
#include <cstring>  
using namespace std;  
const int MOD = 1e4;  
struct node  
{  
    int matrix[2][2];  
    node() {}  
    node(int a, int b, int c, int d)  
    {  
        matrix[0][0] = a;  
        matrix[0][1] = b;  
        matrix[1][0] = c;  
        matrix[1][1] = d;  
    }  
};  
node mul(node p, node q)  
{  
    node t = node(0, 0, 0, 0);  
    for(int i = 0; i < 2; i++)  
        for(int j = 0; j < 2; j++)  
            for(int k = 0; k < 2; k++)  
            t.matrix[i][j] = (t.matrix[i][j] + p.matrix[i][k] * q.matrix[k][j]) % MOD;  
    return t;  
}  
node quick_matrix(node p, int n)  
{  
    node q = node(1, 0, 0, 1);  
    while(n)  
    {  
        if(n & 1) q = mul(p,q);  
        p = mul(p, p);  
        n >>= 1;  
    }  
    return q;  
}  
int main()  
{  
    int n;  
    node p;  
    while(scanf("%d", &n), n+1)  
    {  
        p = node(1, 1, 1, 0);  
        if(n == 0) {printf("0\n" ); continue;}  
        p = quick_matrix(p, n-1);  
        printf("%d\n", p.matrix[0][0]);  
    }  
    return 0;  
}  

错排公式

编号为 1 , 2 ,……, n 的 n 个元素排成一列,若每个元素所处位置的序号都与它的编号不同,则称这个排列为 n 个不同元素的一个错排。记 n 个不同元素的错排总数为 f(n) ,则f(n) = n![1-1/1!+1/2!-1/3!+……+(-1)^n*1/n!]( 1 )本文从另一角度对这个问题进行一点讨论。1. 一个简单的递推公式n 个不同元素的一个错排可由下述两个步骤完成:

第一步,“错排” 1 号元素(将 1 号元素排在第 2 至第 n 个位置之一),有 n - 1 
种方法。
第二步,“错排”其余 n - 1 个元素,按如下顺序进行。视第一步的结果,若 1 
号元素落在第 k 个位置,第二步就先把 k 号元素“错排”好, k 
号元素的不同排法将导致两类不同的情况发生:( 1 ) k 号元素排在第 1 
个位置,留下的 n - 2 个元素在与它们的编号集相等的位置集上“错排”,有 f(n -2) 
种方法;( 2 ) k 号元素不排第 1 个位置,这时可将第 1 个位置“看成”第 k 
个位置,于是形成(包括 k 号元素在内的) n - 1 个元素的“错排”,有 f(n - 1) 
种方法。据加法原理,完成第二步共有 f(n - 2)+f(n - 1) 种方法。
根据乘法原理, n 个不同元素的错排种数f(n) = (n-1)[f(n-2)+f(n-1)] (n>2) 。

#include <iostream>  
#include <cstdio>  
#include <cstring>  
using namespace std;  
const int maxn = 105;  
const int mm = 1e9 + 7;  
long long a[maxn];  
int main()  
{  
    int t,n;  
    scanf("%d",&t);  
    a[1] = 0; a[2] = 1;  
    for(int i = 3; i <= maxn; i++)  
        a[i] = (i-1)*(a[i-1]+a[i-2])%mm;  
    while(t--)  
    {  
        scanf("%d",&n);  
        cout << a[n] << endl;  
    }  
    return 0;  
}  

康拓展开式

原理

#include <cstring>  
#include <cstdio>  
using namespace std;  
int fact(int x)  
{  
    int ans = 1;  
    for(int i = 1; i < x; i++)  
        ans *= i;  
    return ans;  
}  
int main()  
{  
    char str[12];  
    while(~scanf("%s",str))  
    {  
  
        int len = strlen(str);  
        int ans = 0;  
        for(int i = 0; i < len; i++)  
        {  
            int l = 0;  
            for(int j = i + 1; j < len; j++)  
                if(str[j] < str[i])  
                l++;  
            ans += l * fact(len-i);  
        }  
        printf("%d\n",ans+1); //康托展开式只是求他前面有几个值,求这个数式第几个值还得+1;  
    }  
    return  0;  
}  

逆康拓展开

int  fac[] = {1,1,2,6,24,120,720,5040,40320};    
//康托展开的逆运算,{1...n}的全排列,中的第k个数为s[]    
void reverse_kangtuo(int n,int k,char s[])    
{    
    int i, j, t, vst[8]={0};    
    --k;    
    for (i=0; i<n; i++)    
    {    
        t = k/fac[n-i-1];    
        for (j=1; j<=n; j++)    
            if (!vst[j])    
            {    
                if (t == 0) break;    
                --t;    
            }    
        s[i] = '0'+j;    //注意这里是s【i】,用i保存  
        vst[j] = 1;    
        k %= fac[n-i-1];    
    }    
}
HDU 1027
#include<iostream>  
#include<cstdio>  
#include<cstring>  
#include<cmath>  
using namespace std;  
const int maxn = 10;  
int fac[maxn] = {1, 1};  
bool vis[100005];  
  
void init()  
{  
    for(int i = 2 ; i < maxn; i++)  
        fac[i] = fac[i-1]*i;  
}  
  
int main(void)  
{  
    init();  
    int n, m;  
    while(cin >> n >> m)  
    {  
        memset(vis, 0, sizeof(vis));  
        m--;  
        int temp = 1;  
        while(temp < n)  
        {  
            if((n-temp) <= 8)  
            {  
                int k = m/fac[n-temp];  
                m = m%fac[n-temp];  
                int cnt = 0;  
                for(int i = 1; i <= n; i++)  
                {  
                    if(!vis[i]) cnt++;  
                    if((cnt-1) == k)  
                    {  
                        printf("%d ", i);  
                        vis[i] = 1; break;  
                    }  
                }  
            }  
            else  
            {  
                for(int i = 1; i <= n; i++)  
                {  
                    if(!vis[i])  
                    {  
                        vis[i] = 1;  
                        printf("%d ", i); break;  
                    }  
                }  
            }  
            ++temp;  
        }  
        for(int i=1; i<=n; i++)  
            if(!vis[i]) printf("%d\n", i);  
    }  
    return 0;  
}      

逆元

原理

罗马小定理

#include<bits/stdc++.h>  
using namespace std;  
const int mod = 9973;  
const int maxn = 1e5+5;  
char str[maxn];  
int h[maxn];  
  
int p(int a, int n)  
{  
    int ans = 1;  
    while(n)  
    {  
        if(n&1) ans = ans*a%mod;  
        a = a*a%mod;  
        n /= 2;  
    }  
    return ans;  
}  
  
int main(void)  
{  
    int q, a, b;  
    h[0] = 1;  
    while(cin >> q)  
    {  
        scanf(" %s", str);  
        int len = strlen(str);  
        for(int i = 0; i < len; i++)  
            h[i+1] = h[i]*(str[i]-28)%mod;  
        while(q--)  
        {  
            scanf("%d%d", &a, &b);  
            printf("%d\n", h[b]*p(h[a-1], mod-2)%mod);  
        }  
    }  
    return 0;  
}  

线性逆元

#include<bits/stdc++.h>  
using namespace std;  
const int mod = 9973;  
const int maxn = 1e5+5;  
char str[maxn];  
int q, a, b, inv[maxn], h[maxn];  
int main(void)  
{  
    h[0] = inv[1] = 1;  
    for(int i = 2; i < mod; i++)  
        inv[i] = (mod-mod/i)*inv[mod%i]%mod;  
    while(cin >> q)  
    {  
        scanf(" %s", str);  
        int len = strlen(str);  
        for(int i = 0; i < len; i++)  
            h[i+1] = h[i]*(str[i]-28)%mod;  
        while(q--)  
        {  
            scanf("%d%d", &a, &b);  
            printf("%d\n", h[b]*inv[h[a-1]]%mod);  
        }  
    }  
    return 0;  
}  

线性筛

#include<bits/stdc++.h>
using namespace std;
int n,cnt;
int prime[100000];
bool vis[100000];

void Euler()
{
    for(int i=2;i<=n;i++)
    {
        if(!vis[i]) prime[++cnt]=i;
        for(int j=1;j<=cnt&&i*prime[j]<=n;j++)
        {
            vis[i*prime[j]]=1;
            if(i%prime[j]==0)
            break;
        }
    }
}

int main()
{
  cin>>n;
  Euler();
  for(int i=1;i<=cnt;i++)
  cout<<prime[i]<<' ';
  return 0;
}

LCA

原理挑战程序设计

#include <iostream>  
#include <cstring>  
#include <cstdio>  
#include <algorithm>  
using namespace std;  
const int maxn = 1e4 + 7;  
const int maxm = 21;  
int n, dis[maxn], dep[maxn], p[maxm][maxn], head[maxn], K;  
struct node  
{  
    int v, w, next;  
    node(){}  
}edge[maxn*2];  
void init()  
{  
    memset(head, -1, sizeof(head));  
    K = 0;  
}  
void addEdge(int u, int v, int w)  
{  
    edge[K].v = v;  
    edge[K].w = w;  
    edge[K].next = head[u];  
    head[u] = K++;  
}  
void dfs(int u, int f, int d)  
{  
    dep[u] = d;  
    p[0][u] = f;  
    for(int i = head[u]; i != -1; i = edge[i].next)  
    {  
        int to = edge[i].v;  
        if(to == f) continue;  
        dis[to] = dis[u] + edge[i].w;  
        dfs(to, u, d+1);  
    }  
}  
void build()  //构建lca  
{  
    dfs(1, -1, 0);  //第一遍统计, 1是根节点  
    for(int i = 0; i+1 < maxm; i++)  
    {  
        for(int v = 1; v <= n; v++)  
        {  
            if(p[i][v] < 0) p[i+1][v] = -1;  
            else p[i+1][v] = p[i][p[i][v]];  
        }  
    }  
}  
int LCA(int u, int v)  
{  
    if(dep[u] > dep[v]) swap(u, v);  
    for(int i = 0; i < maxm; i++)  
    {  
        if((dep[v]-dep[u])>>i&1)  
            v = p[i][v];  
    }  
    if(u == v) return u;  
    for(int i = maxm-1; i >= 0; i--)  
    {  
        if(p[i][u] != p[i][v])  
        {  
            u = p[i][u];  
            v = p[i][v];  
        }  
    }  
    return p[0][u];  
}  
int get_kth(int u, int v, int lca, int k) //寻找这条路径上第k个点  
{  
    k--;  //因为u算第一个点, 所以k--  
    if(dep[u] - dep[lca] < k)  //如果第k个节点要跨过lca那个点,就等于求v到他那条链上距离为k'的点  
    {  
        k = dep[u] + dep[v] - dep[lca]*2 - k; //k'  
        u = v;  
    }  
    for(int i = 0; i < maxm; i++)  //倍增找距离这个节点为k的节点,注意这里是从0开始循环的,求距离内最远的某个点就要倒着了  
        if(k & (1<<i))  
            u = p[i][u], k ^= (1<<i);  
    return u;  
}  
int main()  
{  
    int T;  
    scanf("%d", &T);  
    while(T--)  
    {  
        scanf("%d", &n);  
        init();  
        int u, v, w;  
        for(int i = 1; i < n; i++)  
        {  
            scanf("%d%d%d", &u, &v, &w);  
            addEdge(u, v, w);  
            addEdge(v, u, w);  
        }  
        build();  
        char cmd[10];  
        while(1)  
        {  
            scanf("%s", cmd);  
            if(cmd[1] == 'O') break;  
            scanf("%d%d", &u, &v);  
            if(cmd[1] == 'I')  
            {  
                int lca = LCA(u, v);  
                printf("%d\n", dis[u] + dis[v] - 2*dis[lca]); //常规的求两点路径的距离  
            }  
            else  
            {  
                int k;  
                scanf("%d", &k);  
                int lca = LCA(u, v);  
                printf("%d\n", get_kth(u, v, lca, k));  
            }  
        }  
    }  
    return 0;  
}  

猜你喜欢

转载自blog.csdn.net/qq_34374664/article/details/80412575