[数学记录]AT5800 [AGC043C] Giant Graph
command_block
2021-07-16 21:17:47
**题意** : 给出三张无向图 $G_a,G_b,G_c$,点数均为 $n$ ,点的标号为 $1\sim n$。
建立一张大图 $G$,其中点为三元组 $(a,b,c)$ ,其中 $a,b,c\in 1\sim n$。
- 对于 $G_a$ 中的边 $(a,v)$ ,在 $(a,b,c)$ 与 $(v,b,c)$ 之间连边。
- 对于 $G_b$ 中的边 $(b,v)$ ,在 $(a,b,c)$ 与 $(a,v,c)$ 之间连边。
- 对于 $G_c$ 中的边 $(c,v)$ ,在 $(a,b,c)$ 与 $(a,b,v)$ 之间连边。
点 $(a,b,c)$ 的权值定义为 $10^{18(a+b+c)}$。
求图 $G$ 的最大独立集权值和,答案对 $998244353$ 取模。
$n,m\leq 10^5$ ,时限$\texttt{2s}$。
------------
由于 $10^{18}$ 非常大,我们可以按照 $a+b+c$ 的顺序从大到小贪心考虑。
不难发现,对于 $a+b+c$ 相同(权值相等)的点,之间必然没有边,互不影响。
- 于是如下的贪心是正确的 :
从大到小枚举点(权值相同顺序任意),贪心选择。
> 赛时到这里就没思路了。
考虑进一步抽象这个贪心,发掘性质。
我们为边定向,从小点连向大点,图变为了 DAG。
从大到小考虑(也是按照拓扑序从后往前考虑)若出边到达的点均没有标记,将其加入最大独立集,并打上标记。
可以发现,这和博弈论中的 $\rm N/P$ 态的定义同构。在这个 DAG 中, $\rm N$ 态即为最大独立集。
考虑如何快速判定 $(a,b,c)$ 是否为 $\rm N/P$ 态。观察到三个维度可以分别移动,且每一步只能移动一维,等同于三个游戏之和。
求出三张图中各个点的 $\rm SG$ 值 $f_a(u),f_b(u),f_c(u)$。
则答案为 :
$$\sum\limits_{f_a(x){\ \rm xor\ }f_b(y){\ \rm xor\ }f_c(z)=0}10^{18x}10^{18y}10^{18z}$$
异或卷积即可。复杂度为 $O(n\log n)$。
- **结论** : DAG 的 $\rm SG$ 函数的上界为 $O(\sqrt{m})$。
于是可以 $O(m)$ 进行暴力异或卷积。
```cpp
#include<algorithm>
#include<cstdio>
#define pb push_back
#define MaxN 100500
using namespace std;
const int mod=998244353,buf=716070898,lim=1024;
int pw[MaxN];
struct Graph
{
vector<int> g[MaxN];
bool vis[MaxN],e[MaxN];
int sg[MaxN];
void dfs(int u)
{
if (vis[u])return ;
vis[u]=1;
for (int i=0;i<g[u].size();i++)
dfs(g[u][i]);
for (int i=0;i<g[u].size();i++)
e[sg[g[u][i]]]=1;
while(e[sg[u]])sg[u]++;
for (int i=0;i<g[u].size();i++)
e[sg[g[u][i]]]=0;
}
void build(int n,int *f){
int m;scanf("%d",&m);
for (int i=1,u,v;i<=m;i++){
scanf("%d%d",&u,&v);
if (u>v)swap(u,v);
g[u].pb(v);
}
for (int i=1;i<=n;i++){
dfs(i);
f[sg[i]]=(f[sg[i]]+pw[i])%mod;
}
}
}G1,G2,G3;
int n,f[1050],f1[1050],f2[1050],f3[1050];
int main()
{
scanf("%d",&n);
pw[0]=1;
for (int i=1;i<=n;i++)
pw[i]=1ll*pw[i-1]*buf%mod;
G1.build(n,f1);G2.build(n,f2);G3.build(n,f3);
int ans=0;
for (int i=0;i<lim;i++)
for (int j=0;j<lim;j++)
ans=(ans+1ll*f1[i]*f2[j]%mod*f3[i^j])%mod;
printf("%d",ans);
return 0;
}
```