最短路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;
}