备战noip week7

检查员的难题(Inspector`s Dilemma, Dhaka2007, UVa12118)

description:

某国家有V个城市,每两个城市之间都有一条双向道路直接相连,长度为T。找一条最短道路(起点和终点任意),使得该道路经过E条指定的边。

data range:

(Vle 10^3) (Ele frac{V*(V-1)}{2})

solution:

这道题还是比较巧妙的
如果E条指定的边形成的图是联通的
如果这个图恰好存在欧拉道路(回路),那么直接走一遍显然是最短的
否则如果有S个奇点,那么我们只用添加((S-2)/2)条边就可以一笔画地走完
(首先排除可作为起点和终点的两个端点,然后剩下的奇点两两配对即可)
那如果图不连通呢?假设有N个联通块
那么只用多添加(N-1)条边将联通块连起来就可以
最后不要忘了(*T)

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1005;
int V,E,T,vis[N],kase;
vector<int>e[N];
int dfs(int u)
{
	vis[u]=1;int anss=0;
	for(int i=0;i<e[u].size();++i)
	{
		int v=e[u][i];
		if(vis[v])continue;
		anss+=dfs(v);
	}
	return (e[u].size()&1?1:0)+anss;
}
int main()
{
	while(scanf("%d%d%d",&V,&E,&T)==3)
	{
		if(!V&&!E&&!T)break;
		for(int i=1;i<=V;++i)e[i].clear();
		fill(vis+1,vis+V+1,0);
		for(int i=1;i<=E;++i)
		{
			int u,v;scanf("%d%d",&u,&v);
			e[u].push_back(v),e[v].push_back(u);
		}
		int ans=E,cnt=0;
		for(int i=1;i<=V;++i)
		{
			if(vis[i]||e[i].empty())continue;
			++cnt;ans+=max(0,(dfs(i)-2)/2);
		}
		printf("Case %d: %d
",++kase,T*(ans+max(0,cnt-1)));
	}
	return 0;
}

POJ2186 受欢迎的牛(USACO 2003 Fall)

description:

给出一个N个节点M条边的有向图,询问有多少个点满足图上任意一个点都可以走到这个点

data range:

(Nle 10^4) (Mle 5*10^4)

solution:

极为经典的题目了
考虑缩点后形成的DAG
显然每个强连通分量内部的点都是可以互达的
那么题目要求的点其实就是缩点后出度为0的点
因为所有的点都可以到达它而不存在它可以到达的点
但是要注意如果有超过一个的点满足这个条件,那么答案是0(因为它们无法相互到达)

code:

#include<cstdio>
#include<stack>
#include<iostream>
using namespace std;
const int N=1e4+5,M=5e4+5;
int n,m,sign,tot,cnt,sz[N],id[N],deg[N];
int fi[N],ne[M],to[M],dfn[N],low[N];
bool insta[N];
stack<int>sta;
inline void add(int x,int y)
{
	ne[++tot]=fi[x],fi[x]=tot,to[tot]=y;
}
void dfs(int u)
{
	low[u]=dfn[u]=++sign;
	insta[u]=1;sta.push(u);
	for(int i=fi[u];i;i=ne[i])
	{
		int v=to[i];
		if(!dfn[v])dfs(v),low[u]=min(low[u],low[v]);
		else if(insta[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		++cnt;int v=0;
		do
		{
			v=sta.top();sta.pop();
			id[v]=cnt;++sz[cnt];
			insta[v]=0;
		}while(v!=u);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
	{
		int u,v;scanf("%d%d",&u,&v);
		add(u,v);
	}
	for(int i=1;i<=n;++i)
		if(!dfn[i])dfs(i);
	for(int i=1;i<=n;++i)
		for(int j=fi[i];j;j=ne[j])
			if(id[i]!=id[to[j]])++deg[id[i]];
	int ans=0,ct=0;
	for(int i=1;i<=cnt;++i)
		if(!deg[i])ans=sz[i],++ct;
	ans=(ct==1?ans:0);
	cout<<ans;
	return 0;
}

等价性证明 (Proving Equivalences,NWERC2008,LA4287)

description:

给出一个N条边M个点的有向图,询问至少还要添加多少条边才能使得整个图强连通

data range:

(Nle 2*10^4) (Mle 5*10^4)

solution:

先缩点,形成一个DAG
显然同一个强连通分量里的点不用再考虑
不妨设入度为0的点有d个,出度为0的点有c个,那么此时答案就是(max(c,d))
可以形象地理解为从首连到尾
图中橙色的边就是新连的边
注意特殊情况:原图本就强连通,此时需要0条边而不是1条边

code:

//注意只有一个点时的特殊情况
//数组清空注意 
#include<bits/stdc++.h>
using namespace std;
const int N=2e4+5,M=5e4+5;
int t,n,m,tot,sign,top,cnt;
int fi[N],ne[M],to[M];
int dfn[N],low[N],id[N],sta[N],r[N],c[N];
bool insta[N];
inline int read()
{
	int s=0,w=1; char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isdigit(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
inline void add(int x,int y){ne[++tot]=fi[x],fi[x]=tot,to[tot]=y;}
void dfs(int x)
{
	low[x]=dfn[x]=++sign;
	sta[++top]=x;insta[x]=true;
	for(int i=fi[x];i;i=ne[i])
	{
		int v=to[i];
		if(!dfn[v])dfs(v),low[x]=min(low[x],low[v]);
		else if(insta[v])low[x]=min(low[x],dfn[v]);
	}
	if(low[x]==dfn[x])
	{
		++cnt;
		while(true)
		{
			int v=sta[top--];
			id[v]=cnt;insta[v]=false;
			if(x==v)break;
		}
	}
}
int main()
{
//	freopen("lx.out","w",stdout);
	t=read();
	while(t--)
	{
		n=read(),m=read();
		for(int i=1;i<=m;++i)add(read(),read());
		for(int i=1;i<=n;++i)if(!dfn[i])dfs(i);
		for(int i=1;i<=n;++i)
			for(int j=fi[i],v=to[j];j;j=ne[j],v=to[j])
				if(id[i]!=id[v])++c[id[i]],++r[id[v]];
		int ct1=0,ct2=0;
		for(int i=1;i<=cnt;++i)
		{
			if(!c[i])++ct1;
			if(!r[i])++ct2;
		}
		int ans=cnt==1?0:max(ct1,ct2);
		printf("%d
",ans);
		fill(r+1,r+cnt+1,0);fill(c+1,c+cnt+1,0);
		cnt=sign=tot=top=0;fill(fi+1,fi+n+1,0);
		fill(dfn+1,dfn+n+1,0);
	}
	return 0;
}

LOJ10092 最大半连通子图 ZJOI2007

description:

给出一个N条边M个点的有向图,求一个最大的点集满足其中任意两个点u,v要么u可以到达v,要么v可以到达u。同时还要求出不同的最大点集的个数

data range:

(Nle 10^5) (Mle 10^6)

solution:

还是老套路,先缩点
那么一个强连通分量里的点肯定都是满足条件的
那么问题就转化为在DAG求最长链(不过这里的权值是在点上)
直接dp即可
(f_i)表示从i出发的最长链的长度,(g_i)表示从i出发的最长链的方案数
那么就有(f_i=max(f_j+d_i))
(g_i=sum g_j(要求满足f_j+d_i==f_i))
其中i可以直接到达j
然后这么写交上去就会得到40分的好成绩
原因是缩点后可能会含有重边,这对于求最长链并没有影响,但是对于求方案数来说可能会有计算重复
怎么去掉重边的贡献呢?
我的方法是用vector存缩点后的图,然后对于每个点,对它可以直接到达的排序
这样相同的一定是挨在一起的,dp时去掉就可以了

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e6+5;
int n,m,mod,tot,cnt,sign,top;
int dfn[N],low[N],sz[N],id[N],sta[N],f[N],g[N];
int fi[N],ne[M],to[M];
bool insta[N];
vector<int>e[N];
inline bool isnum(char &ch){return '0'<=ch&&ch<='9';}
inline int read()
{
	int s=0,w=1;char ch=getchar();
	for(;!isnum(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isnum(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
inline void add(int x,int y){ne[++tot]=fi[x],fi[x]=tot,to[tot]=y;}
void dfs(int u)
{
	low[u]=dfn[u]=++sign;
	sta[++top]=u,insta[u]=1;
	for(int i=fi[u];i;i=ne[i])
	{
		int v=to[i];
		if(!dfn[v])dfs(v),low[u]=min(low[u],low[v]);
		else if(insta[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		++cnt;
		while(1)
		{
			int v=sta[top--];
			id[v]=cnt,++sz[cnt],insta[v]=0;
			if(v==u)break;
		}
	}
}
inline void rebuild()
{
	for(int i=1;i<=n;++i)
	{
		for(int j=fi[i];j;j=ne[j])
			if(id[i]!=id[to[j]])e[id[i]].push_back(id[to[j]]);
		sort(e[id[i]].begin(),e[id[i]].end());
	}
}
int dp(int u)
{
	int &d=f[u],&k=g[u];
	if(d)return d;d=sz[u],k=1;
	for(int i=0;i<e[u].size();++i)
	{
		int v=e[u][i],ansv=dp(v)+sz[u];
		if(i&&v==e[u][i-1])continue;
		if(ansv>d)d=ansv,k=g[v];
		else if(ansv==d)(k+=g[v])%=mod;
	}
	return d;
}
int main()
{
	n=read(),m=read(),mod=read();
	for(int i=1;i<=m;++i)
	{
		int u=read(),v=read();
		add(u,v); 
	}
	for(int i=1;i<=n;++i)
		if(!dfn[i])dfs(i);
	rebuild();
	for(int i=1;i<=cnt;++i)dp(i);
	int ans1=*max_element(f+1,f+cnt+1),ans2=0;
	for(int i=1;i<=cnt;++i)
		if(f[i]==ans1)(ans2+=g[i])%=mod;
	printf("%d
%d",ans1,ans2);
	return 0;
}

最大团 (The Largest Clique, UVa11324)
这道题是上一道题的严格弱化版(只要求求出最长链),这里就不再赘述了
宇航员分组 (Astronauts,LA3713)

description:

有 (A,B,C)3 个任务要分配给 n 个宇航员, 每个宇航员恰好要分配一个任务。设所有n个宇航员的平均年龄为 x, 只有年龄≥x才能分配任务 A; 年龄<x的才能分配 B, 而C没有限制。有 m对宇航员相互讨厌, 因此不能分配到同一任务。找出一个满足上述所有要求的分配方案。

data range:

(Nle 10^5)

solution:

可以发现每个宇航员可以领的任务只有两个
那么这个问题就是一个经典的(2-SAT)问题了
具体来说可以将C设为false,另一个选项设为true
对于两个相互厌恶的宇航员u,v
如果u选择false,则v必须选true
如果v选择flase,则u必须选true
同时如果两人年龄都大于等于平均或都小于时
如果u选true,则v必须选false
如果v选true,则u必须选false
这样连边缩点后构造一个解即可
注意判断无解的情况
这里有一个小技巧:用tarjan缩点后的点的编号是逆拓扑序
可以根据算法弹栈的过程和这张图感性理解下
有了这个性质后解就好构造了:每次选择编号小的点,这样相当于选择拓扑序大的点,对后面的影响更小,一定是可行的

code:

#include<bits/stdc++.h>
#define pb push_back
using namespace std;
const int N=2e5+5;
int n,m,ag[N],sign,top,cnt;
int id[N],dfn[N],low[N],sta[N];
bool insta[N];
vector<int>e[N];
inline bool isnum(char &ch){return '0'<=ch&&ch<='9';}
inline int read()
{
	int s=0,w=1;char ch=getchar();
	for(;!isnum(ch);ch=getchar())if(ch=='-')w=-1;
	for(;isnum(ch);ch=getchar())s=(s<<1)+(s<<3)+(ch^48);
	return s*w;
}
void dfs(int u)
{
	dfn[u]=low[u]=++sign;
	sta[++top]=u,insta[u]=1;
	for(int i=0;i<e[u].size();++i)
	{
		int v=e[u][i];
		if(!dfn[v])dfs(v),low[u]=min(low[u],low[v]);
		else if(insta[v])low[u]=min(low[u],dfn[v]);
	}
	if(low[u]==dfn[u])
	{
		++cnt;
		while(1)
		{
			int v=sta[top--];
			id[v]=cnt,insta[v]=0;
			if(v==u)break;
		}
	}
}
int main()
{
	//multiple data...
	while(1)
	{
		n=read(),m=read();int x=0;
		if(!n&&!m)break;
		fill(dfn+1,dfn+2*n+1,0),fill(insta+1,insta+2*n+1,0);
		for(int i=1;i<=2*n;++i)e[i].clear();
		sign=top=cnt=0;
		for(int i=1;i<=n;++i)ag[i]=read(),x+=ag[i];
//		x=ceil(1.0*(x+0.5)/n);
		for(int i=1;i<=n;++i)ag[i]=(ag[i]*n>=x);
		for(int i=1;i<=m;++i)
		{
			int u=read(),v=read();
			e[u+n].pb(v),e[v+n].pb(u);
			if(ag[u]==ag[v])
				e[u].pb(v+n),e[v].pb(u+n);
		}
		for(int i=1;i<=2*n;++i)if(!dfn[i])dfs(i);
		bool flag=1;
		for(int i=1;i<=n&&flag;++i)
			if(id[i]==id[i+n])puts("No solution."),flag=0;
		if(!flag)continue;
		for(int i=1;i<=n;++i)
			if(id[i]>id[i+n])puts("C");
			else puts(ag[i]?"A":"B");
	}
	return 0;
}
原文地址:https://www.cnblogs.com/zmyzmy/p/13837848.html