例,01问题
求长度为n的01串,满足如下条件
1,长为L0的连续子串中0的个数不少A0,不多于B0
2,长为L1的连续子串中的1的个数不少于A1,不多于B1
如果不存在,输出-1
这个题可以构造图,然后求图的各点的最短路径。
由于上述1,2条件的存在,对于任意的k,用f(k)表示前k串的和
则f(k+L0)-f(k)>=L0-b0
f(k+L0)-f(k)<=L0-a0
f(k+L1)-f(k)>=a1
f(k+L1)-f(k)<=b1
f(k+1)-f(k)>=0
f(k+1)-f(k)<=1
根据<=的关系可以构造一个图,图中的各个节点就是从1到n,关系如上不等式。相当于求n个点到点0的最短距离。而相邻的两个点与0点之间的距离就是我们要求的该位置的0或者1
由于该算法中权值存在负值,所以用bellman松弛法求单源最短路径
源代码如下
#include "stdafx.h" #include #include #include void buildGraph(int n,int firstL,int secondL,int a0,int a1,int b0,int b1,int *h,int **graph); void getH(int n,int *h,int ** graph); void print(int n,int *h); int main() { std::cout<<"ok"<graph[i][j]+h[i]) { std::cout<<-1<=firstL) { graph[i][i-firstL]=b0-firstL; graph[i-firstL][i]=firstL-a0; } if(i>=secondL) { graph[i-secondL][i]=b1; graph[i][i-secondL]=-a1; } if(i>1) { graph[i][i-1]=0; graph[i-1][i]=1; } } }
bellman_ford算法
BELLMAN_FORD(G,w,s)
Init-single-source(G,s)
for i<-1 to |V[G]|-1
do for each edge(u,v) in E[G]
do relax(u,v,w)
for each edge(u,v) in E[G]
do if d[v]>d[u]+w(u,v)
then return FALSE
return TRUE
松弛法
Relax(u,v,w)
if d[v]>d[u]+w(u,v)
then d[v]<-d[u]+w(u,v)
trace[v]<-u;
同时复习一下Dijkstra算法
Dijkstra(G,w,s)
Init-single-source(G,s)
S=null;
Q=V[G];
while Q!=null
do u=min(Q)
S=S+{u}
for each vertex v in Adj[u]
do Relax (u,v,w)
附一个Dijkstra算法的实现
for(int i=1;i<=pointNum;i++)
{
min=MaxInt;
for(int j=i+1;j<=pointNum;j++)
{
if(!isVisited[j])
{
if(d[1][j]
{
minPoint=j;
min=d[1][j];
}
}
}
isVisited=true;
for(int j=1;j<=pointNum;j++)
{
if(d[pointNum][j]+min
d[1][j]=d[pointNum][j]+min;
}
}
2,二分图
求二分图的最大匹配有两种算法:最大流,匈牙利算法
求二分图的最佳匹配:最大流。
最大流的算法基于以下的思想:残留网络,增广路径,最小割。
求最大流的算法过程:1,随意找一个源点到终点的增广路径。2,求该网络的残留网络。3,在残留网络上找增广路径,将增广路径上可通过的最大值加到该路径上的图的流上。4重复2,3直到没有新的增广路径。找增广路径的时候用广度优先算法可以增加算法的效率。
对二分图求最大最优匹配的时候,只用额外的加一个源点和终点即可。
求最大匹配最好还是用匈牙利算法。
以下用匈牙利算法来求解,国王心爱的王子选妻子的题目:国王有n个儿子,宫里有n个美女。每个王子喜欢一些姑娘(艹),国王对每个儿子的喜欢程度不同为Ai,如果王子挑选到了喜欢的姑娘,国王的高兴程度+Ai^2。找一个算法,让国王足够满意。
关于二分图的另一个说明:完全二分图的边的数目小于n^2/4,证明很简单,省略。
在下面的程序中,王子为1~n,女孩为n+1~2n,边的数目正好为n^2,所以开了n^2条边。
#include "stdafx.h" #include #include #ifndef PRINCESSELECTGIRLS_H #define PRINCESSELECTGIRLS_H struct LovedGirl { int index; int next; }; class PrincesSelectGirls { public: PrincesSelectGirls() { std::cin>>n; kingLove=new int[n+1]; princesGirls=new LovedGirl[n*n]; isVisited=new bool[n+1]; link=new int[n+1]; first=new int[n+1]; tot=0; a=new int[n+1]; memset(isVisited,false,sizeof(isVisited)*(n+1)); memset(link,0,sizeof(link)*(n+1)); memset(first,0,sizeof(first)*(n+1)); memset(a,0,sizeof(a)*(n+1)); for(int i=1;i<=n;i++) { std::cin>>kingLove[i]; a[i]=i; } int tempInt; for(int i=1;i<=n;i++) { for(int j=i+1;j<=n;j++) { if(kingLove[i]>tempInt; while(tempInt>0) { add(i,n+tempInt); std::cin>>tempInt; } } for(int i=1;i<=n;i++) { memset(isVisited,false,sizeof(isVisited)*(n+1)); find(a[i]); } } void add(int x,int y) { tot++; princesGirls[tot].index=y; princesGirls[tot].next=first[x]; first[x]=tot; } bool find(int x) { int k=first[x]; while(k!=0) { int i=princesGirls[k].index; if(!isVisited[i-n]) { isVisited[i-n]=true; if(link[i-n]==0||find(link[i-n])) { link[i-n]=x; std::cout<<"prince "<
关于二分图题目2
求二分图中的必须的边
算法有两种:1,枚举所有的边,然后用匈牙利算法求最大匹配,如果找不到最大匹配,则是必须的
这种算法的情况下,如果是邻接矩阵的话,枚举每条边需要的时间是O(n^2),当然也可以存边,存边的话O(e),匈牙利算法的复杂度为O(EN)
2,缩小查找空间,必须的边一定在一个完备匹配中,因为必须的边在所有的完备匹配中,如果少了这个边,一个完备匹配都找不到,所以可以先找一个完备匹配,然后剔除其中的边,用匈牙利算法验证。这样的话,将边的范围减小了。
代码如下
#ifndef NECEEDGE_H #define NECEEDGE_H #include #include #define MAXN 101 class NeceEdge { public: NeceEdge() { memset(edge,0,sizeof(edge)); memset(match,0,sizeof(match)); memset(visited,0,sizeof(visited)); memset(g,0,sizeof(g)); std::cin>>edgeNum>>n; for(int i=1;i<=edgeNum;i++) { std::cin>>edge[i][0]>>edge[i][1]; g[edge[i][0]][edge[i][1]]=1; } findNum=0; for(int i=1;i<=2*n;i++) { memset(visited,0,sizeof(visited)); if(getFirstPerfect(i)) findNum++; } if(findNum2*n) break; g[j][match[j]]=0; if(getMatch()
3, 线段树的应用
问题:树的统计。一棵含有n个节点的树,所有节点的编号依次为1,2,3,...,n。对于编号为v的节点,定义t(v)为后代中所有编号小于v的节点个数。请计算每个节点i的t(i)。
用到的数据结构,树,线段树。算法dfs
不妨先思考这样一个方法:假如这是一颗二叉树。我们将这棵二叉树保存在数组里面。从数组最末开始,向前扫描(本质是从叶子节点开始)。对于每一个节点,和父节点n/2比较,如果比这个父节点小,父节点的t值+1,然后父节点的父节点。算法复杂度为(nlogn)当然二叉树比较好的情况的时候。不好的时候复杂度就要高,最坏n^2。
对于这棵树,不方便用二叉树来做。
本题的算法很有意思。它是这样做的,先对树做一次正向dfs先序遍历,得到遍历序列,然后反向dfs先序,得到一个遍历序列,而,两个遍历序列重叠的部分,正好是树根的子节点。那么
t=正向dfs中小于v的节点数量+逆向中的+祖先中小于v的-整棵树小于v的(整棵树小于v的为v-1个)
该算法对线段树进行了灵活的应用,值得学习。一下是代码和测试用例
#include "stdafx.h" #include #include #ifndef TREECOUNT_H #define TREECOUNT_H //在该算法中运用了线段树的思想,在线段树的构造过程中,完成了对dfs顺序逆序序列的统计,该算法的时间复杂度为nlogn #define MAXM 100//用来表示节点最大的度,例如,最大为5叉的树 #define MAXN 100//最多的节点上数量 class TreeCount { public: TreeCount() { memset(dfs1,0,sizeof(dfs1)); memset(dfs2,0,sizeof(dfs2)); memset(isVisited,false,sizeof(isVisited)); memset(e,0,sizeof(e)); memset(sum,0,sizeof(sum)); std::cin>>n; for(int i=1;i<=n;i++) { std::cin>>e[i][0]; for(int j=1;j<=e[i][0];j++) { std::cin>>e[i][j]; this->isVisited[e[i][j]]=true; } } for(int i=1;i<=n;i++) if(this->isVisited[i]==false) root=i; dfsC1=0; dfsC2=0; dfs(root); dfsN(root); memset(sum,0,sizeof(sum)); memset(t,0,sizeof(t)); for(int i=n;i>=1;i--) { t[dfs1[i]]=t[dfs1[i]]+check(1,1,n,dfs1[i]-1); //t[dfs1[i]]=t[dfs1[i]]+sum[dfs1[i]-1]; insert(1,1,n,dfs1[i]); } memset(sum,0,sizeof(sum)); for(int i=n;i>=1;i--) { t[dfs2[i]]=t[dfs2[i]]+check(1,1,n,dfs2[i]-1); //t[dfs1[i]]=t[dfs1[i]]+sum[dfs1[i]-1]; insert(1,1,n,dfs2[i]); } memset(sum,0,sizeof(sum)); dfsP(root); for(int i=1;i<=n;i++) { t[i]=t[i]-i+1; std::cout<e[i][0];j++) { dfsP(e[i][j]); } this->deleteNode(1,1,n,i); } void TreeCount::deleteNode(int i,int s,int t,int x) { if(t==s) { sum[i]--; return; } int middle=(t+s)/2; if(x<=middle) deleteNode(i*2,s,middle,x); else deleteNode(i*2+1,middle+1,t,x); sum[i]=sum[i*2]+sum[i*2+1]; } void TreeCount::insert(int i, int s, int t, int x) { if(s==t) { sum[i]++; return; } int middle=(s+t)/2; if(x<=middle) { insert(2*i,s,middle,x); } else { insert(2*i+1,middle+1,t,x); } sum[i]=sum[2*i]+sum[2*i+1]; } int TreeCount::check(int i, int s, int t, int x) { if(t==s) { return sum[i]; } int middle=(s+t)/2; if(x<=middle) { return check(2*i,s,middle,x); } else { return check(2*i,s,middle,middle)+check(2*i+1,middle+1,t,x); } } void TreeCount::dfs(int i) { dfsC1++; this->dfs1[dfsC1]=i; for(int j=1;j<=e[i][0];j++) dfs(e[i][j]); } void TreeCount::dfsN(int i) { dfsC2++; this->dfs2[dfsC2]=i; for(int j=e[i][0];j>=1;j--) dfsN(e[i][j]); } #endif
测试用例
15
0
0
2 12 15
0
0
2 5 8
5 1 3 4 9 10
0
2 6 11
1 14
0
0
0
2 2 13
0
运行结果0 0 0 0 0 1 6 0 3 1 0 0 0 2 0
4,多源点最短路径问题
考虑对远点进行合并,然后使用dijkstra算法,记住压缩图和原图之间点序号的映射
#ifndef MULTISOURCEPOINT_H #define MULTISOURCEPOINT_H #define MAXN 500 #define MAXINT 10000 #include int a[MAXN][MAXN]; int b[MAXN]; int c[MAXN]; bool isVisited[MAXN]; void mutiSourcePoint() { int pointNum,edgeNum,sourcePoint; for(int i=0;i>pointNum>>edgeNum>>sourcePoint; int temp=0; for(int i=1;i<=sourcePoint;i++) { std::cin>>temp; c[temp]=1; } b[0]=1; for(int i=1;i<=pointNum;i++) { b[i]=b[i-1]-c[i]+1; c[b[i]]=i; } int x,y,z; for(int i=1;i<=edgeNum;i++) { std::cin>>x>>y>>z; if(b[x]==1&&b[y]!=1) a[1][b[y]]=z; else if(b[x]!=1&&b[y]!=1) a[b[x]][b[y]]=z; } int t=1; int min=MAXINT; int minPoint=1; isVisited[1]=true; for(int i=1;i<=pointNum;i++) { min=MAXINT; for(int j=i+1;j<=pointNum;j++) { if(!isVisited[j]) { if(a[1][j]
5, 闭包运算的题目
如何计算闭包,实际上就是floyd算法的思想。
两种方法
1,采用宽度优先或者深度优先遍历来解决。从任意一个顶点出发,进行一次遍历,就可以求出次顶点和其他各个顶点的联通状况。所以只需要把每个顶点做为出发点遍历一次,就能够知道任意两个顶点之间是否有路存在。穷举每个顶点,总的时间复杂度是O(n*n)
2,类似floyd算法,如下算法实现的那样。
题目:运动员之间可以传递信息,但不是每两个运动员之间都可以相互传递信息。求教练至少要通知几个人才可以使每个运动员都知道自己的命令。
算法,先计算闭包,给每个闭包赋予一个序号。
然后按照序号将图压缩,一个传递闭包一个节点。
统计入度为0的节点的个数,即为教练需要通知的次数
#include #include #define MAXN 100 bool snet[MAXN][MAXN]; bool anet[MAXN][MAXN]; int sIndex[MAXN]; bool avgIn[MAXN]; int n,e; void TeamProblem() { memset(snet,0,sizeof(snet)); memset(anet,0,sizeof(anet)); memset(sIndex,0,sizeof(sIndex)); memset(avgIn,0,sizeof(avgIn)); std::cin>>n>>e; int a,b; for(int i=1;i<=e;i++) { std::cin>>a>>b; snet[a][b]=true; } for(int k=1;k<=n;k++) for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(snet[i][j]||(snet[i][k]&&snet[k][j])) snet[i][j]=true; } int index=0; for(int i=1;i<=n;i++) { if(sIndex[i]==0) { index++; sIndex[i]=index; for(int j=1;j<=n;j++) { if(snet[i][j]&&snet[j][i]) sIndex[j]=index; } } } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) if(!anet[sIndex[i]][sIndex[j]]&&sIndex[i]!=sIndex[j]) anet[i][j]=snet[i][j]; } for(int i=1;i<=index;i++) { for(int j=1;j<=index;j++) if(anet[i][j]) avgIn[j]=true; } int total=0; for(int i=1;i<=index;i++) if(!avgIn[i]) total++; std::cout<
floyd算法又称floyd-warshall算法,是一种动态规划算法,用来求一个有向图G中每对顶点间的最短路径问题,其运行时间为O(v^3)
定义n*n的矩阵W。过程范围最短路径的权值矩阵为D(n)
Floyd-Warshall(W)
n<-rows(W)
D(0) <-W
for(k=1~n)
do for i=1~n
do for k=1~n
do d(k)ij =min(d(k-1)ij , d(k-1)ik +d(k-1)kj )
return D(n)
6, 使用floyd算法的例子。
染色
一个N个点的无向完全图,每条边都被染成[1,m]的一种颜色,要求选择一部分颜色,作为一个集合S,且S中的元素个数不超过(m+1)/2,并满足如下性质:
图中任意两个点a和b,一定存在一条长度不大于3的通路,其每条边的颜色都属于S。
简单的分析:
将m种颜色一分为2,然后证明必有一半的颜色满足。
使用然后,设A集合边为1,B集合的边的权重为4。使用floyd算法,求两点之间的最短距离。如果超过了3,则结果为B集合,否则为A集合。
代码如下
#ifndef DYEPROBLEM_H #define DYEPROBLEM_H class DyeProblem { int n;//节点个数 int cn;//颜色数目 int **g;//图 public: DyeProblem(); int min(int x,int y); }; #endif
#include "stdafx.h" #include #include "DyeProblem.h" DyeProblem::DyeProblem() { std::cin>>n>>cn; g=new int*[n+1]; for(int i=1;i<=n;i++) g[i]=new int[n+1]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { std::cin>>g[i][j]; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) { if(g[i][j]<(cn+1)/2) g[i][j]=1; else g[i][j]=4; } for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { for(int k=1;k<=n;k++) { g[i][j]=min(g[i][j],g[i][k]+g[k][j]); } } } bool answerIsA=true; for(int i=1;i<=n;i++) { for(int j=1;j<=n;j++) { if(g[i][j]>3) answerIsA=false; } } if(answerIsA) { for(int i=1;i<=(cn+1)/2;i++) std::cout<
7,二叉树的应用
坐船问题:某学校有n个学生去公园划船。一条船最多可以坐两个人。如果某两个学生同姓或者同名就可以坐同一条船。学校希望每个学生都上船,但是小船的租用费用很高。学校想要租最少的船。请问学校要租多少条船。
用二叉树来解决。左孩子同姓,右孩子同名。在选择将谁放在一条船上的时候有以下几种情况
1,如果父节点已经被其他节点使用了,则增加一条船。
2,如果父节点没有被使用,并且父节点的另一个节点已经被使用,则当前节点和父节点一条船
3,如果非1,2情况。若父节点是父父节点的左孩子,则父节点和右孩子被访问,左孩子连接到父父节点
4,若父节点是右孩子,则右孩子连接到父父节点
代码如下
#ifndef BOATPROBLEM_H #define BOATPROBLEM_H #include class BoatProblem { int n;//the num of student; int *f;//tree father nodes; int *left; int *right; bool **used; bool *isVisited; int ans; public: BoatProblem() { std::cin>>n; f=new int[n+1]; left=new int[n+1]; right=new int[n+1]; isVisited=new bool[n+1]; used=new int*[n+1]; ans=0; for(int i=0;i<=n;i++) { isVisited[i]=false; f[i]=0; left[i]=0; right[i]=0; used[i]=new int[n+1]; for(int j=0;j<=n;j++) used[i][j]=false; } int temp=0; for(int i=1;i<=n;i++) { std::cin>>temp; while(temp!=0) { if(!isVisited[temp]&&!used[i][temp]) { isVisited[temp]=true; used[i][temp]=true; used[temp][i]=true; if(f[temp]==0) { int j=i; while(left[j]!=0) { j=left[j]; } f[temp]=j; left[j]=temp; } else { int j=temp; while(left[j]!=0) { j=left[j]; } f[i]=j; left[j]=i; } } std::cin>>temp; } std::cin>>temp; while(temp!=0) { if(!isVisited[temp]&&!used[i][temp]) { isVisited[temp]=true; used[i][temp]=true; used[temp][i]=true; if(f[temp]==0) { int j=i; while(right[j]!=0) { j=right[j]; } f[temp]=j; right[j]=temp; } else { int j=temp; while(right[j]!=0) { j=right[j]; } f[i]=j; right[j]=i; } } std::cin>>temp; } } for(int i=0;i<=n;i++) { isVisited[i]=false; } for(int i=1;i<=n;i++) { if(!isVisited(i)) dfs(i); } std::cout<