zoukankan      html  css  js  c++  java
  • 【CF611H】New Year and Forgotten Tree(网络流)

    点此看题面

    • 有一棵(n)个点的树,编号为(1sim n)
    • 给出每条边两个点的编号十进制下的位数。
    • 求构造一棵合法的树,或判断无解。
    • (nle2 imes10^5)

    核心点

    考虑对于每一种位数相同的点,我们都选取一个点作为该位数的核心点

    可以证明,任何一种合法方案必然能够转化成没有两个非核心点相连的情况。

    这是我们做这道题的基础。

    既然没有两个非核心点相连,那么就只有核心点与核心点、核心点与非核心点两种情况了。

    核心点与核心点

    显然核心点与核心点之间的连边构成的必然是一棵树。

    因此我们直接暴搜树的形态。

    正常的方法应该是利用(prufer)序列直接(O(m^{m-2}))枚举。

    不正常的方式(我的做法)也可以是强制(1)号点为根,(O((m-1)^{m-1}))枚举其余每个点的父节点,然后暴力判一下这是不是一棵树即可。

    这个暴搜本来就有很多写法,毕竟(m=6)随便瞎跑都能跑过去,主要看个人喜好。

    核心点与非核心点

    这类边才是本道题的关键。

    对于同种位数的点之间的连边,就每次取一个非关键点连向关键点。

    对于不同位数,显然每一种位数的一个非关键点只会连一次边,因此我们很容易算出该种位数的关键点需要恰好连多少条边。

    每一条边有两种选择,每一种位数又有一个容量限制,容易想到网络流,且有解的条件就是能流满。

    具体建图,从超级源向代表的点连边,从代表的点向代表这条边连的两种位数的两点连边,从代表位数的点向超级汇连边。

    注意到连向两种位数相同的边可以合在一起,所以种数是(C_m^2)的。

    发现网络流总点数很少,随便跑。

    代码:(O()能过())

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define N 200000
    #define mp make_pair
    #define pb push_back
    using namespace std;
    int n,m,tot,tn[10],ps[10],p[10][10],t[10][10],X[N+5],Y[N+5];
    vector<pair<int,int> > res;
    namespace NetFlow//网络流
    {
    	#define PS 30
    	#define ES 100
    	#define add(x,y,f) (e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].F=f)
    	int ee,lnk[PS+5],cur[PS+5],d[PS+5],q[PS+5];struct edge {int F,to,nxt;}e[2*ES+5];
    	I void Add(CI x,CI y,CI f) {add(x,y,f),add(y,x,0);}
    	I bool BFS()
    	{
    		RI i,k,H=1,T=1;for(i=1;i<=tot;++i) d[i]=0;d[q[1]=1]=1;W(H<=T&&!d[2])
    			for(i=lnk[k=q[H++]];i;i=e[i].nxt) e[i].F&&!d[e[i].to]&&(d[q[++T]=e[i].to]=d[k]+1);
    		return d[2]&&memcpy(cur,lnk,sizeof(lnk)),d[2];
    	}
    	I int DFS(CI x=1,RI f=1e9)
    	{
    		if(!f||x==2) return f;RI i,t,res=0;for(i=cur[x];i;i=e[i].nxt)
    		{
    			if(d[e[i].to]^(d[x]+1)||!(t=DFS(e[i].to,min(f,e[i].F)))) continue;
    			if(e[i].F-=t,e[((i-1)^1)+1].F+=t,res+=t,!(f-=t)) break;
    		}return cur[x]=i,res;
    	}
    	I int MaxFlow() {RI f=0;W(BFS()) f+=DFS();return f;}//最大流
    	int M,Mx[10];I void Build()//建图
    	{
    		RI i,j;for(ee=0,i=1;i<=tot;++i) lnk[i]=0;for(i=1;i<=m;++i) Mx[i]=0;//清空
    		for(i=1;i<=m;++i) for(j=i+1;j<=m;++j) t[i][j]&&(Mx[i]+=t[i][j],Mx[j]+=t[i][j],
    			Add(1,p[i][j]+m+2,t[i][j]),Add(p[i][j]+m+2,i+2,t[i][j]),Add(p[i][j]+m+2,j+2,t[i][j]),0);//连边
    		for(M=0,i=1;i<=m;++i) M+=(Mx[i]-=((i^m)?tn[i+1]:n+1)-ps[i]-1),Add(i+2,2,Mx[i]);//连边,M统计总流量
    	}
    	I void Work()//把流法转化成答案
    	{
    		RI i,j,k,w;for(i=1;i<=m;++i) for(j=i+1;j<=m;++j)
    			if(t[i][j]) for(k=lnk[p[i][j]+m+2];k;k=e[k].nxt)
    			{
    				if(e[k].to==i+2) for(w=1;w<=t[i][j]-e[k].F;++w) res.pb(mp(tn[i],++ps[j]));//从i的核心点连出边
    				if(e[k].to==j+2) for(w=1;w<=t[i][j]-e[k].F;++w) res.pb(mp(++ps[i],tn[j]));//从j的核心点连出边
    			}
    	}
    	I bool Check()//检验
    	{
    		Build();for(RI i=1;i<=m;++i) if(Mx[i]<0) return 0;
    		return MaxFlow()==M?(Work(),1):0;//是否能流满
    	}
    }
    namespace DFS//暴搜树的形态
    {
    	int fa[10],vis[10];I bool IsTree()//检验是否为树
    	{
    		memset(vis,0,sizeof(vis));for(RI i=2,x;i<=m;++i)
    			{x=i;W(x^1&&vis[x]^i) vis[x]=i,x=fa[x];if(x^1) return 0;}return 1;//如果能跳父亲跳到根
    	}
    	I bool dfs(CI x)//搜索
    	{
    		if(x>m) return IsTree()&&NetFlow::Check();
    		for(RI i=1;i<=m;++i) if(x^i&&t[x][i])
    		{
    			fa[x]=i,--t[x][i],--t[i][x],res.pb(mp(tn[x],tn[i]));
    			if(dfs(x+1)) return 1;++t[x][i],++t[i][x],res.pop_back();
    		}return 0;
    	}
    }
    int main()
    {
    	RI i,j,k=0;char s1[10],s2[10];for(scanf("%d",&n),i=1;i^n;++i)//读入
    		cin>>s1>>s2,++t[X[i]=strlen(s1)][Y[i]=strlen(s2)],X[i]^Y[i]&&++t[Y[i]][X[i]];
    	for(k=m=1;k<=n;++m) k*=10;for(--m,k=0,i=1;i<=m;++i) for(j=i+1;j<=m;++j) p[i][j]=++k;//给每种边标号
    	for(i=1;i<=m;++i) for(ps[i]=tn[i]=(i^1)?tn[i-1]*10:1,j=1;j<=t[i][i];++j) res.pb(mp(tn[i],++ps[i]));//对于同种位数,向关键点连边
    	if(tot=p[m-1][m]+m+2,!DFS::dfs(2)) return puts("-1"),0;//无解输出-1
    	for(i=0;i<n-1;++i) printf("%d %d
    ",res[i].first,res[i].second);return 0;//输出合法方案
    }
    
  • 相关阅读:
    软件测试入门知识
    QTP小应用一则
    频分时分波分码分
    解析UML9种图的作用
    OSI七层模型
    暑期实习心得
    0724工作小结 SQL查库是重点
    0723脚本存储过程的学习
    0722工作日志
    工作之余回味了曾经的写过的小说
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/CF611H.html
Copyright © 2011-2022 走看看