• Codeforces 1338E JYPnation (图论)


    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=Vsetminus P),则存在 (uin Q,vin P) 满足 ((u,v)) 有边。
    证明 由于整张图强连通,显然。
    (题解在这里的做法是取度数最大的点作为 (X),实际上是需要的,理由将在下面给出。)
    任取一个满足引理 2 条件的点 (v). 设 (R=in(v)cap Q,S=Qsetminus R).
    引理 3 (forall yin S,zin 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=Scup R) 无环。
    到这里,我们就知道我们把这张图划分成了两个部分,且两部分分别无环。

    对两部分分别进行拓扑排序,并给他们标号为 (P_i,Q_i)(现在把集合看成序列),不妨设 (ilt 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) 的一段后缀;
    证明 反证,若存在 (jlt 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_ile l_{i+1}).
    反证:若 (l_igt 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 e +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)=1Leftrightarrow ilt j)
    (dis(P_i,P_j)=2Leftrightarrow jlt iland |inQ(P_i)| e |inQ(P_j)|)
    (dis(P_i,P_j)=3Leftrightarrow jlt iland |inQ(P_i)|=|inQ(P_j)|)
    (dis(Q_i,Q_j)=1Leftrightarrow ilt j)
    (dis(Q_i,Q_j)=2Leftrightarrow jlt iland |inP(Q_i)| e |inP(Q_j)|)
    (dis(Q_i,Q_j)=3Leftrightarrow jlt iland |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
    ",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
    ",ans);
    	return 0;
    }
    
  • 相关阅读:
    HDU
    Count on a tree
    Codeforces Round #368 (Div. 2) D. Persistent Bookcase
    Codeforces Round #321 (Div. 2) E
    Codeforces Round #220 (Div. 2) D
    树状数组区间加法更新板子
    在 tableview的上面 添加 一个view, 下面 加一个 button,都是 监听 scrollview的滑动而已,
    synchronize,
    菊花,
    4294967295,
  • 原文地址:https://www.cnblogs.com/suncongbo/p/12779107.html
Copyright © 2020-2023  润新知