HDU4467:Graph(点的度数分块)

传送门

题意

给出一张n个点m条边的无向图,点的颜色为0/1,每次有两种操作:
1.Asksum x y,查询两点颜色为x和y的边的权值之和
2.Change x,将x颜色取反

分析

最直接的做法是每次改变点的颜色豆浆与该点所连的边更新,\(O(q*m)\),超时
那么我们考虑将点根据度数分类,将度数\(<sqrt(m)\)的点称为普通点,否则为超级点。设置sum[i][0/1]代表第i个点(超级点)与颜色为0/1相连的边的权值和。普通点颜色改变,暴力修改与普通点相连的所有的点对答案的贡献\(O(\sqrt{m})\);超级点颜色改变,直接修改对答案的贡献\(O(1)\);最后修改与变颜色的点相连的所有超级点的sum,具体细节见代码

trick

1.用vector写会wa,不知道为什么
2.一些写的更详细的blog,对于答案的贡献修改有清晰的介绍
hdu4467 Graph(构造法求解)
hdu4467 Graph(图的分块)

代码

//vector(wa)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

#define F(i,a,b) for(int i=a;i<=b;++i)
#define R(i,a,b) for(int i=a;i<b;++i)
#define ll long long 
#define mem(a,b) memset(a,b,sizeof(a))
const int N = 100100;
int n,m,q,cas;
int color[N],du[N],super[N];
ll sum[N][2],ans[3];
struct edge
{
    int u,v;
    ll w;
    bool operator<(const edge &p)const
    {
        return u==p.u?v<p.v:u<p.u;
    }
}e[N];
struct node
{
    int v;
    ll w;
    node(int _v,ll _w):v(_v),w(_w){}
};
vector<node>ve[N];
void change(int x)
{
    color[x]^=1;
    if(super[x])
    {
        ans[(color[x]^1)+0]-=sum[x][0];
        ans[(color[x]^1)+1]-=sum[x][1];
        ans[(color[x])+0]+=sum[x][0];
        ans[(color[x])+1]+=sum[x][1];
    }
    else
    {
        for(int i=0;i<ve[x].size();++i)
        {
            int y=ve[x][i].v;ll z=ve[x][i].w;
                ans[(color[x]^1)+color[y]]-=z;
                ans[(color[x])+color[y]]+=z;
        }
    }
    for(int i=0;i<ve[x].size();++i)
    {
        int y=ve[x][i].v;ll z=ve[x][i].w;
        if(super[y])
        {
            sum[y][color[x]^1]-=z;
            sum[y][color[x]]+=z;
        }
    }
}
/*
void check()
{
    for(int i=1;i<=n;++i) printf("%d%c",color[i],i==n?'\n':' ');
    for(int i=1;i<=n;++i) printf("%lld %lld%c",sum[i][0],sum[i][1],i==n?'\n':' ');
}
*/
int main()
{
    while(scanf("%d %d",&n,&m)!=EOF)
    {
        mem(du,0);mem(ans,0);mem(sum,0);
        F(i,0,n) ve[i].clear();
        F(i,1,n) scanf("%d",color+i);
        F(i,1,m) {scanf("%d %d %lld",&e[i].u,&e[i].v,&e[i].w);if(e[i].u>e[i].v) swap(e[i].u,e[i].v);}
        sort(e+1,e+1+m);
        int cnt=0;
        for(int i=1,j;i<=m;i=j){
            for(j=i+1;j<=m;++j) if(e[i].u==e[j].u&&e[i].v==e[j].v) e[i].w+=e[j].w;else break;
                e[++cnt]=e[i];
        }
        F(i,1,cnt) du[e[i].u]++,du[e[i].v]++;
        int judge=sqrt(cnt);
        F(i,1,n) super[i]=(du[i]>=judge); 
        F(i,1,cnt)
        {
            if(super[e[i].u]) {ve[e[i].v].push_back(node(e[i].u,e[i].w));sum[e[i].u][color[e[i].v]]+=e[i].w;}else ve[e[i].u].push_back(node(e[i].v,e[i].w));
            if(super[e[i].v]) {ve[e[i].u].push_back(node(e[i].v,e[i].w));sum[e[i].v][color[e[i].u]]+=e[i].w;}else ve[e[i].v].push_back(node(e[i].u,e[i].w));
            ans[color[e[i].u]+color[e[i].v]]+=e[i].w;
        }
        printf("Case %d:\n", ++cas);  
        for(scanf("%d",&q);q--;)
        {
            char s[10];scanf("%s",s);
            if(s[0]=='A'){ int x,y;scanf("%d %d",&x,&y);printf("%lld\n",ans[x+y]); }
            else
            {
                int x;scanf("%d",&x);change(x);
            }
        }
     //   check();
    }
    return 0;
}
//邻接表(ac)
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;

#define F(i,a,b) for(int i=a;i<=b;++i)
#define ll long long 
#define mem(a,b) memset(a,b,sizeof(a))
const int N = 100100;
int n,m,q,cas;
int color[N],du[N],super[N];
ll sum[N][2],ans[3];
struct edge
{
    int u,v;
    ll w;
    edge(int u=0,int v=0,ll w=0):u(u),v(v),w(w){}
    bool operator<(const edge &p)const
    {
        return u==p.u?v<p.v:u<p.u;
    }
}e[N];
int head[N][2], v[N<<2], nxt[N<<2], tot;  
ll w[N<<2];  
inline void add(int t, int _u, int _v, ll _w){  v[++tot] = _v; w[tot] = _w; nxt[tot] = head[_u][t]; head[_u][t] = tot; }  
int main()
{
    while(scanf("%d %d",&n,&m)!=EOF)
    {
        mem(du,0);
        F(i,1,n) scanf("%d",color+i);
        F(i,1,m) {scanf("%d %d %I64d",&e[i].u,&e[i].v,&e[i].w);if(e[i].u>e[i].v) swap(e[i].u,e[i].v);}
        sort(e+1,e+m+1);
        //对边去重
        int cnt=0;
        for(int i=1,j;i<=m;i=j){
            for(j=i+1;j<=m&&e[i].u==e[j].u&&e[i].v==e[j].v;++j)e[i].w+=e[j].w;
                e[++cnt]=e[i];
        }
        F(i,1,cnt) du[e[i].u]++,du[e[i].v]++;//计算度数
        int judge=sqrt(cnt);//设定界限,高于界限为超级点,否则为普通点
        F(i,1,n) super[i]=(du[i]>=judge); 
        mem(head,0);tot=0;mem(ans,0);mem(sum,0);
        F(i,1,cnt)//构建邻接表
        {
            int x=e[i].u,y=e[i].v;ll z=e[i].w;
            //如果为超级点,就要放到被访问的位置,否则放到访问的位置,对超级点相连的边记录sum值
            if(super[x]) {add(1,y,x,z);sum[x][color[y]]+=z;}else add(0,x,y,z);
            if(super[y]) {add(1,x,y,z);sum[y][color[x]]+=z;}else add(0,y,x,z);
            ans[color[x]+color[y]]+=z;//将边权记录到答案中
        }
        printf("Case %d:\n", ++cas);  
        for(scanf("%d",&q);q--;)
        {
            char s[10];scanf("%s",s);
            if(s[0]=='A'){ int x,y;scanf("%d %d",&x,&y);printf("%I64d\n",ans[x+y]); }
            else
            {
                int x;scanf("%d",&x);
                color[x]^=1;//先取反
                if(super[x])
                 {
                    for(int i = 0; i < 2; i++)//点的状态改变,将答案中的与点有关的部分转移
                    {  
                        ans[(color[x] ^ 1) + i] -= sum[x][i];  
                        ans[color[x] + i] += sum[x][i];  
                    }  
                }
                else
                {
                    for(int i=head[x][0];i;i=nxt[i])//普通点暴力更新答案
                    {
                        ans[(color[x]^1)+color[v[i]]]-=w[i];
                        ans[color[x]+color[v[i]]]+=w[i];
                    }
        
                }
                for(int i=head[x][1];i;i=nxt[i])//暴力修改与点相连的超级点sum
                {
                    sum[v[i]][color[x]^1]-=w[i];
                    sum[v[i]][color[x]]+=w[i];
                }
            }
        }
    }
    return 0;
}

转载于:https://www.cnblogs.com/chendl111/p/7091351.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值