题意
有一张n个点的无向完全图,给每一条边规定一个方向后得到一个新图
现在只知道每个点的出度
你需要计算有多少点对(u, v) u可以到达v,特殊的,每个点自己都可以到达自己
保证答案唯一
思路
首先定下一个"基准图",即点 [ 1.. n ] [1..n] [1..n]出度分别为 [ 0.. n − 1 ] [0..n-1] [0..n−1]。在这张图上,每个点只能连到下标比自己小的点上面。
那么考虑把这张基准图改成拥有题目给出出度的图。把出度按升序排序,那么可以保证相邻的两个点一定可以有一条从
i
+
1
i+1
i+1连到
i
i
i的边。In this case,假设del[i]表示需要的出度与基准的出度的差,则有一些点的出度大于基准图(
d
e
l
[
i
]
>
0
del[i]>0
del[i]>0),有一些点出度小于基准图(
d
e
l
[
i
]
<
0
del[i]<0
del[i]<0)。因为图是存在的,所以前缀的
d
e
l
[
i
]
del[i]
del[i]和必然大于等于0,即对于任意一个
d
e
l
[
i
]
>
0
del[i]>0
del[i]>0,他的后面一定存在至少一个
d
e
l
[
j
]
<
0
del[j]<0
del[j]<0,两者之间的边反一下方向,即可使大于0的-1 (s),小于0的+1 (s)。
于是,因为题目的一句保证答案唯一,我们只要构造一种链接 d e l [ i ] > 0 del[i]>0 del[i]>0和 d e l [ j ] < 0 del[j]<0 del[j]<0的点的方案,使得 d e l [ i ] del[i] del[i]全部变为0。
但是因为某一对 ( i , j ) (i, j) (i,j)之间只有一条边可以用来反向,如果随便构造可能会出现剩下两个点 d e l [ i ] > 1 del[i]>1 del[i]>1和 d e l [ j ] < 1 del[j] < 1 del[j]<1,这种情况是不可行的。所以来一个贪心,对于从前往后的每一个 d e l [ j ] < 0 del[j]<0 del[j]<0,尽量找前面 d e l [ i ] del[i] del[i]大的连反向边,这样可以保证剩下的 d e l [ i ] > 0 del[i]>0 del[i]>0的数量尽量多,那么后面出现不可行情况的可能就最小。于是答案就构造出来了。
贪心反过来仍旧可行,即从后往前找 d e l [ i ] > 0 del[i]>0 del[i]>0的,匹配他后面 d e l [ j ] < 0 del[j]<0 del[j]<0的。感谢scl的数据。
代码放一下吧,实在是一道不错的题目,感谢zlm大佬讲题。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 110;
int n, a[N];
bool to[N][N];
bool vis[N], ins[N];
int Dfs(int u)
{
int ret = 1;
ins[u] = 1;
for (int i = 1; i <= n; i++)
if (!ins[i] && to[u][i])
ret += Dfs(i);
return ret;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a+1, a+n+1);
memset(to, 0, sizeof(to));
for (int i = 1; i <= n; i++)
for (int j = i+1; j <= n; j++)
to[j][i] = 1;
for (int i = 1; i <= n; i++)
a[i] = a[i]-(i-1);
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++)
if (a[i] < 0){
memset(vis, 0, sizeof(vis));
for (int k = a[i]; k < 0; k++){
int id = -1;
for (int j = 1; j < i; j++)
if (a[j] > 0 && !vis[j] && (id == -1 || a[id] < a[j]))
id = j;
a[id]--;
a[i]++;
to[id][i] = 1;
to[i][id] = 0;
vis[id] = 1;
}
}
int ans = 0;
for (int i = 1; i <= n; i++){
memset(ins, 0, sizeof(ins));
ans += Dfs(i);
}
cout << ans;
return 0;
}
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 110;
int n, a[N];
bool to[N][N];
bool vis[N], ins[N];
int Dfs(int u)
{
int ret = 1;
ins[u] = 1;
for (int i = 1; i <= n; i++)
if (!ins[i] && to[u][i])
ret += Dfs(i);
return ret;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a+1, a+n+1);
memset(to, 0, sizeof(to));
for (int i = 1; i <= n; i++)
for (int j = i+1; j <= n; j++)
to[j][i] = 1;
for (int i = 1; i <= n; i++)
a[i] = a[i]-(i-1);
for (int i = n; i >= 1; i--)
if (a[i] > 0){
memset(vis, 0, sizeof(vis));
for (int k = 1, sz = a[i]; k <= sz; k++){
// for循环的第二个分好之前是每做一次判断一次,所以如果a[i]在不停的变化,与k比较的就是变化后的a[i],而不是原来的a[i]
int id = -1;
for (int j = i+1; j <= n; j++)
if (!vis[j] && a[j] < 0 && (id == -1 || a[id] > a[j]))
id = j;
if (id == -1) continue;
a[id]++;
a[i]--; // 出锅地点
to[id][i] = 0;
to[i][id] = 1;
vis[id] = 1;
}
}
int ans = 0;
for (int i = 1; i <= n; i++){
memset(ins, 0, sizeof(ins));
ans += Dfs(i);
}
cout << ans;
return 0;
}