题解:CF1616G Just Add an Edge
或许更好的阅读体验
题意
- 有一个
N 个点M 条边的 DAG。每条边u\to v 均有v>u 。 - 你要添加一条边
x\to y(x>y) ,问有多少种方法可以使这张图具有哈密顿路。 -
1\le N\le 150000,1\le M\le \min(\frac{n(n-1)}{2},150000)
题解
考虑怎么样这张图才能有哈密顿路。
首先一个 DAG 什么时候有哈密顿路,那由于题设的性质,它必然存在一条
特判这种情况后,考虑一般情况。容易发现除了加的这条边
那么最终的哈密顿回路必然形如这样:
其中两个括起来的部分表示每次
是否存在
容易发现这个条件形如存在一对
由于两条链都只能从小到大延伸,想要覆盖一段连续的区间,那么两条链必定形如这样:
那么考虑枚举对颜色交界点 DP,设
具体转移考虑若
这样就能
容易发现,对于一个
由于需要存在
此处暂且断一下,注意到
f_{0} 没有定义,但这里能取到0 是因为哈密顿路的起点不一定是1 ,可能根本不存在(1\to \dots \to y-1) 的路径。不妨建一个虚点,向所有点连边,这样起点就能固定是0 了。同理建一个虚点n+1 ,让所有点向它连边,这样终点也能固定了。
回到刚才的思路,由于
考虑对于
然后路径就转化成了
而且这个非常强力,考虑一个简单的容斥,计算满足
注意到一个 bug,根据枚举的方式,假如
复杂度
code
#include<bits/stdc++.h>
#define mem(a,b) memset(a,b,sizeof(a))
#define forup(i,s,e) for(int i=(s);i<=(e);i++)
#define fordown(i,s,e) for(int i=(s);i>=(e);i--)
#ifdef DEBUG
#define msg(args...) fprintf(stderr,args)
#else
#define msg(...) void()
#endif
using namespace std;
using i64=long long;
#define gc getchar()
inline int read(){
int x=0,f=1;char c;
while(!isdigit(c=gc)) if(c=='-') f=-1;
while(isdigit(c)){x=(x<<3)+(x<<1)+(c^48);c=gc;}
return x*f;
}
#undef gc
const int N=150005,inf=0x3f3f3f3f;
int n,m,a[N],r[N];
int dp[N][2];
vector<int> e[N];
void solve(){
n=read();m=read();
forup(i,0,n+1){//注意清空
e[i].clear();
r[i]=i;a[i]=0;
dp[i][0]=dp[i][1]=0;
if(i>1) e[0].push_back(i);
if(i<n) e[i].push_back(n+1);
}
a[n]=a[0]=1;
forup(i,1,m){
int u=read(),v=read();
if(v==u+1) a[u]=1;
else e[u].push_back(v);
//如果 v=u+1 就不加边,因为这种边在转移时 j-1<i+1,这样可以避免转移时特判
}
fordown(i,n+1,0){
if(a[i]) r[i]=r[i+1];
}
if(r[0]==n+1){//特判 r[1]=n,注意边界的改变
printf("%lld\n",1ll*n*(n-1)/2);
return;
}
dp[r[0]][0]=1;
forup(i,r[0],n+1){
for(auto j:e[i]){
if(j-1<=r[i+1]){
dp[j-1][0]|=dp[i][1];
dp[j-1][1]|=dp[i][0];
}
}
}
fordown(i,r[0]-1,0){
for(auto j:e[i]){
if(j-1<=r[i+1]){
dp[i][0]|=dp[j-1][1];
dp[i][1]|=dp[j-1][0];
}
}
}
int cx0=0,cx1=0,cy0=0,cy1=0,cx01=0,cy01=0;
forup(i,0,r[0]){
cy0+=dp[i][0];
cy1+=dp[i][1];
cy01+=dp[i][0]&&dp[i][1];
}
int lst=n+1;
while(r[lst]==n+1) --lst;
forup(i,lst,n){
cx0+=dp[i][0];
cx1+=dp[i][1];
cx01+=dp[i][0]&&dp[i][1];
}
i64 ans=1ll*cx0*cy0+1ll*cx1*cy1-1ll*cx01*cy01;
if(r[r[0]+1]==n+1) --ans;
printf("%lld\n",ans);
}
signed main(){
int t=read();
while(t--){
solve();
}
}