UPD 2020.04.30:本题解被发现存在严重错误,已更正。

题目链接

https://codeforces.com/contest/1338/problem/E

题解

这题太神了……这才是 div1E 啊,比什么 nim 积意义下的离散对数之类的高明到不知道哪里去了
这篇题解主要复述一下官方题解并补充一下官方题解上省略的证明。所有证明都是蒟蒻口胡的,有问题敬请指出。

下面把题目保证不存在的那个 \(4\) 个点的子图称作 \(H\),用四元组表示 \(H\) 时,默认最后一个点入度为 \(3\);整张图的点集记作 \(V\). 设一个点 \(u\) 的入点集合为 \(in(u)\).

首先对这个图进行拓扑排序,每次删掉入度为 \(0\) 的点,则该点对答案的贡献是 \((614n+1)\) 乘以剩下的点数。不妨假设剩下的图非空,下面的内容都在剩下的图上进行。我们会发现:
引理 0 不存在入度为 \(0\) 的点时,整张图是强连通的。
证明 对其缩点后,大小超过 \(1\) 的 SCC 必定有三元环,而入度为 \(0\) 的 SCC 必定大小超过 \(1\). 因此如果 SCC 个数超过 \(1\),则取入度为 \(0\) 的 SCC 的一个三元环和其余的 SCC 中的一个点,会构成 \(H\).
引理 1 \(\forall u, in(u)\cup \{u\}\) 无环。
证明 反证,如果有环的话环上的点构成一个大小至少为 \(3\) 的 SCC,必定存在三元环,和 \(u\) 点构成 \(H\).
引理 2 任取一个点 \(X\),我们可以把整张图划分为两部分 \(P=in(X)\cup \{X\},Q=V\setminus P\),则存在 \(u\in Q,v\in P\) 满足 \((u,v)\) 有边。
证明 由于整张图强连通,显然。
(题解在这里的做法是取入度最大的点作为 \(X\),实际上是需要的,理由将在下面给出。)
任取一个满足引理 2 条件的点 \(v\). 设 \(R=in(v)\cap Q,S=Q\setminus R\).
引理 3 \(\forall y\in S,z\in R\)\((y,z)\) 有边。
证明 反证,设 \((z,y)\) 有边,则 \((v,X,z,y)\) 四个点构成 \(H\).
引理 4 \(S\) 无环,\(R\) 无环。
证明 根据引理 1 得 \(R\) 无环;若 \(S\) 有环则和 \(R\) 中任何一点构成 \(H\).
引理 5 \(P\) 无环,\(Q\) 无环。
证明 根据引理 1 得 \(P\) 无环,由 \(S,R\) 分别无环且 \(S,R\) 之间连的边都由 \(S\) 指向 \(R\) 得到 \(Q=S\cup R\) 无环。
到这里,我们就知道我们把这张图划分成了两个部分,且两部分分别无环。

对两部分分别进行拓扑排序,并给他们标号为 \(P_i,Q_i\)(现在把集合看成序列),不妨设 \(i\lt j\) 当且仅当存在边 \((P_i,P_j)\)\(Q\) 同理。
\(inP(u)=in(u)\cap P,inQ(u)=in(u)\cap Q\).
引理 6a \(\forall i\)\(inQ(P_i)\)\(Q\) 的一段后缀;
证明 反证,若存在 \(j\lt k\) 满足 \((P_i,Q_k),(Q_j,P_i)\). 注意到 \(P\) 的最后一个元素是 \(X\),且 \(X\)\(Q\) 中每个点都连了边。于是 \((P_i,Q_j,X,Q_k)\) 构成 \(H\).
那么不难发现,\(\forall i,j\), 若\(|inQ(P_i)|=|inQ(P_j)|\)\(inQ(P_i)=inQ(P_j)\),否则大的包含小的。
引理 6b \(\forall i\)\(inP(Q_i)\)\(P\) 的一段后缀。
证明\(l_i\) 为最小的 \(j\) 满足 \((Q_j,P_i)\) 有边(若不存在视为 \(+\infty\)),可以证明 \(l_i\le l_{i+1}\).
反证:若 \(l_i\gt l_{i+1}\) 且都不为 \(+\infty\),则 \((P_i,Q_{l_{i+1}},Q_{l_i},P_{i+1})\) 四个点构成 \(H\).
\(l_i=+\infty\),则由于入度不为 \(0\)\(P_1\) 一定满足 \(l_1\ne +\infty\),即 \((Q_{|Q|},P_1)\). 而因为 \((P_i,Q_{|Q|}),(Q_{|Q|},P_{i+1})\)\((P_1,P_i,Q_{|Q|},P_{i+1})\) 构成 \(H\).

还有一个问题:\(dis(Q_j,P_i)\)\((P_i,Q_j)\) 有边时的距离没有解决。由于整张图中没有入度大于 \(X\) 的点,故 \(Q\) 中每个点会往 \(P\) 中连至少一条边。而因为 \(Q\)\(P\) 连的点是 \(P\) 的一个前缀,因此一定会连到 \(P_1\),故 \(dis(Q_j,P_i)=2\).

最后总结一下结论:
\(dis(P_i,P_j)=1\Leftrightarrow i\lt j\)
\(dis(P_i,P_j)=2\Leftrightarrow j\lt i\land |inQ(P_i)|\ne |inQ(P_j)|\)
\(dis(P_i,P_j)=3\Leftrightarrow j\lt i\land |inQ(P_i)|=|inQ(P_j)|\)
\(dis(Q_i,Q_j)=1\Leftrightarrow i\lt j\)
\(dis(Q_i,Q_j)=2\Leftrightarrow j\lt i\land |inP(Q_i)|\ne |inP(Q_j)|\)
\(dis(Q_i,Q_j)=3\Leftrightarrow j\lt i\land |inP(Q_i)|=|inP(Q_j)|\)
\(dis(P_i,Q_j)+dis(Q_j,P_i)=3\)

时间复杂度 \(O(n^2)\).

代码

#include<bits/stdc++.h>
#define llong long long
#define mkpr make_pair
#define x first
#define y second
#define iter iterator
#define riter reversed_iterator
#define y1 Lorem_ipsum_dolor
using namespace std;

inline int read()
{
	int x = 0,f = 1; char ch = getchar();
	for(;!isdigit(ch);ch=getchar()) {if(ch=='-') f = -1;}
	for(; isdigit(ch);ch=getchar()) {x = x*10+ch-48;}
	return x*f;
}

const int mxN = 8000;
int ind[mxN+3];
vector<int> s1,s2;
char a[mxN+3][mxN+3];
queue<int> que;
int n; llong w,ans;

char decode(char x) {return x>=65?x-55:x-48;}

bool cmp(int x,int y) {return a[x][y];}

int main()
{
	scanf("%d",&n); w = 614ll*n;
	for(int i=1; i<=n; i++)
	{
		char ch = getchar();
		for(int j=4; j<=n; j+=4)
		{
			ch = decode(getchar());
			a[i][j-3] = (ch&8)>>3,a[i][j-2] = (ch&4)>>2,a[i][j-1] = (ch&2)>>1,a[i][j] = ch&1;
		}
	}
	for(int i=1; i<=n; i++) for(int j=i+1; j<=n; j++)
	{
		if(a[i][j]) {ind[j]++;} else {ind[i]++;}
	}
	for(int i=1; i<=n; i++) if(ind[i]==0) {que.push(i);}
	int cur = n;
	while(!que.empty())
	{
		int u = que.front(); que.pop();
		cur--; ans += (w+1ll)*cur;
		for(int v=1; v<=n; v++) if(a[u][v]&&v!=u)
		{
			ind[v]--;
			if(ind[v]==0) {que.push(v);}
		}
	}
	if(cur==0) {printf("%I64d\n",ans); return 0;}
	int u = 0; for(int i=1; i<=n; i++) if(u==0||ind[i]>ind[u]) {u = i;}
	for(int i=1; i<=n; i++) if(ind[i]) {if(u==i||a[i][u]) {s1.push_back(i);} else {s2.push_back(i);}}
	sort(s1.begin(),s1.end(),cmp); sort(s2.begin(),s2.end(),cmp);
	ans += 3ll*s1.size()*s2.size()+s1.size()*(s1.size()-1ll)/2ll+s2.size()*(s2.size()-1ll)/2ll;
	for(int i=0; i<s1.size(); i++) {ind[s1[i]] -= i;}
	for(int i=0; i<s1.size(); i++) for(int j=0; j<i; j++)
	{
		ans += ind[s1[i]]==ind[s1[j]]?3ll:2ll;
	}
	for(int i=0; i<s2.size(); i++) {ind[s2[i]] -= i;}
	for(int i=0; i<s2.size(); i++) for(int j=0; j<i; j++)
	{
		ans += ind[s2[i]]==ind[s2[j]]?3ll:2ll;
	}
	printf("%I64d\n",ans);
	return 0;
}