奇异图(思维+贪心)

题意

有一张n个点的无向完全图,给每一条边规定一个方向后得到一个新图
现在只知道每个点的出度
你需要计算有多少点对(u, v) u可以到达v,特殊的,每个点自己都可以到达自己
保证答案唯一

思路

首先定下一个"基准图",即点 [ 1.. n ] [1..n] [1..n]出度分别为 [ 0.. n − 1 ] [0..n-1] [0..n1]。在这张图上,每个点只能连到下标比自己小的点上面。

那么考虑把这张基准图改成拥有题目给出出度的图。把出度按升序排序,那么可以保证相邻的两个点一定可以有一条从 i + 1 i+1 i+1连到 i i i的边。In this case,假设del[i]表示需要的出度与基准的出度的差,则有一些点的出度大于基准图( d e l [ i ] &gt; 0 del[i]&gt;0 del[i]>0),有一些点出度小于基准图( d e l [ i ] &lt; 0 del[i]&lt;0 del[i]<0)。因为图是存在的,所以前缀的 d e l [ i ] del[i] del[i]和必然大于等于0,即对于任意一个 d e l [ i ] &gt; 0 del[i]&gt;0 del[i]>0,他的后面一定存在至少一个 d e l [ j ] &lt; 0 del[j]&lt;0 del[j]<0,两者之间的边反一下方向,即可使大于0的-1 (s),小于0的+1 (s)

于是,因为题目的一句保证答案唯一,我们只要构造一种链接 d e l [ i ] &gt; 0 del[i]&gt;0 del[i]>0 d e l [ j ] &lt; 0 del[j]&lt;0 del[j]<0的点的方案,使得 d e l [ i ] del[i] del[i]全部变为0。

但是因为某一对 ( i , j ) (i, j) (i,j)之间只有一条边可以用来反向,如果随便构造可能会出现剩下两个点 d e l [ i ] &gt; 1 del[i]&gt;1 del[i]>1 d e l [ j ] &lt; 1 del[j] &lt; 1 del[j]<1,这种情况是不可行的。所以来一个贪心,对于从前往后的每一个 d e l [ j ] &lt; 0 del[j]&lt;0 del[j]<0,尽量找前面 d e l [ i ] del[i] del[i]大的连反向边,这样可以保证剩下的 d e l [ i ] &gt; 0 del[i]&gt;0 del[i]>0的数量尽量多,那么后面出现不可行情况的可能就最小。于是答案就构造出来了。

贪心反过来仍旧可行,即从后往前找 d e l [ i ] &gt; 0 del[i]&gt;0 del[i]>0的,匹配他后面 d e l [ j ] &lt; 0 del[j]&lt;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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值