1. 程式人生 > 實用技巧 >CF437C The Child and Toy

CF437C The Child and Toy

CF437C The Child and Toy

洛谷傳送門

題意翻譯

n個帶權點,m條無向邊,刪除一個點就要付出所有與之有聯絡且沒有被刪除的點的點權之和的代價。

求刪除所有點的最小代價。


題解:

2020.11.26模擬賽T3 滿分場。

場上看到最優化先想了DP。但是這道題壓根就劃分不出狀態。看到很多大神用的拓撲序解法。各種玄學,反正我是沒想到。既然DP沒戲了,那就開始想貪心。

怎麼貪呢?手推了幾組樣例,發現每次刪除的總是當前沒被刪除的所有點中權值最大的那個點。然後按這個順序都刪完就是最優解。(出題人向其中加入了重邊和自環,所以程式碼裡有些許這道題用不上的點)

考場上沒證明這個貪心,過了大樣例就沒再管。

然後場下證明這個貪心:如果不是每次刪除當前狀態下權值最大的節點,那麼會導致這個節點至少被算入貢獻一次。此時就沒有直接刪除它更優秀。

程式碼:

#include<cstdio>
#include<set>
#include<queue>
#include<algorithm>
#define int long long
using namespace std;
const int maxn=1e4+4;
int n,m,ans;
int w[maxn];
bool v[maxn];
priority_queue<pair<int,int> >q;
set<int> e[maxn];
int tot,head[maxn],nxt[maxn<<2],to[maxn<<2];
void add(int x,int y)
{
    to[++tot]=y;
    nxt[tot]=head[x];
    head[x]=tot;
}
signed main()
{
    // freopen("star.in","r",stdin);
    // freopen("star.out","w",stdout);
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&w[i]);
        q.push(make_pair(w[i],i));
    }
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%lld%lld",&x,&y);
        if(x==y)
            continue;
        if(e[x].find(y)==e[x].end())
        {
            add(x,y);
            add(y,x);
            e[x].insert(y);
            e[y].insert(x);
        }
    }
    while(!q.empty())
    {
        int x=q.top().second;
        q.pop();
        v[x]=1;
        for(int i=head[x];i;i=nxt[i])
        {
            int y=to[i];
            if(v[y])
                continue;
            ans+=w[y];
        }
    }
    printf("%lld\n",ans);
    return 0;
}