• 备战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;
    }
    
  • 相关阅读:
    Python 中的map函数,filter函数,reduce函数
    编程中,static的用法详解
    C++ list容器系列功能函数详解
    python中的configparser类
    310实验室OTL问题----将写好的C++文件转换成Python文件,并将数据可视化
    310实验室OTL问题
    常量指针、指针常量、指向常量的指针常量
    Iterator迭代器的相关问题
    struts2中action中的通配符
    struts2访问servlet API
  • 原文地址:https://www.cnblogs.com/zmyzmy/p/13837848.html
Copyright © 2020-2023  润新知