CSP模拟16 猜道路 简单环
我必胜
T1【floyed图论最短路】给出任意点之间的最短路,构造一个图满足条件,要求边权最小。(n<=500)
一
根本不用求最短路,就是固定的给出的,为了记录floyed中dis[x][y]的最短路是不是由(x,y)边自己更新的,记录一个vis,如果存在dis(x,k)+dis(k,y)<=dis(x,y)就给vis打上标记,代表(x,y)可以没有
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=300+10;
int n;
ll dis[N][N],dp[N][N];
struct Node
{
int fr,to,w;
bool operator<(const Node&U)const
{
return w<U.w;
}
}e[N*N];int tot;
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
ll ans=0;
memset(dis,0x3f,sizeof(dis));
_f(i,1,n)
_f(j,1,n)
{
if(i==j)dis[i][j]=0;
int x=re();
if(i<j)dis[i][j]=dis[j][i]=x,e[++tot]=(Node){i,j,x};
}
// memcpy(dp,dis,sizeof(dis));
sort(e+1,e+1+tot);
_f(k,1,n)
_f(i,1,n)
_f(j,1,n)
{
if(dis[i][j]>=dis[i][k]+dis[k][j])
{
dis[i][j]=dis[i][k]+dis[k][j];
if(i!=k&&k!=j)dp[i][j]=1;//是别人帮忙的
}
}
_f(i,1,tot)
{
int f=e[i].fr,t=e[i].to,V=e[i].w;
if(dis[f][t]<V)
{
chu("-1");return 0;
}
if(dis[f][t]==V&&!dp[f][t])ans+=V;//也有可能是别人帮忙
}
chu("%lld",ans);
return 0;
}
/*
(1)并查集:
如果add_edge联通了2个块,只会影响块之间的mindis
3
0 1 3
1 0 2
3 2 0
*/
二
从小到大加入边,那么如果此时(x,y)的最短路>val,(x,y,val)必须加入,=不用,<不合法。
怎么在加入边之后维护最短路呢?floyedn^3会炸,去掉一层循环,会wrong,可以直接先跑DIJ把(x,y)和其他点的最短距离更新,然后再枚举通过(x,y)的点对找到mindis。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=300+10;
ll dis[N][N],ds[N],fa[N];//fa[N];//g是要求最短路,e是建立出来的边的动态最短路
int n,head[N],tot;bool vis[N][N];
struct Node
{
int fr,to,v;
inline bool operator<(const Node&U)const
{
return v<U.v;
}
}g[N*N];int cnt;
struct DUG
{
int to,nxt,w;
}e[N*N*2];
priority_queue<pair<ll,int> >q;
inline int getfa(int x)
{
return (x==fa[x])?x:(fa[x]=getfa(fa[x]));
}
inline void Add(int u,int v,int w)
{
e[++tot].to=v;
e[tot].nxt=head[u];
head[u]=tot;
e[tot].w=w;
}
inline void Dij(int st)
{
// chu("dij:%d\n",st);
_f(i,1,n)ds[i]=0x3f3f3f3f3f3f3f3f;
// chu("dfdij:%d q.size(:%d\n",st,q.size());
q.push(make_pair(0,st));
// if(st==1)chu("didfdsfs:%d\n",st);
while(!q.empty())
{
int tmp=q.top().second;
ll das=-q.top().first;
q.pop();
if(ds[tmp]!=0x3f3f3f3f3f3f3f3f)continue;
ds[tmp]=das;
// if(st==1)chu("try:%d\n",tmp);
for(rint i=head[tmp];i;i=e[i].nxt)
{
int to=e[i].to;
// chu("%d-->%d\n",tmp,to);
if(ds[to]>ds[tmp]+e[i].w)
{
q.push(make_pair(-ds[tmp]-e[i].w,to));
}
}
}
_f(i,1,n)dis[st][i]=dis[i][st]=ds[i];
}
int main()
{
// freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re();
_f(i,1,n)
_f(j,1,n)
{
int x=re();
if(x)
{
if(vis[i][j])continue;
g[++cnt]=(Node){i,j,x};
vis[i][j]=vis[j][i]=1;
}
}
sort(g+1,g+1+cnt);
// _f(i,1,n)fa[i]=i;
//chu("cnt:%d\n",cnt);
memset(dis,0x3f,sizeof(dis));
_f(i,1,n)dis[i][i]=0;
ll tot=0;
_f(i,1,cnt)
{
int V=g[i].v,f=g[i].fr,t=g[i].to;
if(dis[f][t]<V)
{
chu("-1");return 0;
}
else if(dis[f][t]==V)continue;
else
{
tot+=V;
Add(f,t,V);Add(t,f,V);
Dij(f);Dij(t);
_f(x,1,n)
_f(y,1,n)
{
dis[x][y]=min(dis[x][y],dis[x][f]+dis[f][y]);
dis[x][y]=min(dis[x][y],dis[x][t]+dis[t][y]);
}
}
}
chu("%lld",tot);
return 0;
}
/*
(1)并查集:
如果add_edge联通了2个块,只会影响块之间的mindis
3
0 1 3
1 0 2
3 2 0
*/
T2【图论】求不一定联通的图中只包含在一个简单环里的边个数,(n<=1e6)
一
什么样的边满足条件?如果对搜索树差分,计算出val[x]表示x的父亲边被覆盖次数,发现val[x]>1的一定是no,但是val[x]=1的也可能是yes,所以不嫩从覆盖次数角度考虑,但是因为自己dfs建立的搜索树一定只有返租边,所以使得出现覆盖的边一定是向上,所以发现如果一条返租边[x,y]包囊的边只被覆盖一定那么一定满足条件,枚举返租边树上val进行pre求和同dep验证就可以。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define _f(i,a,b) for(register int i=a;i<=b;++i)
#define f_(i,a,b) for(register int i=a;i>=b;--i)
#define chu printf
#define ll long long
#define rint register int
#define ull unsigned long long
inline ll re()
{
ll x=0,h=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-')h=-1;
ch=getchar();
}
while(ch<='9'&&ch>='0')
{
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*h;
}
const int N=1e5+100;
vector<int>son[N],gr[N];
int head[N],tot,n,m,fa[N],vis[N],dep[N],val[N],pre[N];
struct Np
{
int to,nxt;
}e[N<<1];
map<pair<int,int>,int>mp;
vector<int>ans;
inline void Add(int x,int y)
{
e[++tot].to=y;
e[tot].nxt=head[x];
head[x]=tot;
}
inline void dfs1(int x,int f)
{
dep[x]=dep[f]+1;vis[x]=1;
for(rint i=head[x];i;i=e[i].nxt)
{
int to=e[i].to;
if(to==f)continue;
if(!vis[to])
{
//chu("%d-->%d\n",x,to);
dfs1(to,x);fa[to]=x;val[x]+=val[to];
son[x].push_back(to);
}
else
{
if(dep[x]>dep[to])
{
val[x]++;val[to]--;
gr[x].push_back(to);
}
}
}
}
inline void dfs2(int x)
{
pre[x]=(val[x]==1)+pre[fa[x]];
for(rint to:gr[x])
{
if(dep[x]-dep[to]==pre[x]-pre[to])
{
// chu("%d--%d %dcan\n",x,to,mp[minmax(x,to)]);
ans.push_back(mp[minmax(x,to)]);
// chu("%d--%d %dcan\n",x,to,mp[minmax(x,to)]);
int u=x,fu=fa[x];
while(u!=to)
{
// chu("u:%d\n",u);
ans.push_back(mp[minmax(u,fu)]);
u=fu,fu=fa[u];
}
}
}
for(rint to:son[x])dfs2(to);
}
int main()
{
//freopen("1.in","r",stdin);
// freopen("1.out","w",stdout);
n=re(),m=re();
_f(i,1,m)
{
int u=re(),v=re();
Add(u,v);Add(v,u);
mp[minmax(u,v)]=i;
}
_f(i,1,n)
if(!vis[i])
{
dfs1(i,0);
// chu("df\n");
//_f(j,1,n)chu("fa[%d]:%d\n",j,fa[j]);
dfs2(i);
}
sort(ans.begin(),ans.end());
chu("%lu\n",ans.size());
for(rint eg:ans)
{
chu("%d ",eg);
}
return 0;
}
二
找环?那我把他拿出来。进行tarjan缩点,在一个点双里面的点集如果是简单环那么edge一定=dot,拿出来判断,如果满足那么所有块内的边都合法。
点击查看代码
#define sandom signed
#define fre(x, y) freopen(#x ".in", "r", stdin), freopen(#y ".out", "w", stdout);
#include <bits/stdc++.h>
#define re register int
using namespace std; int wrt[20], TP;
const int Z = 1e5 + 10;
inline int read() { int x = 0, f = 0; char c = getchar(); while (!isdigit(c)) f |= c == '-', c = getchar(); while (isdigit(c)) x = (x << 1) + (x << 3) + (c ^ 48), c = getchar(); return f ? -x : x; }
inline void write(int x) { TP = 0; if (x < 0) putchar('-'), x = -x; while (x >= 10) wrt[++TP] = x % 10, x /= 10; wrt[++TP] = x; while (TP) putchar(wrt[TP--] | 48); putchar(' '); }
int n, m;
vector <int> ans;
struct edge { int u, v, ne; } e[Z << 1];
int head[Z], cnt;
inline void add(int x, int y, int id) { e[id] = edge{x, y, head[x]}; head[x] = id; }
int dfn[Z], low[Z], tim, dfs[Z], top;
vector <int> vdcc[Z];
int be[Z], tot[Z], num;
void tarjan(int rt)
{
dfn[rt] = low[rt] = ++tim;
dfs[++top] = rt;
for (re i = head[rt]; i; i = e[i].ne)
{
int son = e[i].v;
if (!dfn[son])
{
tarjan(son);
low[rt] = min(low[rt], low[son]);
if (low[son] >= dfn[rt])
{
int j; num++;
vdcc[num].push_back(rt);
do
{
j = dfs[top--];
vdcc[num].push_back(j);
} while (j != son);
}
}
else low[rt] = min(low[rt], dfn[son]);
}
}
void calc(int t)
{
for (auto rt : vdcc[t]) be[rt] = t;
for (auto rt : vdcc[t])
for (re i = head[rt]; i; i = e[i].ne)
if (be[e[i].v] == t) tot[t]++;
tot[t] >>= 1;
}
void solve(int t)
{
for (auto rt : vdcc[t]) be[rt] = t;
for (auto rt : vdcc[t])
for (re i = head[rt]; i; i = e[i].ne)
if (be[e[i].v] == t && i <= m) ans.push_back(i);
}
sandom main()
{
n = read(), m = read();
for (re i = 1; i <= m; i++)
{
int u = read(), v = read();
add(u, v, i), add(v, u, i + m);
}
for (re i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
for (re i = 1; i <= num; i++) calc(i);//计算每个联通分量的边数
for (re i = 1; i <= num; i++)
if (vdcc[i].size() == tot[i]) solve(i);//点数=边数,简单环
sort(ans.begin(), ans.end());
write(ans.size()), putchar('\n');
for (auto i : ans) write(i);
return 0;
} `