• 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();
    }
    
  • 相关阅读:
    Java实现 LeetCode 649 Dota2 参议院(暴力大法)
    Java实现 LeetCode 648 单词替换(字典树)
    Java实现 LeetCode 648 单词替换(字典树)
    php getimagesize 函数
    PHP gd_info
    PHP 5 时区
    PHP zip_read() 函数
    PHP zip_open() 函数
    滚动界限终点 | scroll-snap-destination (Scroll Snap)
    滚动界限种类 | scroll-snap-type (Scroll Snap)
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15800897.html
Copyright © 2020-2023  润新知