竞赛图的得分序列 (SRM 717 div 1 250)

SRM 717 DIV 1 中 出了这样一道题:

竞赛图就是把一个无向完全图的边定向后得到的有向图,得分序列就是每个点的出度构成的序列。

给出一个合法的竞赛图出度序列, 要求构造出原图(原题是求(u, v)有路径的点对数,似乎有不需要构造出原图的方法)。

 

当时我的做法是 直接构造一个网络,跑最大流。

比赛后总觉得这个题有什么神奇的性质,于是搜了一下相关资料:

有一篇关于得分序列的论文:
http://www.sciencedirect.com/science/article/pii/0095895679900455?via%3Dihub

 

其中介绍了一个性质:

Landau Theorem: 竞赛图的出度序列为si的充要条件是 对于任意的子集X,iXsi(|X|2)

1.必要性很容易证明:如果si是竞赛图的得分序列, 对于任意一个子集X, 它的得分之和一定大于等于它内部点之间的得分和。

2.充分性证明: 大致思路是构造一个二分图然后利用Hall定理 证明完美匹配。

 首先把边<i, j> (i < j) 看做左边的点。 右边部分,对于每个si 搞出si个点(这些点记为i类点)。

 对于左边的点<i, j> ,  向右边所有的i类点和j类点各连一条边。 显然一个完美匹配 和 一个原图对应。

   根据Hall定理,有完美匹配的充要条件是  对于左边任意的点集X,   |H(X)||X|.  H(X)是右边与X中的点有边相连的点的集合。

   对于左边任意的点集X, 设集合YX中的边的端点的集合。 即Y={x|(x,t)X or (t,x)X}

   根据所给的条件, 我们有 |X|(|Y|2)iYsi=|H(X)|, 所以存在完美匹配。定理得证。

 

接下来我们怎么用这个定理来构造原图呢?

当然可以构造出二分图然后跑最大匹配。 

我自己又YY了一种贪心做法:

大致思想是不断给边定向,但是要让得分序列满足Landau Theorem。

先将所有点按si 从小到大排序, 考虑 si最小的那个点x, 有n1si 条边指向它, 我们确定哪些点向它连边,让这些点的score -1. 显然贪心一下 让score 最大的n1si个点 向它连边最优。    对于其它的点, x向它们连边就好。  

 

AC代码:

复制代码
 1 // BEGIN CUT HERE  
 2   
 3 // END CUT HERE  
 4 #line 5 "ScoresSequence.cpp"  
 5 #include <vector>  
 6 #include <list>  
 7 #include <map>  
 8 #include <set>  
 9 #include <deque>  
10 #include <stack>  
11 #include <bitset>  
12 #include <algorithm>  
13 #include <functional>  
14 #include <numeric>  
15 #include <utility>  
16 #include <sstream>  
17 #include <iostream>  
18 #include <iomanip>  
19 #include <cstdio>  
20 #include <cmath>  
21 #include <cstdlib>  
22 #include <ctime>  
23 #include <cstring>  
24 using namespace std;  
25 #define N 102
26 const int INF = 1e9 + 1;
27 
28 bool a[N][N];
29 vector<pair<int, int> > lis, tmp;
30 
31 
32 class ScoresSequence  
33 {  
34 public:  
35     int count(vector <int> s)  
36     {  
37         int n = s.size();
38         memset(a, 0, sizeof(a));
39         for (int i = 0; i < n; ++i) a[i][i] = true;
40 
41         lis.clear();
42         for (int i = 0; i < n; ++i) lis.push_back(make_pair(s[i], i));
43 
44         for (int i = 0; i < n - 1; ++i)
45         {
46             sort(lis.begin(), lis.end());
47             int c = n - i - lis[0].first - 1;
48             for (int k = 1; k <= c; ++k)
49             {
50                 lis[n - i - k].first--;
51                 a[lis[n - i - k].second][lis[0].second] = true;
52             }
53             for (int k = 1; k < n - i - c; ++k)
54                 a[lis[0].second][lis[k].second] = true;
55             tmp.clear();
56             for (int j = 1; j < lis.size(); ++j) tmp.push_back(lis[j]);
57             lis = tmp;
58         }
59         int ans = 0;
60         for (int k = 0; k < n; ++k)
61             for (int i = 0; i < n; ++i)
62                 for (int j = 0; j < n; ++j)
63                     a[i][j] |= a[i][k] & a[k][j];
64         for (int i = 0; i < n; ++i)
65             for (int j = 0; j < n; ++j)
66                 ans += a[i][j];
67         return ans;
68     }
69 
70 // BEGIN CUT HERE
71     public:
72     void run_test(int Case) { if ((Case == -1) || (Case == 0)) test_case_0(); if ((Case == -1) || (Case == 1)) test_case_1(); if ((Case == -1) || (Case == 2)) test_case_2(); if ((Case == -1) || (Case == 3)) test_case_3(); if ((Case == -1) || (Case == 4)) test_case_4(); }
73     private:
74     template <typename T> string print_array(const vector<T> &V) { ostringstream os; os << "{ "; for (typename vector<T>::const_iterator iter = V.begin(); iter != V.end(); ++iter) os << '\"' << *iter << "\","; os << " }"; return os.str(); }
75     void verify_case(int Case, const int &Expected, const int &Received) { cerr << "Test Case #" << Case << "..."; if (Expected == Received) cerr << "PASSED" << endl; else { cerr << "FAILED" << endl; cerr << "\tExpected: \"" << Expected << '\"' << endl; cerr << "\tReceived: \"" << Received << '\"' << endl; } }
76     void test_case_0() { int Arr0[] = {2, 0, 1}; vector <int> Arg0(Arr0, Arr0 + (sizeof(Arr0) / sizeof(Arr0[0]))); int Arg1 = 6; verify_case(0, Arg1, count(Arg0)); }
77     void test_case_1() { int Arr0[] = {1, 0, 2}; vector <int> Arg0(Arr0, Arr0 + (sizeof(Arr0) / sizeof(Arr0[0]))); int Arg1 = 6; verify_case(1, Arg1, count(Arg0)); }
78     void test_case_2() { int Arr0[] = {1, 1, 1}; vector <int> Arg0(Arr0, Arr0 + (sizeof(Arr0) / sizeof(Arr0[0]))); int Arg1 = 9; verify_case(2, Arg1, count(Arg0)); }
79     void test_case_3() { int Arr0[] = {0, 2, 8, 4, 3, 9, 1, 5, 7, 6}; vector <int> Arg0(Arr0, Arr0 + (sizeof(Arr0) / sizeof(Arr0[0]))); int Arg1 = 55; verify_case(3, Arg1, count(Arg0)); }
80     void test_case_4() { int Arr0[] = {22,20,14,13,17,15,12,18,23,15,21,26,33,5,19,9,37,0,25,28,4,12,35,32,25,7,31,6,2,29,10,33,36,27,39,28,40,3,8,38,3}; vector <int> Arg0(Arr0, Arr0 + (sizeof(Arr0) / sizeof(Arr0[0]))); int Arg1 = 1422; verify_case(4, Arg1, count(Arg0)); }
81 
82 // END CUT HERE
83   
84 };  
85   
86 // BEGIN CUT HERE  
87 int main()  
88 {  
89 ScoresSequence ___test;  
90 ___test.run_test(-1);  
91 system("pause");  
92 }  
93 // END CUT HERE  
复制代码

 

 

实现方法比较暴力,大概是 O(n^2 logn)

 

posted @   lzw4896s  阅读(546)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
点击右上角即可分享
微信分享提示