带花树、斯坦纳树、树上莫队、基环树作业

Ural 1099 Work Scheduling

一般图最大匹配模板题。

带花树:
A C   C o d e \mathcal AC \ Code AC Code

#include<bits/stdc++.h>
#define maxn 233
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n;
int info[maxn],Prev[maxn*maxn],to[maxn*maxn],cnt_e;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }
int mat[maxn],nxt[maxn],F[maxn],typ[maxn],q[maxn],ql,qr;
int Find(int u){ return !F[u] ? u : F[u] = Find(F[u]); }
int Lca(int u,int v){
	static int vis[maxn]={},tim=0;
	for(++tim;;swap(u,v)) if(u=Find(u)){//切记不能放到for 中间!!! 
		if(vis[u] == tim) return u;
		else vis[u] = tim , u = nxt[mat[u]];
	}
}
void Blossom(int u,int v,int p){
	for(;Find(u)!=p;){
		nxt[u] = v , v = mat[u];
		if(typ[v] == 1) typ[v] = 2 , q[++qr] = v;
		F[u] = F[v] = p , u = nxt[v]; 
	}
}
int BFS(int u){
	rep(i,1,n) nxt[i] = F[i] = typ[i] = 0;
	q[ql=qr=0]=u,typ[u] = 2;
	for(;ql<=qr;){
		u=q[ql++];
		for(int j=info[u],v;j;j=Prev[j])
			if(typ[v=to[j]] != 1 && Find(u) != Find(v)){
				if(!typ[v]){
					nxt[v] = u , typ[v] = 1;
					if(mat[v]) typ[mat[v]] = 2 , q[++qr] = mat[v];
					else{
						for(int t;v;) t = mat[nxt[v]] , mat[nxt[v]] = v , mat[v] = nxt[v] , v = t;
						return 1;
					}
				}
				else{
					int t= Lca(u,v);
					Blossom(u,v,t) , Blossom(v,u,t);
				}
			}
	}
	return 0;
}

void MaxMatch(){
	int ans = 0;
	rep(i,1,n) if(!mat[i]) ans += BFS(i);
	printf("%d\n",ans<<1);
	rep(i,1,n) if(i < mat[i])
		printf("%d %d\n",i,mat[i]);
}

int main(){
	scanf("%d",&n);
	for(int u,v;scanf("%d%d",&u,&v) != EOF;)
		Node(u,v),Node(v,u);
	MaxMatch();
}

HDU 3446 daizhenyang’s chess

给出一个有些障碍点不能落足的棋盘,给出一个兵象马的杂交国王的位置,两人依次移动,不能重复经过格子,不能操作者输,问先手是否必胜。

如果国王的位置不一定在最大匹配中,那么一定存在一个最大匹配,先手走完之后后手直接按照最大匹配走,那么先手走了之后后手总是有对应的走法,先手必败,否则后手不能总是有对应的走法,但是先手总是有对应后手的走法,先手必胜。

如何判断?
删点后再跑一次一般图最大匹配即可。

简单增广路随机法。

Count on a tree II SPOJ - COT2

多次询问树上的一条链上的颜色总数。

树上莫队模板题。

A C   c o d e \mathcal AC \ code AC code

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#define maxn 100005
#define lim 19
using namespace std;

int n,m,len;
int cnt[maxn],num[maxn],s[maxn],f[lim][maxn],lca[maxn],dep[maxn],ans[maxn];
int l[maxn],r[maxn],lb[maxn],c[maxn],a[maxn],b[maxn],bl[maxn],Siz=300,cnt_b;
int Prev[2*maxn],to[2*maxn],info[maxn],cnt_e,dfn[maxn],tim,seq[maxn];
bool ext[maxn];
int sum;

bool cmp(const int a,const int b){
	if(lb[a]==lb[b]) return dfn[r[a]]<dfn[r[b]];
	return lb[a]<lb[b];
}

void Node(int a,int b){
	cnt_e++;
	Prev[cnt_e]=info[a],info[a]=cnt_e,to[cnt_e]=b;
}

int sta[maxn],tp;
void dfs(int now,int ff){
	dfn[now]=++tim;
	f[0][now]=ff;
	dep[now]=dep[ff]+1;
	int bot=tp;
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff){
			dfs(to[i],now);
			if(tp-bot>=Siz)
			{
				++cnt_b;
				while(tp>bot)
					bl[sta[tp--]]=cnt_b;
			}
		}
	sta[++tp]=now;
}

void init(){
	for(int j=1;j<lim;j++)
		for(int i=1;i<=n;i++)
			f[j][i]=f[j-1][f[j-1][i]];
}

int Lca(int u,int v){
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=lim-1;i>=0;i--)
		if((dep[u]-dep[v]) & (1<<i))
			u=f[i][u];
	if(u==v) return u;
	for(int i=lim-1;i>=0;i--)
		if(f[i][u]!=f[i][v])
			u=f[i][u],v=f[i][v];
	return f[0][u];
}

void modify(int now){
	if(!ext[now])	sum+=(cnt[s[now]]++==0);
	else sum-=(--cnt[s[now]]==0);
	ext[now]^=1;
}

void Move(int a,int b,int c,int d){
	if(dep[a]<dep[c]) swap(a,c);
	while(dep[a]>dep[c]) modify(a),a=f[0][a];
	while(a!=c) modify(a),a=f[0][a],modify(c),c=f[0][c];
	if(dep[b]<dep[d]) swap(b,d);
	while(dep[b]>dep[d]) modify(b),b=f[0][b];
	while(b!=d) modify(b),b=f[0][b],modify(d),d=f[0][d];
}

int main(){
	int u,v;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%d",&s[i]),seq[i]=s[i];
	sort(seq+1,seq+n+1);
	int len=unique(seq+1,seq+1+n)-seq-1;
	for(int i=1;i<=n;i++) s[i]=lower_bound(seq+1,seq+1+len,s[i])-seq;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&u,&v);
		Node(u,v);Node(v,u);
	}
	dfs(1,0);
	init();
	for(int i=1;i<=m;i++){
		scanf("%d%d",&u,&v);
		c[i]=i;l[i]=u;r[i]=v;lb[i]=bl[u];lca[i]=Lca(u,v);
	}
	sort(c+1,c+1+m,cmp);
	l[c[0]]=r[c[0]]=1;
	for(int i=1;i<=m;i++){
		Move(l[c[i-1]],r[c[i-1]],l[c[i]],r[c[i]]);
		sum+=(cnt[s[lca[c[i]]]]++==0);
		ans[c[i]]=sum;
		sum-=(--cnt[s[lca[c[i]]]]==0);
	}
	for(int i=1;i<=m;i++)
		printf("%d\n",ans[i]);
}

CF835F Roads in the Kingdom

求基环树删去环上任意一边后直径最小值,直径定义为所有点对最近距离的最大值。
预处理出一系列:
点走树上的最长路,
在环上有一部分的最长路,
之前的最短路被枚举的边截了后的最短路的最长路。
即可 O ( n ) O(n) O(n)求出答案。
如下图:
假设基环树上的点是 1... n 1...n 1...n
断开 1 − > n 1->n 1>n这条边,预处理 p r e f i pref_i prefi表示 i i i左边的绿线的最大值,
预处理 s u f f i suff_i suffi表示 i i i右边的绿线的最大值。
预处理 p r e g i preg_i pregi表示 i i i左边的红线的最大值。
预处理 s u f g i sufg_i sufgi表示 i i i右边的红线的最大值。
那么断开 i − > i + 1 i->i+1 i>i+1的边后的答案就是:
max ⁡ ( p r e f i + s u f f i + w 1 , n , p r e g i , s u f g i ) \max(pref_i + suff_i + w_{1,n} ,preg_i , sufg_i ) max(prefi+suffi+w1,n,pregi,sufgi)
之类的。
在这里插入图片描述

A C   C o d e \mathcal AC \ Code AC Code

#include<bits/stdc++.h>
#define maxn 200005
#define LL long long
#define inf 0x3f3f3f3f3f3f3f3fll
#define pb push_back
using namespace std;

int n;
int info[maxn],Prev[maxn<<1],to[maxn<<1],cst[maxn<<1],cnt_e;
void Node(int u,int v,int w){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=w; }
vector<int>L,C;
bool vis[maxn];
int sta[maxn],csta[maxn],in[maxn],cnt=0;
LL S[maxn],f[maxn],p1[maxn],p2[maxn],s1[maxn],s2[maxn],Ans1,Ans2;

bool flg = 0;
void dfs0(int u,int ff){
	vis[u] = 1;
	sta[++sta[0]] = u;
	for(int i=info[u],v;i;i=Prev[i])
		if((v=to[i])!=ff){
			if(!vis[v]) csta[sta[0]+1]=cst[i],dfs0(v,u);
			else{
				for(int t=-1;t!=v;){
					L.pb(t = sta[sta[0]]) , C.pb(csta[sta[0]--]);
					if(t == v) C.back() = cst[i];
					in[t] = 1;
				}
				flg = 1;
				return ;
			}
			if(flg) return;
		}
	--sta[0];
}

void dfs1(int u,int ff){
	for(int i=info[u],v;i;i=Prev[i])
		if((v=to[i])!=ff && !in[v]){
			dfs1(v,u);
			Ans1 = max(Ans1 , f[v] + f[u] + cst[i]);
			f[u] = max(f[u] , f[v] + cst[i]);
		}
}

int main(){
	scanf("%d",&n);
	for(int i=1,u,v,w;i<=n;i++){
		scanf("%d%d%d",&u,&v,&w);
		Node(u,v,w),Node(v,u,w);
	}
	dfs0(1,0);
	int m = L.size();
	for(int i=0;i<m;i++)	
		dfs1(L[i],0),S[i]=(i?S[i-1]+C[i-1]:0);
	S[m] = S[m-1] + C[m-1];
	LL tmp = -inf;
	for(int i=0;i<m;i++){
		p1[i] = max(i?p1[i-1]:-inf,S[i]+f[L[i]]);
		p2[i] = max(i?p2[i-1]:-inf,S[i]+f[L[i]]+tmp);
		tmp=max(tmp,f[L[i]]-S[i]);
	}
	tmp = -inf;
	for(int i=m-1;i>=0;i--){
		s1[i] = max(i<m-1?s1[i+1]:-inf,f[L[i]]-S[i]);
		s2[i] = max(i<m-1?s2[i+1]:-inf,f[L[i]]-S[i]+tmp);
		tmp=max(tmp,f[L[i]]+S[i]);
	}
	Ans2 = p2[m-1];
	for(int i=1;i<m;i++) Ans2 = min(Ans2,max(S[m]+p1[i-1]+s1[i],max(p2[i-1],s2[i])));
	printf("%lld\n",max(Ans1,Ans2));	
}

【WC2013】糖果公园

树上带修莫队模板题。
A C   C o d e \mathcal AC \ Code AC Code

#include<bits/stdc++.h>
#define N 100005
using namespace std;
struct edge{int to,next;}e[N*2];
struct Qtype{int l,r,t,id;}a[N],b[N];
int dfn[N],ed[N],id[N*2],be[N*2],v[N];
int fa[N][18],c[N],sum[N],last[N];
int w[N],head[N],dep[N],vis[N];
long long now,ans[N];
int n,m,tot,Tim;
#define gc() getchar()
int read(){
	int x=0;
	char ch=gc();
	for (;ch<'0'||ch>'9';ch=gc());
	for (;ch>='0'&&ch<='9';ch=gc())
		x=x*10-48+ch;
	return x;
}
void add(int x,int y){
	e[++tot]=(edge){y,head[x]};
	head[x]=tot;
}
void dfs(int x){
	dfn[x]=++Tim;
	id[Tim]=x;
	for (int i=1;i<=17;i++)
		fa[x][i]=fa[fa[x][i-1]][i-1];
	for (int i=head[x];i;i=e[i].next)
		if (e[i].to!=fa[x][0]){
			fa[e[i].to][0]=x;
			dep[e[i].to]=dep[x]+1;
			dfs(e[i].to);
		}
	ed[x]=++Tim;
	id[Tim]=x;
}
int lca(int x,int y){
	if (dep[x]<dep[y]) swap(x,y);
	int tmp=dep[x]-dep[y];
	for (int i=0;i<=17;i++,tmp/=2)
		if (tmp&1) x=fa[x][i];
	for (int i=17;i>=0;i--)
		if (fa[x][i]!=fa[y][i])
			x=fa[x][i],y=fa[y][i];
	return x==y?x:fa[x][0];
}
bool cmp(Qtype x,Qtype y){
	if (be[x.l]!=be[y.l])
		return be[x.l]<be[y.l];
	if (be[x.r]!=be[y.r])
		return be[x.r]<be[y.r];
	return x.t<y.t;
}
void upd2(int x){
	if (vis[x])
		now-=1ll*v[c[x]]*w[sum[c[x]]--];
	else now+=1ll*v[c[x]]*w[++sum[c[x]]];
	vis[x]^=1;
}
void upd1(int x,int y){
	if (vis[x]){
		upd2(x);
		c[x]=y;
		upd2(x);
	}
	else c[x]=y;
}
int main(){
	int Q;
	n=read(); m=read(); Q=read();
	for (int i=1;i<=m;i++) v[i]=read();
	for (int i=1;i<=n;i++) w[i]=read();
	for (int i=1;i<n;i++){
		int x=read(),y=read();
		add(x,y); add(y,x);
	}
	for (int i=1;i<=n;i++)
		last[i]=c[i]=read();
	dfs(1);
	int sz=(int)pow(n,2.0/3);
	for (int i=1;i<=Tim;i++)
		be[i]=(i-1)/sz;
	int cntQ=0,cntM=0;
	while (Q--){
		int t,l,r;
		t=read(); l=read(); r=read();
		if (t){
			if (dfn[l]>dfn[r]) swap(l,r);
			a[++cntQ].r=dfn[r];
			a[cntQ].id=cntQ;
			a[cntQ].t=cntM;
			a[cntQ].l=(lca(l,r)==l?dfn[l]:ed[l]);
		}
		else{
			b[++cntM].l=l;
			b[cntM].t=last[l];
			last[l]=b[cntM].r=r;
		}
	}
	sort(a+1,a+cntQ+1,cmp);
	int l=1,r=0,t=1;
	for (int i=1;i<=cntQ;i++){
		for (;t<=a[i].t;t++) upd1(b[t].l,b[t].r);
		for (;t>a[i].t;t--) upd1(b[t].l,b[t].t);
		for (;l>a[i].l;upd2(id[--l]));
		for (;l<a[i].l;upd2(id[l++]));
		for (;r<a[i].r;upd2(id[++r]));
		for (;r>a[i].r;upd2(id[r--]));
		int x=id[l],y=id[r],z=lca(x,y);
		if (x!=z&&y!=z) upd2(z);
		ans[a[i].id]=now;
		if (x!=z&&y!=z) upd2(z);
	}
	for (int i=1;i<=cntQ;i++)
		printf("%lld\n",ans[i]);
}

王室联邦

简单树形DP。
A C   C o d e \mathcal AC \ Code AC Code

#include <cstdio>
#define N 1005
#define M 2005
using namespace std;

int n,B,sz,cnt,tot,info[N],to[M],Prev[M],st[N],rt[N],bel[N];

void add(int u,int v) { to[++tot]=v,Prev[tot]=info[u],info[u]=tot; }
void dfs(int u,int p) {
	int cnr=sz;
	for(int i=info[u];i;i=Prev[i]) if((v=to[i])^p){
		dfs(v,u);
		if(sz-cnr>=B) {
			rt[++cnt]=u;
			while(sz>cnr) bel[st[sz--]]=cnt;
		}
	}
	st[++sz]=u;
}
int main() {
	scanf("%d%d",&n,&B);
	for(int i=1;i<n;++i) {
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v),add(v,u);
	}
	dfs(1,0);
	if(!cnt) rt[++cnt]=1;
	while(sz) bel[st[sz--]]=cnt;
	printf("%d\n",cnt);
	for(int i=1;i<=n;++i) printf("%d%c",bel[i]," \n"[i==n]);
	for(int i=1;i<=cnt;++i) printf("%d%c",rt[i]," \n"[i==cnt]);
}

[ARC079D] Namori Grundy

给出一颗基环内向树,判断是否有一种分配权值的方案使得每个点的权值是指向他的点的 m e x \rm mex mex

把树上从底向上递推(设叶子的权值为 0 0 0)。
然后在环上如果有奇数个点,并且他们权值一样,则无解,
否则有解,具体是在 a u = a v a_u = a_v au=av时让 a u = a v + 1 a_u = a_v +1 au=av+1
具体证明:
环边的影响是,如果有 x − > y x->y x>y,并且 a ( x ) = a ( y ) a(x)=a(y) a(x)=a(y)那么此时 a ( x ) a(x) a(x)会变大为 a ’ ( x ) a’(x) a(x),继而若 z − > x z->x z>x并且 a ( z ) = a ’ ( x ) a(z)=a’(x) a(z)=a(x),那么 a ( z ) a(z) a(z)会变大,依次类推。
显然有影响的边 x − > y x->y x>y当且仅当 a ( x ) ≥ a ( y ) a(x)\geq a(y) a(x)a(y),这样就能够把环拆成若干链,不同的链互补影响,并且每一条链都一定可以被改好。
(注意到如果外面的树在 a ( x ) a(x) a(x)增大后会让 a ( x ) a(x) a(x)继续增大,那么还是可以把环拆成若干条链,不影响。)
唯一不能拆成多条链的情况就是环上的值都一样,那么环长为偶数的时候时候我们显然可以简单构造出一组解比如 x , x + 1 , x , x + 1 x,x+1,x,x+1 x,x+1,x,x+1这样的。
环长为奇数并且值都为 x x x的时候。
每个不是 x x x的点都需要一个 x x x来提供支持。
那么就一定会有一个 x x x落单,这个 x x x会指向另一个 x x x,矛盾。
所以我们就证明了环为奇数而且 a ( x ) a(x) a(x)都相同时无解。

#include<bits/stdc++.h>
#define maxn 400005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,fa[maxn];
int info[maxn],Prev[maxn<<1],to[maxn<<1],cnt_e=1;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }

int ar[maxn],onc[maxn],a[maxn];
int vis[maxn],sta[maxn],tp;
void dfs0(int u,int ff){
	vis[u] = 1;
	sta[++tp] = u;
	for(int i=info[u],v;i;i=Prev[i]) if(i^ff){
		v=to[i];
		if(!vis[v]) dfs0(v,(i^1));
		else if(!onc[v]){
			for(int t=-1;t!=v;){
				t = sta[tp--];
				ar[++ar[0]] = t;
				onc[t] = 1;
			}
			per(i,ar[0],1) sta[++tp] = ar[i];
		}
	}
	if(sta[tp] == u) tp--;
}

void dfs1(int u,int ff){
	static int vis[maxn],tim;
	for(int i=info[u],v;i;i=Prev[i]) if((v=to[i]) ^ ff && !onc[v])
		dfs1(v,u);
	++tim;
	for(int i=info[u],v;i;i=Prev[i]) if((v=to[i]) ^ ff && !onc[v])
		vis[a[v]] = tim;
	a[u] = 0;
	for(;vis[a[u]] == tim; a[u]++);
}

int main(){
	scanf("%d",&n);
	rep(i,1,n){
		scanf("%d",&fa[i]);
		Node(fa[i],i),Node(i,fa[i]);
	}
	dfs0(1,0);
	rep(i,1,ar[0]) dfs1(ar[i],0);
	set<int>st;
	rep(i,1,ar[0]) st.insert(a[ar[i]]);
	if((ar[0] & 1) && st.size() == 1)
		puts("IMPOSSIBLE");
	else 
		puts("POSSIBLE");
}

NOI2012迷失游乐园

基环树上随机游走,不能走重复的点,求期望走的长度。
儿子数量: s z i sz_i szi
父亲数量: s z f i szf_i szfi(环上的点的父亲是环上的邻居。)
则第一步往儿子走的时候,就确定他不能往回走了。
所以确定第一步往下走的期望长度为 f i = 1 s z i ∑ v    i s   a   s o n   o f   i f v + w i , v f_i = \frac 1{sz_i}\sum_{v \ \ is \ a \ son \ of \ i} f_v + w_{i,v} fi=szi1v  is a son of ifv+wi,v
对于不在环上的点 i i i,第一步往上走的期望长度为:
g i = w i , f a i + g f a i s z f f a i + f f a i ( s z f a i − 1 ) − w i , f a i s z i + s z f i − 1 g_i = w_{i,fa_i} + \frac {g_{fa_i}szf_{fa_i} + f_{fa_i}(sz_{fa_i}-1)-w_{i,fa_i}}{sz_i+szf_i-1} gi=wi,fai+szi+szfi1gfaiszffai+ffai(szfai1)wi,fai
对于环上的点 i i i,注意到环上节点很少,直接 O ( 20 ) O(20) O(20)计算答案即可。
g i = 1 2 ∑ j ≠ i P i , j ( f j s z j s z j + 1 + ∑ e d g e   k   b e t w e e n   t h e   r o a d   o f   i   a n d   j w k ) g_i = \frac 12\sum_{j\neq i}P_{i,j} (\frac {f_{j}{sz_j}}{sz_j+1} + \sum_{edge \ k \ between \ the \ road \ of \ i \ and \ j} w_k) gi=21j=iPi,j(szj+1fjszj+edge k between the road of i and jwk)
这是顺时针的式子,逆时针再算一次即可。
a n s = 1 n ∑ i f i s z i + g i s z f i s z i + s z f i ans = \frac 1n\sum_{i} \frac {f_isz_i + g_iszf_i}{sz_i+szf_i} ans=n1iszi+szfifiszi+giszfi

代码风格:只要dfs写的多
A C   C o d e \mathcal AC \ Code AC Code

#include <bits/stdc++.h>
#define maxn 100005
#define rep(i, j, k) for (int i = (j), LIM = (k); i <= LIM; i++)
#define per(i, j, k) for (int i = (j), LIM = (k); i >= LIM; i--)
#define db double
using namespace std;

int n, m;
int info[maxn], Prev[maxn << 1], to[maxn << 1], cnt_e = 1;
db cst[maxn << 1], f[maxn], g[maxn], P[maxn];
void Node(int u, int v, int c) { Prev[++cnt_e] = info[u], info[u] = cnt_e, to[cnt_e] = v, cst[cnt_e] = c; }

int vis[maxn], onc[maxn], ar[maxn], sta[maxn], tp;
db ca[maxn], sz[maxn], szf[maxn];
void dfs0(int u, int ff) {
    vis[u] = 1;
    for (int i = info[u], v; i; i = Prev[i])
        if (i ^ ff) {
            v = to[i];
            if (!vis[v]) {
                sta[++tp] = i ^ 1;
                dfs0(v, i ^ 1);
                if (sta[tp] == i ^ 1)
                    tp--;
            } else if (!onc[v]) {
                ar[++ar[0]] = u, ca[ar[0]] = cst[i], onc[u] = 1;
                for (int t = -1; to[t] != v;) {
                    t = sta[tp--];
                    ar[++ar[0]] = to[t], ca[ar[0]] = cst[t];
                    onc[to[t]] = 1;
                }
                per(i, ar[0], 1) sta[++tp] = ar[i];
            }
        }
}

void dfs1(int u, int ff) {
    for (int i = info[u], v; i; i = Prev[i])
        if ((v = to[i]) ^ ff && !onc[v])
            dfs1(v, u), f[u] += f[v] + cst[i], sz[u]++;
    if (sz[u])
        f[u] /= sz[u];
}

void dfs2(int u, int ff) {
    for (int i = info[u], v; i; i = Prev[i])
        if ((v = to[i]) ^ ff && !onc[v]) {
            g[v] = cst[i] + (sz[u] + szf[u] - 1 > 0
                                 ? (g[u] * szf[u] + f[u] * sz[u] - f[v] - cst[i]) / (sz[u] + szf[u] - 1)
                                 : 0);
            dfs2(v, u);
        }
}

int main() {
    scanf("%d%d", &n, &m);
    rep(i, 1, m) {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        Node(u, v, w), Node(v, u, w);
    }
    dfs0(1, 0);
    rep(i, 1, n) szf[i] = 1 + (onc[i]);
    if (ar[0] == 0)
        ar[++ar[0]] = 1, szf[1] = 0;
    rep(i, 1, ar[0]) dfs1(ar[i], 0);
    rep(i, 1, ar[0]) {
        db P = 0.5;
        for (int j = i % ar[0] + 1; j != i; j = j % ar[0] + 1) {
            g[ar[i]] += P * (ca[j] + (sz[ar[j]] + (j % ar[0] + 1 != i) > 0
                                          ? f[ar[j]] * sz[ar[j]] / (sz[ar[j]] + (j % ar[0] + 1 != i))
                                          : 0));
            P *= 1 / (sz[ar[j]] + 1);
        }
        P = 0.5;
        for (int j = (i + ar[0] - 2) % ar[0] + 1; j != i; j = (j + ar[0] - 2) % ar[0] + 1) {
            g[ar[i]] += P * (ca[j % ar[0] + 1] +
                             (sz[ar[j]] + ((j + ar[0] - 2) % ar[0] + 1 != i) > 0
                                  ? f[ar[j]] * sz[ar[j]] / (sz[ar[j]] + ((j + ar[0] - 2) % ar[0] + 1 != i))
                                  : 0));
            P *= 1 / (sz[ar[j]] + 1);
        }
    }
    rep(i, 1, ar[0]) dfs2(ar[i], 0);
    db ans = 0;
    rep(i, 1, n) ans += (f[i] * sz[i] + g[i] * szf[i]) / (sz[i] + szf[i]);
    ans /= n;
    printf("%.5lf\n", ans);
}

HDU 3551 Hard Problem

有若干条边,从中选出若干条,判断是否有选边方案使得第 i i i个点的度数为 d i d_i di

考虑将每个点的度数 d i d_i di拆分成 d i d_i di个点。

对于一条边 ( u , v ) (u,v) (u,v)
建点 u e , v e ue,ve ue,ve,
u e , v e ue,ve ue,ve间连边。
然后 u e ue ue向所有 u u u拆出来的点连边, v e ve ve同理。
那么这样的一个图,
我们求他的最大匹配。
如果这个图的最大匹配是完美匹配。
那么如果 u e ue ue匹配 v e ve ve,说明这条边没被选中。
否则就是 u e ue ue分走了 u u u的一个点度, v e ve ve同理。
完美匹配则度数都被处理完毕。

A C   C o d e \mathcal AC \ Code AC Code

#include<bits/stdc++.h>
#define maxn 805
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,m;
int sx[maxn],ty[maxn],d[maxn],tot,ue[maxn],ve[maxn];
int info[maxn],Prev[maxn*maxn],to[maxn*maxn],cnt_e;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }
vector<int>G[maxn];
int mat[maxn],typ[maxn],F[maxn],nxt[maxn],q[maxn],ql,qr;
int Find(int u){ return !F[u] ? u : F[u] = Find(F[u]); }

int Lca(int u,int v){  
	static int vis[maxn]={},tim=0;
	for(++tim;;swap(u,v)) if(u=Find(u))
		if(vis[u] == tim) return u;
		else vis[u] = tim , u = nxt[mat[u]];
}

void Blossom(int u,int v,int p){
	for(;Find(u) != p;){
		nxt[u] = v , v = mat[u];
		if(typ[v] == 1) typ[v] = 2 , q[++qr] = v;
		F[u] = F[v] = p , u = nxt[v];
	}
}

int BFS(int u){
	rep(i,1,tot) nxt[i] = F[i] = typ[i] = 0;
	q[ql=qr=0] = u , typ[u] = 2;
	for(;ql<=qr;){
		u=q[ql++];
		for(int j=info[u],v;j;j=Prev[j])
			if(typ[v=to[j]] != 1 && Find(u) != Find(v)){
				if(!typ[v]){
					nxt[v] = u , typ[v] = 1;
					if(mat[v]) q[++qr] = mat[v] , typ[mat[v]] = 2;
					else{
						for(int t;v;v=t) t=mat[mat[v]=nxt[v]],mat[mat[v]]=v;
						return 1;
					}
				}
				else{
					int t = Lca(u,v);
					Blossom(u,v,t) , Blossom(v,u,t);
				}
			}
	}
	return 0;
}

int main(){
	int T,cas=0;
	for(scanf("%d",&T);T--;){
		scanf("%d%d",&n,&m);
		tot = 0;
		rep(i,1,m){
			scanf("%d%d",&sx[i],&ty[i]);
			ue[i] =++tot , ve[i] = ++tot;
			Node(ue[i],ve[i]),Node(ve[i],ue[i]);
			G[sx[i]].push_back(ue[i]);
			G[ty[i]].push_back(ve[i]);
		}
		rep(i,1,n){
			scanf("%d",&d[i]);
			rep(j,1,d[i]){
				++tot;
				rep(k,0,G[i].size()-1)
					Node(tot,G[i][k]),
					Node(G[i][k],tot);
			}
		}
		int ans = 0;
		rep(i,1,tot) if(!mat[i]) ans += BFS(i);
		printf("Case %d: ",++cas);
		if(ans * 2 == tot) puts("YES");
		else puts("NO");
		
		rep(i,1,tot) info[i] = 0 , G[i].clear() , mat[i] = 0;
		cnt_e = 0;
	}
}

HDU 3311 Dig The Wells

给你n个寺庙,m个村庄,p条路,现在你要在这n+m个位置中选出若干个位置打井,每个位置打井的费用会告诉你,同时p条路也有修建费用,现在每个寺庙都住着一个和尚,问你最小的费用让这n个和尚都能喝上水。
斯坦纳树模板题。

#include<bits/stdc++.h>
#define maxn 2005
#define maxm 20005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,m,p;
int info[maxn],Prev[maxm],to[maxm],cst[maxm],cnt_e;
void Node(int u,int v,int c){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c; }

int f[1<<5][maxn];
void Steiner_Tree(){
	#define pii pair<int,int>
	#define mp make_pair
	priority_queue<pii,vector<pii >,greater<pii > >q;
	memset(f,0x3f,sizeof f);
	rep(i,1,n) f[1<<i-1][i] = f[0][i] = 0;
	rep(i,n+1,n+m+1) f[0][i] = 0;
	rep(i,0,(1<<n)-1){
		rep(j,1,n+m+1){
			for(int k=(i-1)&i;k;k=(k-1)&i)
				f[i][j] = min(f[i][j] , f[k][j] + f[i^k][j]);
			q.push(mp(f[i][j],j));
		}
		for(int u,w;!q.empty();){
			u = q.top().second , w = q.top().first; q.pop();
			if(w != f[i][u]) continue;
			for(int k=info[u],v;k;k=Prev[k]) if(f[i][v=to[k]] > w + cst[k])
				f[i][v] = w + cst[k] , q.push(mp(f[i][v],v));
		}
	}
	printf("%d\n",f[(1<<n)-1][n+m+1]);
}

int main(){
	while(~scanf("%d%d%d",&n,&m,&p)){
		int w;
		rep(i,1,n+m){
			scanf("%d",&w);
			Node(n+m+1,i,w);
			Node(i,n+m+1,w);
		}
		rep(i,1,p){
			int u,v,w;scanf("%d%d%d",&u,&v,&w);
			Node(u,v,w),Node(v,u,w);
		}
		Steiner_Tree();
		rep(i,1,n+m+1)
			info[i] = 0;
		cnt_e = 0;
	}
}

[WC2016]挑战NPC

n n n球入 m m m筐,每筐最多 3 3 3个,求最多有多少个筐装了 ≤ 1 \leq 1 1个球,输出方案。

筐拆成3个点,三个点之间互相连边。
这样如果匹配了 ≤ 1 \leq 1 1个球,剩下的两个点就会自己匹配,

#include<bits/stdc++.h>
#define maxn 805
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,m,E;
int info[maxn],Prev[maxn*maxn],to[maxn*maxn],cnt_e;
void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v; }
int mat[maxn],nxt[maxn],F[maxn],q[maxn],typ[maxn],ql,qr;
int Find(int u){ return !F[u] ? u : F[u] = Find(F[u]); }
int Lca(int u,int v){
	static int vis[maxn]={},tim=0;
	for(++tim;;swap(u,v)) if(u=Find(u))
		if(vis[u] == tim) return u;
		else vis[u] = tim , u = nxt[mat[u]];
}
void Blossom(int u,int v,int p){
	for(;Find(u) != p;){
		nxt[u] = v , v = mat[u];
		if(typ[v] == 1) typ[v] = 2 , q[++qr] = v;
		F[u] = F[v] = p , u = nxt[v];
	}
}
int BFS(int u){
	rep(i,1,n+3*m) nxt[i] = typ[i] = F[i] = 0;
	typ[q[ql=qr=0] = u] = 2;
	for(;ql<=qr;){
		u = q[ql++];
		for(int i=info[u],v;i;i=Prev[i]) if(typ[v=to[i]] != 1 && Find(u) != Find(v)){
			if(!typ[v]){
				nxt[v] = u , typ[v] = 1;
				if(!mat[v]){
					for(int t;v;v=t) t=mat[mat[v] = nxt[v]] , mat[mat[v]] = v;
					return 1;
				}
				typ[q[++qr] = mat[v]] = 2;
			}
			else{
				int t = Lca(u,v);
				Blossom(u,v,t),Blossom(v,u,t);
			}
		}
	}
	return 0;
}

int main(){
	int T;
	for(scanf("%d",&T);T--;){
		scanf("%d%d%d",&n,&m,&E);
		rep(i,1,m) 
			Node(i+2*m,i+m),Node(i+m,i+2*m);
		rep(i,1,E){
			int u,v;
			scanf("%d%d",&u,&v);
			Node(3*m+u,v),Node(v,3*m+u);
			Node(3*m+u,v+m),Node(v+m,3*m+u);
			Node(3*m+u,v+2*m),Node(v+2*m,3*m+u);
		}
		int ans = 0;
		per(i,n+3*m,1) if(!mat[i])
			ans += BFS(i);
		
		printf("%d\n",ans - n);
		rep(i,1,3*m+n) info[i]=0,mat[i]=0;
		cnt_e=0;
	}
}

「JLOI2015」管道连接

斯坦纳树+子集卷积。
A C   C o d e \mathcal AC \ Code AC Code

#include<bits/stdc++.h>
#define maxn 1005
#define maxm 6005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,m,K,p[11],cnt[11];
int info[maxn],Prev[maxm],to[maxm],cst[maxm],cnt_e;
void Node(int u,int v,int c){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v,cst[cnt_e]=c; }

int f[1<<10][maxn],g[1<<10];
void Steiner_Tree(){
	#define pii pair<int,int>
	#define mp make_pair
	priority_queue<pii,vector<pii >,greater<pii > >q;
	rep(i,0,(1<<K)-1){
		rep(j,1,n) for(int k=(i-1)&i;k;k=(k-1)&i)
			f[i][j] = min(f[i][j] , f[k][j] + f[i^k][j]);
		rep(j,1,n) q.push(mp(f[i][j],j));
		for(int u,w;!q.empty();){
			u=q.top().second,w=q.top().first;q.pop();
			if(w != f[i][u]) continue;
			for(int j=info[u],v;j;j=Prev[j]) if(f[i][v=to[j]] > f[i][u] + cst[j])
				q.push(mp(f[i][v] = f[i][u] + cst[j] , v));
		}
	}
	memset(g,0x3f,sizeof g);
	rep(i,1,(1<<K)-1){
		int c[11]={};
		bool ER = 0;
		rep(j,0,K-1) if(i>>j&1) c[p[j]]++;
		rep(j,0,K-1) if(c[j] != 0 && c[j] != cnt[j]){
			ER = 1;
			break;
		}
		if(ER) continue;
		rep(j,1,n) g[i] = min(g[i] , f[i][j]);
	}
	rep(i,1,(1<<K)-1) 
		for(int j=(i-1)&i;j;j=(j-1)&i)
			g[i] = min(g[i] , g[j] + g[i-j]);
	printf("%d\n",g[(1<<K)-1]);
}

int main(){
	scanf("%d%d%d",&n,&m,&K);
	rep(i,1,m){
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		Node(u,v,w),Node(v,u,w);
	}
	memset(f,0x3f,sizeof f);
	int x;rep(i,0,K-1) scanf("%d%d",&p[i],&x),f[1<<i][x] = 0,cnt[p[i]]++;
	rep(i,1,n) f[0][i] = 0;
	Steiner_Tree();
}

「THUSCH 2017」巧克力

随机化+二分答案+斯坦纳树

A C   C o d e \mathcal AC \ Code AC Code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
#define MAX 250
const int inf = 1e9;
inline int read() {
    int x = 0;
    bool t = false;
    char ch = getchar();
    while ((ch < '0' || ch > '9') && ch != '-') ch = getchar();
    if (ch == '-')
        t = true, ch = getchar();
    while (ch <= '9' && ch >= '0') x = x * 10 + ch - 48, ch = getchar();
    return t ? -x : x;
}
int tr[MAX * MAX];
int n, m, K, c[MAX][MAX], a[MAX][MAX];
int f[1 << 5][MAX][MAX], val[MAX][MAX];
struct data {
    int x, y;
};
queue<data> Q;
bool vis[MAX][MAX];
int d[4][2] = { 1, 0, -1, 0, 0, 1, 0, -1 };
void SPFA(int S, int V) {
    while (!Q.empty()) {
        int x = Q.front().x, y = Q.front().y;
        Q.pop();
        for (int i = 0; i < 4; ++i) {
            int xx = x + d[i][0], yy = y + d[i][1];
            if (xx < 1 || yy < 1 || xx > n || yy > m || c[xx][yy] == -1)
                continue;
            int p = f[S][x][y] + val[xx][yy];
            if (p < f[S][xx][yy]) {
                f[S][xx][yy] = p;
                if (!vis[xx][yy])
                    vis[xx][yy] = true, Q.push((data){ xx, yy });
            }
        }
        vis[x][y] = false;
    }
}
int DP(int V) {
    for (int S = 0; S < (1 << K); ++S)
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) f[S][i][j] = inf;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j)
            if (c[i][j] != -1)
                f[1 << tr[c[i][j]]][i][j] = val[i][j];
    for (int S = 0; S < 1 << K; ++S) {
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) {
                if (c[i][j] == -1)
                    continue;
                for (int t = (S - 1) & S; t; t = (t - 1) & S)
                    f[S][i][j] = min(f[S][i][j], f[t][i][j] + f[S ^ t][i][j] - val[i][j]);
                if (f[S][i][j] <= inf)
                    Q.push((data){ i, j }), vis[i][j] = true;
            }
        SPFA(S, V);
    }
    int mn = inf;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) mn = min(mn, f[(1 << K) - 1][i][j]);
    return mn;
}
int St[MAX * MAX], top;
void Work() {
    n = read();
    m = read();
    K = read();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) c[i][j] = read();
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) a[i][j] = St[++top] = read();
    sort(&St[1], &St[top + 1]);
    top = unique(&St[1], &St[top + 1]) - St - 1;
    for (int i = 1; i <= n; ++i)
        for (int j = 1; j <= m; ++j) a[i][j] = lower_bound(&St[1], &St[top + 1], a[i][j]) - St;
    int l = 1, r = top, ret = r + 1, blk = n * m + 1;
    while (l <= r) {
        int mid = (l + r) >> 1, mn = inf;
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j <= m; ++j) val[i][j] = (a[i][j] > mid) ? 1001 : 1000;
        for (int Tim = 200; Tim; --Tim) {
            for (int i = 1; i <= n * m; ++i) tr[i] = rand() % K;
            mn = min(mn, DP(mid));
            if (blk > n * m || mn / 1000 != blk)
                continue;
            int b = mn / 1000, c = mn % 1000;
            if (b - c >= (b + 1) / 2)
                break;
        }
        if (mn >= inf) {
            puts("-1 -1");
            return;
        }
        blk = mn / 1000;
        int cnt = mn % 1000;
        if (blk - cnt >= (blk + 1) / 2)
            r = mid - 1, ret = mid;
        else
            l = mid + 1;
    }
    printf("%d %d\n", blk, St[ret]);
}
int main() {
    srand(time(NULL));
    int T = read();
    while (T--) Work();
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值