检查员的难题(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;
}