CF1616G Just Add an Edge
一、题目
二、解法
先讲一下 \(\tt dfs\) 的做法吧,如果看懂了 \(dp\) 做法也会补上去的🕵️♂️(放一个折棒在这里
首先如果没有这条边路径时极简单的,因为每个点都要经过所有不能错过任何点,那么一定是按 \(1\rightarrow n\) 的顺序经过这些点的。考虑增加的一条边虽然带来了更多灵活性,但是上述大体不变,下面讨论不能直接有哈密顿路的情况。
上述图片表示添加 \((y,x)\) 这条边之后经过的顺序,并且所有的路径都是这样的形式。中间的蓝路径和红路径代表两条不交且并为 \([x-1,y+1]\) 的链,如果 \((y,x)\) 确定了那么我们通过寻找这两条链来判定答案的合法性。
暂且不说这两条链很难找,单是枚举 \((y,x)\) 时间复杂度就已经绷不住了。我们尝试寻找所有 \((y,x)\) 问题的共性,从一个细微的角度出发:任取一个没有边的直接相连的 \(a/a+1\),那么 \(a/a+1\) 一定分属两个不同的链。
那么就把 \(a/a+1\) 做为这两条链的代表,考虑它如何为统计答案服务,考虑 \(x-1/x\) 和 \(y/y+1\) 这两对相邻的位置,它们有贡献的必要条件就是分属两条不同的链。所以我们记数组表示相邻位置是否可以分属两条不同的链,即 \(v[x][0/1]\) 表示 \(x\) 和 \(x+1\) 分属两条不同的链,并且 \(x\) 的颜色是 \(0/1\)
那么考虑如何处理出这个数组即可,可以从 \(a\) 开始 \(\tt dfs\),沿途要保证路上的点都可以被访问到才能走下去,对于向右和向左 \(\tt dfs\) 的情况有细微的区间要分别考虑,可以看下面的图示:
\(\tt dfs\) 的起终点都在上图表示好了,并且 \(\tt dfs\) 之后我们需要切换颜色,上图的"可直行"可以预处理出 \(l[i],r[i]\) 表示 \(i\) 向左\(/\)向右 只走 \((o,o+1)\) 的边的最远延伸点,注意向左需要建出反图 \(dfs\)
最后如何统计答案?考虑合法的充要条件是:起点可以直行到 \(x-1\),\(y+1\) 可以直行到终点,并且 \(x-1/x\),\(y/y+1\) 分属于不同的链,且 \((x-1,y),(x,y+1)\) 所属链的颜色应当一样,那么卡一下范围用乘法原理,具体来说就是对于颜色 \(0/1\),数出左边和右边的点数相乘计入贡献,然后把多算的 \(01\) 颜色都有的情况减掉即可。
大体的思路就是上述这些,下面分点讲解一些实现细节:
- 一开始需要添加辅助点 \(0/n+1\),因为 \(dfs\) 的时候可能出现单点访问不到的情况。
- 如果 \(n\) 向左直行到的 \(r\) 和 \(1\) 向右直行到的 \(l\) 之间满足 \(l+1=r\),那么会算一次边 \((l,r)\) 这种错误的情况,需要减去。
- 由于本题多次设计相邻对的讨论,所以一定注意需不需要 \(+1/-1\)
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 150005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,a[M],l[M],r[M],vis[M][2];
vector<int> g1[M],g2[M];
void dfs1(int u,int f)
{
if(vis[u][f]) return ;vis[u][f]=1;
for(auto v:g1[u]) if(r[u+1]>=v-1) dfs1(v-1,!f);
}
void dfs2(int u,int f)
{
if(vis[u][f]) return ;vis[u][f]=1;
for(auto v:g2[u+1]) if(l[u]<=v+1) dfs2(v,!f);
}
int chk(int x,int y)
{
return (vis[x][0]&&vis[y-1][0])
||(vis[x][1]&&vis[y-1][1]);
}
void work()
{
n=read();m=read();
for(int i=0;i<=n+1;i++)
{
a[i]=vis[i][0]=vis[i][1]=l[i]=r[i]=0;
g1[i].clear(),g2[i].clear();
}
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
if(u+1==v) a[u]=v;
else
{
g1[u].push_back(v);
g2[v].push_back(u);
}
}
for(int i=1;i<=n;i++)
{
if(i<n) g1[i].push_back(n+1);
if(i>1) g2[i].push_back(0);
}
a[0]=a[n]=1;r[n+1]=n+1;
for(int i=n;i>=0;i--) r[i]=a[i]?r[i+1]:i;
for(int i=1;i<=n+1;i++) l[i]=a[i-1]?l[i-1]:i;
if(!l[n+1]) {printf("%lld\n",1ll*n*(n-1)/2);return ;}
dfs1(r[1],1);vis[r[1]][1]=0;dfs2(r[1],1);
long long ans=0,c1=0,c2=0;
for(int i=0;i<=r[1];i++) c1+=vis[i][1];
for(int i=l[n]-1;i<=n;i++) c2+=vis[i][1];
ans+=c1*c2;c1=c2=0;
for(int i=0;i<=r[1];i++) c1+=vis[i][0];
for(int i=l[n]-1;i<=n;i++) c2+=vis[i][0];
ans+=c1*c2;c1=c2=0;
for(int i=0;i<=r[1];i++) c1+=vis[i][0]&&vis[i][1];
for(int i=l[n]-1;i<=n;i++) c2+=vis[i][0]&&vis[i][1];
ans-=c1*c2;c1=c2=0;
printf("%lld\n",ans-(r[1]+1==l[n]));
}
signed main()
{
T=read();
while(T--) work();
}