• 备战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;
    }
    
  • 相关阅读:
    springboot文件上传: 单个文件上传 和 多个文件上传
    Eclipse:很不错的插件-devStyle,将你的eclipse变成idea风格
    springboot项目搭建:结构和入门程序
    POJ 3169 Layout 差分约束系统
    POJ 3723 Conscription 最小生成树
    POJ 3255 Roadblocks 次短路
    UVA 11367 Full Tank? 最短路
    UVA 10269 Adventure of Super Mario 最短路
    UVA 10603 Fill 最短路
    POJ 2431 Expedition 优先队列
  • 原文地址:https://www.cnblogs.com/zmyzmy/p/13837848.html
Copyright © 2020-2023  润新知