传送门:奖金
题意:
由题意可以很容易发现,这是一道可以用差分约束来解的题目,比一般的差分约束裸题都简化了很多。建好边之后用spfa求最长路径即可,存在负环就无解。
用强连通分量来想的话就是最后缩完点之后也是一个拓扑图,判断有无解就是看有没有哪一个强连通分量存在边权不为0的边,不过这道题目建边的边权全都是1,意味着只要在一个强连通分量里面有两个或以上的节点就一定无解,也就是说最后强连通分量编号如果不等于n的话就无解。
拓扑排序思路:按照要求建边后走一遍拓扑排序得到拓扑序列。遍历该序列,对每一个点的出点都更新最长路。
为什么是最长路而不是最短路呢?
理由:和差分约束求最小值时用最长路来求一样,里面的点可能会有多个下限,为了满足所有的下限,必须从中挑出一个最大的作为答案。
代码:
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long ll;
const int N=1e4+10,M=2e4+10;
int idx,e[M],h[N],ne[M],w[M];
int d[N];
int q[N];
int dist[N];
int n,m;
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
bool topsort()
{
int hh=0,tt=-1;
for(int i=1;i<=n;i++)
{
if(d[i]==0)
{
q[++tt]=i;
}
}
while(hh<=tt)
{
int t=q[hh++];
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
d[j]--;
if(d[j]==0) q[++tt]=j;
}
}
return tt==n-1;
}
int main()
{
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
int a,b;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&a,&b);
add(b,a);
d[a]++;
}
if(!topsort()) puts("Poor Xed");
else
{
for(int i=1;i<=n;i++)
dist[i]=100;
for(int i=0;i<n;i++)
{
int j=q[i];
for(int k=h[j];~k;k=ne[k])
dist[e[k]]=max(dist[e[k]],dist[j]+1);
}
int sum=0;
for(int i=1;i<=n;i++)
sum+=dist[i];
printf("%d",sum);
}
return 0;
}
差分约束代码(写好了):
思路:这道题的不等式关系只有一种,就是a>=b+1,则只要连一条从b到a的边权为1的边即可,
题目有规定最小的奖金是100,并且整个图不一定完全连通,所以要建立一个超级源点0向所有的点连一条长度为100的边。用一个数组记录每个点的入队次数,超过n次说明存在负环则无解。
代码:
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e4+10.,M=4e4+10;
int idx,e[M],h[N],ne[M],w[M];
int d[N];
int q[N];
int n,m;
int dist[N];
bool st[N];
int cnt[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
bool spfa()
{
memset(dist,-0x3f,sizeof dist);
dist[0]=0;
st[0]=true;
int hh=0,tt=1;
while(hh!=tt)
{
int t=q[hh++];
st[t]=false;
if(hh==N) hh=0;
for(int i=h[t];~i;i=ne[i])
{
int j=e[i];
if(dist[j]<dist[t]+w[i])
{
dist[j]=dist[t]+w[i];
cnt[j]=cnt[t]+1;
if(cnt[j]>=n+1) return false;
if(!st[j])
{
q[tt++]=j;
if(tt==N) tt=0;
st[j]=true;
}
}
}
}
return true;
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
add(b,a,1);
}
for(int i=1;i<=n;i++)
add(0,i,100);
if(!spfa()) puts("Poor Xed");
else
{
int sum=0;
for(int i=1;i<=n;i++)
sum+=dist[i];
cout<<sum<<endl;
}
return 0;
}
强连通分量写法:
思路:tarjan算法主要用来判断有无解以及得到一个拓扑序,因为这道题目的特殊性,每一个强连通分量都只能是由一个点构成,最后缩完点后的拓扑图也是有n个点才有合法解,不够n个就判断吴无解。
因为tarjan算法得到的拓扑图上的点本身就是按照点的个数逆序来就是拓扑序,所以不用再做一遍拓扑排序,除了要用一个id数组记录每一个节点的强连通分量的编号,还要同时增加一个id2数组记录每一个编号对应的原图的点,这个id2是因为这道题目的特殊性才能这么用,一般题目的强连通分量会有多个编号就不能这么搞了???好像也可以,用一个二维数组来存好像也行,但是图个啥呢?
#include <iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=1e4+10.,M=4e4+10;
int idx,e[M],h[N],ne[M],w[M];
int d[N];
int n,m;
int dist[N];
int dfn[N];
int low[N];
int stk[N],top;
int id[N];
int id2[N];
int times,scc_cnt;
bool in_stk[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx]=c,h[a]=idx++;
}
void tarjan(int u)
{
dfn[u]=low[u]=++times;
stk[++top]=u,in_stk[u]=true;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
if(!dfn[j])
{
tarjan(j);
low[u]=min(low[u],low[j]);
}else if(in_stk[j])
low[u]=min(low[u],dfn[j]);
}
if(dfn[u]==low[u])
{
int y;
++scc_cnt;
do{
y=stk[top--];
in_stk[y]=false;
id[y]=scc_cnt;
id2[scc_cnt]=y;
}while(y!=u);
}
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
while(m--)
{
int a,b;
scanf("%d%d",&a,&b);
add(b,a,1);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
tarjan(i);
if(scc_cnt!=n)
{
puts("Poor Xed");
}else
{
for(int t=scc_cnt;t>=1;t--)
{
for(int i=h[id2[t]];~i;i=ne[i])
{
int j=e[i];
dist[j]=max(dist[j],dist[id2[t]]+1);
// cout<<j<<' '<<dist[j]<<endl;
}
}
int sum=n*100;
for(int i=1;i<=n;i++)
{
sum+=dist[i];
}
cout<<sum<<endl;
}
return 0;
}
tnnd,用的数组一个比一个多。