匹配问题 - 好孩子's Blog

hdu2389Rain on your Parade

//做啦好久老是WA,不知到为什么,刚刚开始吧他们都设成是双浮点型,老是过不了;改成整形就过啦;

//后来又是超时,吧cin改成scanf();果断过!!!!!

#include<stdio.h>
#include<queue>
#include<iostream>
#include<string.h>
#include<math.h>
using namespace
std;
#define eps 1e-6
const int MAXN=3005;
const int INF=1<<28;
int g[MAXN][MAXN],Mx[MAXN],My[MAXN],Nx,Ny;int dx[MAXN],dy[MAXN],dis;
bool vst[MAXN];
struct Node1{
    int x,y,s;
}
guests[MAXN];
struct Node2{
    int x,y;
}
um[MAXN];
double distance(Node1 a,Node2 b){
    double x=a.x-b.x; 
    double
y=a.y-b.y;   
    return
sqrt(x*x+y*y);
}
 
bool
searchP(void){
    queue<int> Q;
    dis = INF;
    memset(dx, -1, sizeof(dx));
    memset(dy, -1, sizeof(dy));
    for (int i = 0; i < Nx; i++)
        if (Mx[i] == -1){
            Q.push(i); dx[i] = 0;
        }

        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            if (dx[u] > dis) break;
            for (int v = 0; v < Ny; v++)
                if (g[u][v] && dy[v] == -1) {
                    dy[v] = dx[u]+1;
                    if (My[v] == -1) dis = dy[v];
                    else{
                        dx[My[v]] = dy[v]+1;
                        Q.push(My[v]);
                    }
                }
        }

        return dis != INF;
}

bool DFS(int u){
    for (int v = 0; v < Ny; v++)
        if (!vst[v] && g[u][v] && dy[v] == dx[u]+1) {
            vst[v] = 1;
            if (My[v] != -1 && dy[v] == dis) continue;
            if (My[v] == -1 || DFS(My[v])) {
                My[v] = u; Mx[u] = v;
                return 1;
            }
        }

        return 0;
}

int MaxMatch(void){
    int res = 0;
    memset(Mx, -1, sizeof(Mx));
    memset(My, -1, sizeof(My));
    while (searchP()) {
        memset(vst, 0, sizeof(vst));
        for (int i = 0; i < Nx; i++)
            if (Mx[i] == -1 && DFS(i)) res++;
    }

    return res;
}

int main(){
    //freopen("test.in","r",stdin);
    //freopen("test.out","w",stdout);   

    int n,m,t,i,j;   
    int
T,iCase=0;  
    scanf("%d",&T); 
    while
(T--)   
    {
     
        iCase++;    
        scanf("%d",&t); 
        scanf("%d",&m);   
        for
(i=0;i<m;i++)      
            scanf("%d%d%d",&guests[i].x,&guests[i].y,&guests[i].s);   
        scanf("%d",&n);     
        for
(i=0;i<n;i++)    
            scanf("%d%d",&um[i].x,&um[i].y);
        Nx=m;Ny=n;      
        memset(g,0,sizeof(g));    
        for
(i=0;i<m;i++)    
        {
           
            for
(j=0;j<n;j++) 
            {
             
                if
(distance(guests[i],um[j])/guests[i].s-t<eps)    
                {
                
                    g[i][j]=1;       
                }       
            }       
        }
           
        printf("Scenario #%d:\n%d\n\n",iCase,MaxMatch());  
    }
   
    return
0;
}
      

二分图匹配匈牙利算法(邻接表矩阵实现

#include <iostream>
#include <cstdio>
using namespace std;
int const MAXN = 250;
int graph[MAXN][MAXN], cnt[MAXN];
//邻接表矩阵,点度
bool ck[MAXN];//记录是否被访问
int match[MAXN];//匹配数组,记录右端到左端的匹配
int V, E;//点数、边数
bool search(int G[][MAXN], int k)
{//找增广边
    for(int i = 0; i < cnt[k]; i++)
    {
        int p = G[k][i];
        if(ck[p])
        {
            ck[p] = false;
            int t = match[p];
            if(t == -1 || search(G, t) )
            {
                match[p] = k;
                return true;
            }
            //match[p] = t;
        }
    }
    return false;
}

int hungary(int G[][MAXN], int left)
{//传入矩阵和左端点数,传出匹配的对数
    int answer(0);
    int i, j;
    for(i = 0; i < V; i++)        // V为右端顶点数
    {
        match[i] = -1;
    }
    for(i = 0; i < left; i++)
    {
       for(j = 0; j < V; j++)
       {
            ck[j] = true;
        }
       if(search(G, i))
       {
            ++answer;
       }
    }
    return answer;
}

 

Hopcroft-Carp的算法

/**********************************************
二分图匹配(Hopcroft-Carp的算法)。
初始化:g[][]邻接矩阵
调用:res=MaxMatch();  Nx,Ny要初始化!!!
时间复杂大为 O(V^0.5 E)

适用于数据较大的二分匹配 
***********************************************/ 
const int MAXN=3001;
const int INF=1<<28;
int g[MAXN][MAXN],Mx[MAXN],My[MAXN],Nx,Ny;
int dx[MAXN],dy[MAXN],dis;
bool vst[MAXN];
bool searchP()
{
    queue<int>Q;
    dis=INF;
    memset(dx,-1,sizeof(dx));
    memset(dy,-1,sizeof(dy));
    for(int i=0;i<Nx;i++)
        if(Mx[i]==-1)
        {
            Q.push(i);
            dx[i]=0;
        }  
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop();
        if(dx[u]>dis)  break;
        for(int v=0;v<Ny;v++)
            if(g[u][v]&&dy[v]==-1)
            {
                dy[v]=dx[u]+1;
                if(My[v]==-1)  dis=dy[v];
                else
                {
                    dx[My[v]]=dy[v]+1;
                    Q.push(My[v]);
                }    
            }    
    }  
    return dis!=INF;    
}    
bool DFS(int u)
{
    for(int v=0;v<Ny;v++)
       if(!vst[v]&&g[u][v]&&dy[v]==dx[u]+1)
       {
           vst[v]=1;
           if(My[v]!=-1&&dy[v]==dis) continue;
           if(My[v]==-1||DFS(My[v]))
           {
               My[v]=u;
               Mx[u]=v;
               return 1;
           }    
       }  
    return 0;  
}
int MaxMatch()
{
    int res=0;
    memset(Mx,-1,sizeof(Mx));
    memset(My,-1,sizeof(My));
    while(searchP())
    {
        memset(vst,0,sizeof(vst));
        for(int i=0;i<Nx;i++)
          if(Mx[i]==-1&&DFS(i))  res++;
    }
    return res;   
}    
    

KMP

 

字符串匹配算法:KMP学习心得

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。

这周的数据结构课讲的是串,本以为老师会讲解KMP算法的,谁知到他直接略过了...没办法只能自己研究,这一琢磨就是3天,期间我都有点怀疑自己的智商...不过还好昨天半夜终于想明白了个中缘由,总结一些我认为有助于理解的关键点好了...

书上有的东西我就不说了,那些东西网上一搜一大片,我主要说一下我理解的由前缀函数生成的next数组的含义,先贴出求next数组的方法。

字符串匹配算法:KMP学习心得
 

当一个字符串以0为起始下标时,next[i]可以描述为"不为自身的最大首尾重复子串长度"。

也就是说,从模式串T[0...i-1]的第一个字符开始截取一段长度为m(m < i-1)子串,再截取模式串T[0...i-1]的最后m个字符作为子串,如果这两个子串相等,则该串就是一个首尾重复子串。我们的目的就是要找出这个最大的m值。

例如:

KMP算法

若 i = 4 ,则 i - 1 = 3 , m = next[4] = 2

从T[0...3]截取长度为2的子串,为"ab"

从T[0..3]截取最后2个字符,为"ab"

此时2个子串相等,则说明 next[4] = 2 成立,也可证明 m = 2 为最大的m值。

本来一开始我是没有加"不为自身"这个限制条件的,可是后来我发现一种情况:

KMP算法

若 i = 4 ,则 i - 1 = 3 , m = next[4] = 3

从T[0...3]截取长度为3的子串,为"aaa"

从T[0..3]截取最后3个字符,为"aaa"

此时2个子串相等,则说明 next[4] = 3 成立。

但是我发现如果next[4] = 4:

从T[0...3]截取长度为4的子串,为"aaaa"

从T[0..3]截取最后4个字符,为"aaaa"

此时2个子串也是相等的,那么是不是说明 next[4] 应该等于4呢?

仔细观察后发现,如果 next[4] = 4 ,那么T[0...3]的前4个字符和后4个字符是重合的,并且重复子串和T[0...3]也是相等的。看过教材后发现教材中给出的前缀函数定义有一句为:next[j] = max{k | 0 < k < j 且 'p[0]...p[k-1]' = 'p[j-k+1]...p[j-1]'},应该不包含子串为本身的情况...

这样再做PKU 2406 和 PKU 1961 的时候就很简单了,用 length - next[length] 求出"不为自身的最大首尾重复子串长度",此时需要多求一位next[length]值(而next[length]刚好反映了lenght之前不为。。的长度),若最大重复子串的长度是length的非1整数倍,则证明字符串具有周期重复性质。

PKU 2752 是求 前缀 == 后缀的长度,也就是首尾重复子串长度,利用next数组记录的"不为自身的最大首尾重复子串长度"可以马上得到结果。

 

 

#include<iostream>
using namespace
std;
int a[1000010],b[10010],next[10010];
int lena,lenb;

void inset()
{

    int i;
    scanf("%d%d",&lena,&lenb);
    for(i=0;i<lena;i++)
        scanf("%d",&a[i]);
    for(i=0;i<lenb;i++)
        scanf("%d",&b[i]);
}





void Gurm()
{

    int j=0,k=-1;
    next[0]=-1;
    while(j<lenb)
    {

        if(k==-1 || b[j]==b[k])
        {
            ++
j;
            ++
k;
            next[j]=k;
        }

        else
        {
            k=next[k];
        }
    }
}




int kmp()
{

    int i=0,j=0;
    Gurm();
    while(i<lena && j<lenb)
    {

        if(j==-1 || a[i]==b[j] )
        {

            i++;
            j++;
        }

        else
        {
            j=next[j];
        }
    }

    if(j<lenb)  return -1;
    else return i-j+1;
}






int main()
{

    //freopen("D:\\in.txt","r",stdin);
    int R;scanf("%d",&R);
    while(R--)
    {

        inset();
        printf("%d\n",kmp());
    }

    return 0;
}

 

 

 

KMP:next[]的应用。

 * 一般next[i]与优化next[i]的区别:

 *如果模式串p下标为i的字符(字符第一个的下标为0)的前k个字符与开头前k个字符相等(0=<k<i 注意这里没有等于i

 *     对于一般的next[i],不管p[i]是否等于p[k+1]next[i]都是等于k;
 *   
  对于优化后的next[i],如果p[i]不等于p[k+1],next[i]=k,反之,next[i]=0.

 * 这题的next[]是从0求到n,有n+1个值,因此对于next[i]=k的意义(0除外)就为:字符串下标为i的前k-1个字符与字符串头k个字符相等(上面提到的next[i]是与前k个字符与头k个字符相等next[i]才等于k)。
 * 对于长度为i个字符串,i-next[i](满足i % (i-next[i])==0)代表的意义就是 长度为i的字符串 的 最小循环子串的长度
 *  因此 字符串中存在周期的子串的最大循环次数=i/ (i-next[i]);
 *
 

Hopcroft_Karp算法

 

匈牙利算法中,我们每次寻找一条增广路来增加匹配集合M.可以证明,每次找增广路的复杂度是O(E),一共需要增广O(V)次,因此总时间复杂度为O(VE).为了降低时间复杂度,在Hopcroft Karp算法中,我们在增加匹配集合M时,每次寻找多条增广路.可以证明,这样迭代次数最多为2*V^0.5,所以,时间复杂度就降到了O(V^0.5*E).

hdoj 2389  Rain on your Parade

001 #include <iostream>
002 #include <cstdio>
003 #include <cstring>
004 #include <algorithm>
005 #include <vector>
006 #include <cmath>
007 #include <iterator>
008 using namespace std;
009  
010 struct NodeGuest
011 {
012     double x, y, speed;
013 }ng[3010];
014  
015 struct NodeNmbre
016 {
017     double x, y;
018 }nn[3010];
019  
020  
021 int T, cntGuest, cntUmbre, match;
022 int disx[3010], disy[3010], matx[3010], maty[3010], que[3010];
023 double t;
024 vector<int> mp[3010];
025  
026  
027  
028 double getTime(int a, int b)
029 {
030     double dis = sqrt( (ng[a].x - nn[b].x) * (ng[a].x - nn[b].x) + (ng[a].y - nn[b].y) * (ng[a].y - nn[b].y));
031     return dis/ng[a].speed;
032 }
033  
034  
035 bool bfs()
036 {
037     int i, j, fore = 0, rear = 0;
038     bool flag = false;
039     vector<int>::iterator ite;
040     for(i=0; i<cntGuest; ++i){  //找出中还没有匹配的点
041         if(matx[i] == -1) que[rear++] = i;
042         disx[i] = 0;
043     }
044     for(i=0; i<cntUmbre; ++i) disy[i] = 0;
045     while(fore<rear){//从这些没有匹配的点里,找路径
046         int x = que[fore++];
047         for(ite=mp[x].begin(); ite!=mp[x].end(); ++ite){
048             if(disy[*ite] == 0){
049                 disy[*ite] = disx[x] + 1;
050                 if(maty[*ite] == -1) flag = true; //找到一条路径,则返回值是正的
051                 else {         //找不到路径,则把前向点如队列
052                     disx[maty[*ite]] = disy[*ite] + 1;
053                     que[rear++] = maty[*ite];
054                 }
055             }
056         }
057     }
058     return flag;
059 }
060  
061  
062  
063 bool dfs(int x)
064 {
065     vector<int>::iterator ite;
066     for(ite=mp[x].begin(); ite!=mp[x].end(); ++ite){
067         if(disy[*ite] == disx[x] + 1){
068             disy[*ite] = 0;
069             if(maty[*ite] == -1 || dfs(maty[*ite])){
070                 matx[x] = *ite;
071                 maty[*ite] = x;
072                 return 1;
073             }
074         }
075     }
076     return false;
077 }
078  
079  
080  
081 int Hopcroft_Karp()
082 {
083     int i;
084     match = 0;
085     memset(matx, -1, sizeof(matx));
086     memset(maty, -1, sizeof(maty));
087     while(bfs()){
088         for(i=0; i<cntGuest; ++i){
089             if(matx[i] == -1 && dfs(i)) ++match;
090         }
091     }
092     return match;
093 }
094  
095  
096 int main()
097 {
098 //    freopen("c:/aaa.txt", "r", stdin);
099     int ca = 1;
100     scanf("%d", &T);
101     while(T--){
102         scanf("%lf %d", &t, &cntGuest);
103         int i, j;
104         for(i=0; i<cntGuest; ++i) scanf("%lf %lf %lf", &ng[i].x, &ng[i].y, &ng[i].speed);
105         scanf("%d", &cntUmbre);
106         for(i=0; i<cntUmbre; ++i) scanf("%lf %lf", &nn[i].x, &nn[i].y);
107         for(i=0; i<cntGuest; ++i) mp[i].clear();
108         for(i=0; i<cntGuest; ++i){
109             for(j=0; j<cntUmbre; ++j){
110                 double tt = getTime(i, j);
111                 if(tt <= t) mp[i].push_back(j);
112             }
113         }
114  
115         printf("Scenario #%d:\n%d\n\n", ca++, Hopcroft_Karp());
116     }
117     return 0;
118 }

 

匈牙利算法___最大匹配

#include<stdio.h>
#include<string.h>
 
bool g[201][201];
int n,m,ans;
bool b[201];
int link[201];
 
bool init()
{
        int _x,_y;
        memset(g,0,sizeof(g));
        memset(link,0,sizeof(link));
        ans=0;
        if(scanf("%d%d",&n,&m)==EOF)return false;
        for(int i=1;i<=n;i++)
        {
                scanf("%d",&_x);
                for(int j=0;j<_x;j++)
                {
                        scanf("%d",&_y);
                        g[ i ][_y]=true;
                }
        }
        return true;
}
 
bool find(int a)
{
        for(int i=1;i<=m;i++)
        {
                if(g[a][ i ]==1&&!b[ i ])
                {
                        b[ i ]=true;
                        if(link[ i ]==0||find(link[ i ]))
                        {
                                link[ i ]=a;
                                return true;
                        }
                }
        }
        return false;
}
 
int main()
{
        while(init())
        {
                for(int i=1;i<=n;i++)
                {
                        memset(b,0,sizeof(b));
                        if(find(i))ans++;
                }
                printf("%d\n",ans);
        }
}

每次增广时间为O(E),最多进行O(V)次迭代,时间复杂度为O(VE).