病毒入侵——解题报告

题目链接:

http://172.18.70.217/problem/9

题目大意:

题目分析:

本题的题意就是让我们求出每次破坏后的两个数值。很容易想到按照病毒进行 B F S BFS BFS就可求出答案,但是这样的时间复杂度为 O ( n q ) O(nq) O(nq),超过了题目允许的范围,所以我们需要更快的做法。
首先,因为本质上对答案有贡献的只有那些防御值小于攻击值的边。因为我们需要加速,所以一个事实就是,如果之前就处理了低于某个攻击值的边,那么后面就不用处理了,这样时间复杂度会大大下降,因为每条边只遍历一次的缘故时间复杂度下降了。
故我们可以将边按防御值从小到大排序,并将每次攻击按攻击力从小到大排序。那么还有个问题,每次投放病毒的位置不同,怎么快速得到答案呢?因为这本身是一个图,一旦一个点上有病毒,周围边的防御值小于攻击力,那么边那一头的点就会被破坏,举个例子:
这里写图片描述
这里病毒起始点在五角星处,攻击力为 4 4 4,因为周围各点连边防御值小于 4 4 4所以被破坏,并以新破坏的点为起点重复刚才的行为。所以这些被感染的点在攻击力为 4 4 4的条件下是一个整体,这里就可以用并查集来维护了。按题目要求,并查集的域包括:父亲,最大值,最大值个数。我们按照攻击力遍历时就将这些防御值小于攻击值的点合并起来。查询时就查询病毒起始点所在的整体的情况就行了。由于本题时间卡得较死,所以各处都需要细心处理,不浪费多余的时间。算法时间复杂度大致为 O ( m a x ( m , q ) ) O(max(m,q)) O(max(m,q))

正解代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#define inf 0x7fffffff

using namespace std;
typedef long long ll;
const ll maxn=300010;
struct node
{
    ll u;
    ll v;
    ll w;
}e[400010];
vector<ll> vec[100005];
struct ano
{
    ll name;
    ll a;
    ll b;
    ll num;
    ll vec;
    vector<ll> *pos;
}p[400010];
ll n,m,q,imp[maxn],mon[maxn];
bool cmp1(node a,node b)
{
    return a.w<b.w;
}
bool cmp2(ano a,ano b)
{
    return a.vec<b.vec;
}
ll fa[maxn],Max[maxn],times[maxn],val[maxn];
ll resa[100005], resb[100005];
ll get(ll x)
{
	if(fa[x]!=x)
		fa[x]=get(fa[x]);
	return fa[x];
}
void merge(ll a,ll b)
{
    ll f1=get(a),f2=get(b);
    if(f1!=f2)
    {
        fa[f1]=f2;
        if(Max[f1]>Max[f2])
        {
            Max[f2]=Max[f1];
            times[f2]=times[f1];
        }
        else if(Max[f1]==Max[f2])
            times[f2]+=times[f1];
        val[f2]+=val[f1];
    }
}
int vis[maxn];
bool nus[maxn]; 
int main()
{
    //freopen("TEST3.txt","r",stdin);
    scanf("%lld%lld%lld",&n,&m,&q);
    for(ll i=1;i<=n;i++)
        scanf("%lld",&imp[i]);
    ll whole=0;
    for(ll i=1;i<=n;i++)
    {
        scanf("%lld",&mon[i]);
        whole+=mon[i];
    }
    for(ll i=1;i<=m;i++)
    {
        ll x,y,z;
        scanf("%lld%lld%lld",&x,&y,&z);
        e[i].u=x;e[i].v=y;e[i].w=z;
    }
    sort(e+1,e+m+1,cmp1);
    for(ll i=1;i<=q;i++)
    {
        p[i].name=i;
        scanf("%lld%lld",&p[i].num,&p[i].vec);
        p[i].pos = &vec[i];
        for(ll j=1;j<=p[i].num;j++)
        {
            ll x;
            scanf("%lld",&x);
            p[i].pos->push_back(x);
        }
    }
    sort(p+1,p+1+q,cmp2);
    for(ll i=1;i<=n;i++)
    {
        fa[i]=i;
        Max[i]=imp[i];
        times[i]=1;
        val[i]=mon[i];
    }
    ll end1=1;
    for(ll i=1;i<=q;i++)
    {
    	memset(nus,false,sizeof(nus));
        while(end1<=m && e[end1].w<=p[i].vec)
        {
        	merge(e[end1].u, e[end1].v);
			++end1;
		}
        ll ans=0,count1=0,sum=0;
        for(ll j=0;j<p[i].num;j++)
        {
            ll tmpf=get((*p[i].pos)[j]);
            if(vis[tmpf]!=i)
            {
                if(Max[tmpf]>ans)
                {
                    count1=times[tmpf];
                    ans=Max[tmpf];
                }
                else if(Max[tmpf]==ans)
                    count1+=times[tmpf];        
                sum+=val[tmpf];
                vis[tmpf]=i;
            }
        }
        p[i].a=ans*count1;
        p[i].b=whole-sum;
    }
    for (int i =1;i<=q;++i)
	{
        resa[p[i].name]=p[i].a;
        resb[p[i].name]=p[i].b;
    }
    for(ll i=1;i<=q;i++)
        printf("%lld %lld\n",resa[i],resb[i]);
    
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值