WaWa的奇妙冒险(第十三周集训自闭现场)

第十三周周记(水水过,回来缓了缓,确实没太多思考)

  • (一)点双、边双、强连通分量
  • (二)treap树
  • (三)最短路
  • (四)c++封装大整数型

(一)点双、边双、强连通分量

本来这周应该是做个总结跑路的。。。结果上周还没补。

但实际上这东西没啥特别好说的,周末把详细理解补上说一说吧。
**本质:**图上跑搜索树利用回退边压栈染色(这就是总结的全部了)

实际上,个人认为,真正学到的东西,应该是图上跑搜索树这个操作,这个操作才是真正实用性广,且和图论密切相关的东西。

写了一道跑树的题,这里贴一下好了,虽然和分类关系不大

ICPC 2017 沈阳 Tree
题意:找出树里面能连接同色点集合的交集

思路:只要这条边连接的子树部分的结点树和剩余结点数都大于等于颜色数,这条边就能作为交集的贡献之一。(想了一个多小时,很神奇的树上问题,dfs的思路是直接拿tarjan模板改的,或者说,tarjan模板本身就是在搜索树的概念上建立起来的)

#include 
using namespace std;
const int maxn = 2e5+10;

vector<int> e[maxn];
int u,v,t,n,k,ans,sum[maxn];

void dfs(int u,int fa){
	sum[u] = 1;
	for(int i = 0;i < e[u].size();++i){
		int v = e[u][i];
		if(v == fa) continue;
		dfs(v,u);
		sum[u] += sum[v];
		if(sum[v] >= k && n-sum[v] >= k) ans++;
	}
}

void init(int n){
	for(int i = 1;i <= n;++i) e[i].clear(),sum[i] = 0;
	return ;
}

int main()
{
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&k);
		init(n);
		for(int i = 1;i < n;++i){
			scanf("%d%d",&u,&v);
			e[u].push_back(v);
			e[v].push_back(u);
		}
		
		ans = 0;
		dfs(1,0);
		printf("%d\n",ans);
	}
	return 0;
} 

**HDU - 1827 Summer Holiday **
题意:找到联系所有人的最小费用——“我”应当联系尽可能少的人

思路:一个强连通分量内的必然可以互相联系,接下来只要考虑要联系哪些人就好了,发现只要找入度为0的缩点,然后找到该缩点内联系代价最小的人即可

#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 1e3+5;
const int INF = 0x3f3f3f3f;

vector <int> e[maxn];
int w[maxn],dfn[maxn],low[maxn],stk[maxn],top,cnt,col[maxn],color,in[maxn];
bool ins[maxn];

void init(int n){
    color = top = cnt = 0;
    for(int i = 1;i <= n;++i){
        e[i].clear();
        in[i] = col[i] = ins[i] = dfn[i] = low[i] = 0;
    }
    return ;
}

void tarjan(int u,int fa){
    int v;
    dfn[u] = low[u] = ++cnt;
    stk[top++] = u;
    ins[u] = 1;
    int len = e[u].size();

    for(int i = 0;i < len;++i){
        v = e[u][i];
        if(!dfn[v]){
            tarjan(v,u);
            low[u] = min(low[u],low[v]);
        }
        else if(ins[v]) low[u] = min(low[u],dfn[v]);
    }

    if(low[u] == dfn[u]){
        ++color;
        do{
            v = stk[--top];
            ins[v] = 0;
            col[v] = color;
        }while(u != v);
    }

    return ;
}

int main()
{
    int u,v,n,m;
    while(~scanf("%d%d",&n,&m)){
        init(n);
        for(int i = 1;i <= n;++i) scanf("%d",&w[i]);
        for(int i = 0;i < m;++i){
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
        }

        for(int i = 1;i <= n;++i){
            if(!dfn[i]) tarjan(i,-1);
        }

        for(int i = 1;i <= n;++i){
            for(int j = 0;j < e[i].size();++j){
                if(col[i] != col[e[i][j]]) in[col[e[i][j]]]++;
            }
        }

        int ans = 0,sum = 0;
        for(int i = 1;i <= color;++i){
            int mins = INF;
            if(!in[i]){
                ans++;
                for(int j = 1;j <= n;++j){
                    if(col[j] == i) mins = min(mins,w[j]);
                }
                sum += mins;
            }
        }

        printf("%d %d\n",ans,sum);
    }
    return 0;
}

**HDU - 3072 Intelligence System **
题意:找出图中所有的强连通分量,然后建立一棵边权总和最小的树即可(即最小权值树)。

思路:缩点,不同点之间找权值最小边

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 5e4+5;
const int INF = 0x3f3f3f3f;

vector <int> e[maxn];
int ss[maxn << 1],ee[maxn << 1],w[maxn << 1],dfn[maxn],low[maxn],stk[maxn],top,cnt,col[maxn],color,cost[maxn];
bool ins[maxn];

void init(int n){
    color = top = cnt = 0;
    for(int i = 0;i < n;++i){
        e[i].clear();
        col[i] = ins[i] = dfn[i] = low[i] = 0;
    }
    memset(cost,INF,sizeof(cost));
    return ;
}

void tarjan(int u,int fa){
    int v;
    dfn[u] = low[u] = ++cnt;
    stk[top++] = u;
    ins[u] = 1;
    int len = e[u].size();

    for(int i = 0;i < len;++i){
        v = e[u][i];
        if(!dfn[v]){
            tarjan(v,u);
            low[u] = min(low[u],low[v]);
        }
        else if(ins[v]) low[u] = min(low[u],dfn[v]);
    }

    if(low[u] == dfn[u]){
        ++color;
        do{
            v = stk[--top];
            ins[v] = 0;
            col[v] = color;
        }while(u != v);
    }

    return ;
}

int main()
{
    int u,v,n,m;
    while(~scanf("%d%d",&n,&m)){
        init(n);

        for(int i = 0;i < m;++i){
            scanf("%d%d%d",&ss[i],&ee[i],&w[i]);
            e[ss[i]].push_back(ee[i]);
        }

        for(int i = 0;i < n;++i){
            if(!dfn[i]) tarjan(i,-1);
        }

        for(int i = 0;i < m;++i){
            int c1 = col[ss[i]];
            int c2 = col[ee[i]];
            if(c1 != c2) cost[c2] = min(cost[c2],w[i]);
        }

        int ans = 0;
        for(int i = 0;i < n;++i){
            if(cost[i] != INF) ans += cost[i];
        }

        printf("%d\n",ans);
    }
    return 0;
}

HDU - 3836 Equivalent Sets
题意:如果A、B属于同一集合内,那么他们必然属于同一强联通分量内,问加几条边能使整个图变成一个强连通分量。

思路:缩点建树,然后看入度为0和出度为0的点有多少个,取其中大值即可,保证出度入度都不为0,即可实现全局为强连通分量(如果刚开始整个图就是一个强连通分量,那么需要特判一下,因为它会被缩成一个点)

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 5e4+5;
const int INF = 0x3f3f3f3f;

vector <int> e[maxn];
int ss[maxn],ee[maxn],dfn[maxn],low[maxn],stk[maxn],top,cnt,col[maxn],color,in[maxn],out[maxn];
bool ins[maxn];

void init(int n){
    color = top = cnt = 0;
    for(int i = 1;i <= n;++i){
        e[i].clear();
        in[i] = out[i] = col[i] = ins[i] = dfn[i] = low[i] = 0;
    }
    return ;
}

void tarjan(int u,int fa){
    int v;
    dfn[u] = low[u] = ++cnt;
    stk[top++] = u;
    ins[u] = 1;
    int len = e[u].size();

    for(int i = 0;i < len;++i){
        v = e[u][i];
        if(!dfn[v]){
            tarjan(v,u);
            low[u] = min(low[u],low[v]);
        }
        else if(ins[v]) low[u] = min(low[u],dfn[v]);
    }

    if(low[u] == dfn[u]){
        ++color;
        do{
            v = stk[--top];
            ins[v] = 0;
            col[v] = color;
        }while(u != v);
    }

    return ;
}

int main()
{
    int u,v,n,m;
    while(~scanf("%d%d",&n,&m)){
        if(!m){printf("%d\n",n);continue;}
        init(n);

        for(int i = 0;i < m;++i){
            scanf("%d%d",&ss[i],&ee[i]);
            e[ss[i]].push_back(ee[i]);
        }

        for(int i = 1;i <= n;++i){
            if(!dfn[i]) tarjan(i,-1);
        }

        for(int i = 0;i < m;++i){
            int c1 = col[ss[i]];
            int c2 = col[ee[i]];
            if(c1 != c2){
                in[c2]++;
                out[c1]++;
            }
        }

        int s1 = 0,s2 = 0;
        for(int i = 1;i <= color;++i){
            if(!in[i]) s1++;
            if(!out[i]) s2++;
        }

        printf("%d\n",color == 1 ?0 :max(s1,s2));
    }
    return 0;
}

**HDU - 3639 Hawk-and-Chicken **
题意:类似牛的舞会(甚至可以说完全一致)

思路:缩点反向建图,给点赋点权,然后dfs找出哪个点的拥护者最多即可(优化一下找入度为0的点,因为入度不为0的点,必然不可能找到最大值)

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
const int maxn = 5e4+5;
const int INF = 0x3f3f3f3f;

vector <int> e[maxn],ek[maxn];
int dfn[maxn],low[maxn],stk[maxn],col[maxn],in[maxn],sum[maxn],p[maxn];
int ss,top,cnt,color;
bool ins[maxn],vis[maxn];

void init(int n){
    color = top = cnt = 0;
    for(int i = 0;i <= n;++i){
        ek[i].clear();
        e[i].clear();
        p[i] = in[i] = sum[i] = col[i] = ins[i] = dfn[i] = low[i] = 0;
    }
    return ;
}

void tarjan(int u,int fa){
    int v;
    dfn[u] = low[u] = ++cnt;
    stk[top++] = u;
    ins[u] = 1;

    for(int i = 0;i < e[u].size();++i){
        v = e[u][i];
        if(!dfn[v]){
            tarjan(v,u);
            low[u] = min(low[u],low[v]);
        }
        else if(ins[v]) low[u] = min(low[u],dfn[v]);
    }

    if(low[u] == dfn[u]){
        ++color;
        do{
            v = stk[--top];
            ins[v] = 0;
            sum[color]++;
            col[v] = color;
        }while(u != v);
    }

    return ;
}

void dfs(int u){
    vis[u] = 1;
    ss += sum[u];
    for(int i = 0;i < ek[u].size();++i){
        int v = ek[u][i];
        if(!vis[v]) dfs(v);
    }
    return ;
}

inline int slove(int n){
    for(int i = 0;i < n;++i){
        for(int j = 0;j < e[i].size();++j){
            int v = e[i][j];
            if(col[i] != col[v]){
                in[col[i]]++;
                ek[col[v]].push_back(col[i]);
            }
        }
    }

    int maxs = 0;
    for(int i = 1;i <= color;++i){
        if(!in[i]){
            memset(vis,0,sizeof(vis));
            ss = 0;
            dfs(i);
            p[i] = ss;
            if(p[i] > maxs) maxs = p[i];
        }
    }

    return maxs;
}

int main()
{
    int t,u,v,n,m;
    scanf("%d",&t);
    for(int g = 1;g <= t;++g){
        scanf("%d%d",&n,&m);
        init(n);

        for(int i = 0;i < m;++i){
            scanf("%d%d",&u,&v);
            e[u].push_back(v);
        }

        for(int i = 0;i < n;++i){
            if(!dfn[i]) tarjan(i,-1);
        }

        int k = slove(n);

        printf("Case %d: %d\n",g,k-1);
        int f = 0;
        for(int i = 0;i < n;++i){
            if(p[col[i]] == k){
                if(f) printf(" ");
                printf("%d",i);
                f = 1;
            }
        }
        printf("\n");
    }
    return 0;
}

(二)treap树

刚开始学了一点,还没做题,大概理解所谓的旋转操作和建树操作了,做完题之后回来记录一下

(三)最短路

1.floyd
本质上是一个dp,思考了一下也可以通过邻接表,貌似就变成了BF的操作了,差距不大,理解算有一点,但没啥特别好讲的,说说应用

(1)找最小环
(2)判负环
(3)闭包传递
(4)最短路

大致以上四类,其实floyd能魔改的题型很多,还是讲求思维灵活运用最佳

剩下三种写完vj来讲讲

(四)c++封装大整数型

在搞了 拉出来之后会往这放一份模板

你可能感兴趣的:(萌新级)