目录
最短路spfa、dij、floyd + 记录路径
floyd:
spfa 和 dij
次短路
其他例题:
博弈
SG函数
尼姆博弈
威佐夫博弈
巴什博弈
Tarjan算法
缩点裸题
强联通缩点的应用
最小生成树
prime
Krusal
匈牙利裸题
区间更新区间查询
DP
求最大子矩阵
最大子段和
最长公共子序列
最长公共子串
LIS
LICS
矩阵取数(多线程DP)
背包模板
并查集
字符串
马拉车
kmp
对主串做next数组
对子串做next数组
扩展KMP
字典树
矩阵快速幂
错排公式
康拓展开式
逆元
线性筛
LCA
思路:用一个二维数组path[][]来记录。这里有两种方法,1 用path[ i ][ j ]记录 j 的前驱顶点 ;2 用path[ i ][ j ]记录 i 的后面的点。
提醒:需要注意的是path的初始化
#include
#include
#include
#include
#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;
}
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
{
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[] v = new Vector[maxn];
static void spfa()
{
Arrays.fill(book, 0);
Arrays.fill(pre, -1);
Arrays.fill(dis, 0);
Queue q = new LinkedList();
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 pq = new PriorityQueue();
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();
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
#include
#include
#include
#include
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 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 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 即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
#include
#include
#include
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
#include
#include
#include
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
#include
#include
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 A第一次取一个t,(1<= s-t <=p),那么B取一个m,A取p+q-m,最后就剩下s-t个硬币留给B取,B得一次性取完,B必败。
(4)如果n=(p+q)*k+s,(q 先取q,这样剩下了 s < p,然后b取m,a跟着取p+q-m,最后剩下的m一定是b拿,所以a赢
简而言之就是:谁面对余数在1-p,谁就一定输,面对余数>p都可以胜利,因为他可以一步变成余数小于p的,让对面面对余数在1-p
#include
#include
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;
}
算法流程是: 开始先随机选一个点,沿着这个点进行遍历, 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
也就是,给定一个有向图,求:
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
#include
#include
#include
#include
#include
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 v[maxn];
stack 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
#include
#include
#include
#include
#include
#include
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 v[maxn];
stack 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;
}
#include
#include
#include
#include
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 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;
}
#include
#include
#include
#include
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
#include
#include
#include
#include
using namespace std;
const int maxn = 2e3 + 5;
vector v[maxn];
int n, m, match[maxn], book[maxn];
int judge()
{
queue 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
#include
#include
#include
#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;
}
总的来说就是 n2枚举所有列的组合, 然后求出每一行在这两列之间的和(这里随着j的移动求就好了,o1复杂度,不比再一个for从头枚举), 答案就是 最大子段和了, 求一段连续的区间,让他们和最大, 只不过这里区间每个数代表的是一段数的和, 巧妙的将n5复杂度的算法通过枚举变成了n3了~
#include
#include
#include
#include
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
#include
#include
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
#include
#include
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;
}
}
方法1(n^2):设f(i)表示L中以ai为末元素的最长递增子序列的长度。则有如下的递推方程:
这个递推方程的意思是,在求以ai为末元素的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j元素的最长递增子序列的长度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由于f(j)没有顺序,只能顺序查找满足aj
#include
#include
#include
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
#include
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;
}
定义状态
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
#include
#include
#include
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
#include
#include
#include
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;
}
设有N*N的方格图(N<=10,我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。如下图所示(见样例):
某人从图的左上角的A 点出发,可以向下行走,也可以向右走,直到到达右下角的B点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。
n^4
#include
#include
#include
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
#include
#include
#include
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
#include
#include
#include
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
#include
#include
#include
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
#include
#include
#include
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
#include
#include
#include
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
#include
#include
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 <> t;
while(t--)
{
memset(temp,0,sizeof(temp));
cin >> str;
malache(str);
}
return 0;
}
原理解释
例题
#include
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;
}
#include
#include
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
点击打开链接
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+1po+next[po],则要从头开始匹配
while(i+jex[po]+po则要从头开始匹配
while(i+j
#include
#include
#include
#include
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
#include
#include
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
#include
#include
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
#include
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
#include
#include
#include
#include
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
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
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
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<
原理挑战程序设计
#include
#include
#include
#include
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<