BZOJ.3498.[PA2009]Cakes(三元环 枚举)

题目链接

感觉我可能学的假的(复杂度没问题,但是常数巨大)。
一个比较真的说明见这儿:https://czyhe.me/blog/algorithm/3-mem-ring/3-mem-ring/


\(Description\)

n个点m条边的无向图,每个点有点权。对于任意一个三元环\((i,j,k),i<j<k\),其贡献为\(max\{a_i,a_j,a_k\}\)。求所有三元环的贡献和。


一般的三元环计数问题:根据出度是否\(\leq\sqrt m\)将点分为两类。
对于\(dgr[x]\leq \sqrt{m}\)的点x,枚举x的任意两条出边,判断这两条出边的端点间是否有边(map/set/邻接表hash)。因为一条边最多被起点枚举\(\sqrt m\)次,最多枚举\(m\)条边,所以复杂度不超过\(O(m\sqrt{m})\)(无向图的话计算过的点不再计算了)。
对于\(dgr[x]>\sqrt{m}\)的点x,三个三个枚举这些点,暴力判断之间是否有边。因为最多只有\(\sqrt m\)个这样的点,so复杂度是\(O((\sqrt m)^3)=O(m\sqrt m)\)


\(Solution1\)

对于本题,我们消除取max的限制。对于原图中的一条边u<->v,若val[u]<val[v],则连边v->u;否则连边u->v。(相等的话一样吧)
然后我们从val最大的点遍历新图(因为是有向图,so不会重复)。枚举当前点的出边x->v,若\(dgr[v]\leq\sqrt m\),则枚举v的出边,判断是否与x相邻;否则枚举x的出边v',判断是否与v相邻。
判断是判断v是否与v'有连边,而不是v'与v有边!因为若v'度数小会只枚举其出边,比如如果按后者判断,v->v'有边,枚举v时不算答案;而v'是枚举其出边,并不包括v。这个贡献就没了。so都统一计出边答案就行了。
(可以在此之前枚举x的出边x->v,对v打标记,就可以知道某个点是否与x相邻)
复杂度不知道。

???卡数组邻接表还行,T了三遍。

//13948kb   5904ms
#include <set>
#include <cmath>
#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 250000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
const int N=1e5+5,M=250005;

int n,m,/*Enum,H[N],nxt[M],to[M],*/Link[N],dgr[N];
std::set<int> s[N];
std::vector<int> vec[N];
char IN[MAXIN],*SS=IN,*TT=IN;
struct Node{
    int val,id;
    bool operator <(const Node &a)const{
        return val>a.val;
    }
}A[N];

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline void AddEdge(int u,int v){
    ++dgr[u], vec[u].push_back(v);
//  ++dgr[u], to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
}

int main()
{
    n=read(), m=read();
    for(int i=1; i<=n; ++i) A[i]=(Node){read(),i};
    for(int i=1,u,v; i<=m; ++i)
        u=read(), v=read(), A[u].val>A[v].val?(AddEdge(u,v),s[u].insert(v)):(AddEdge(v,u),s[v].insert(u));//set连双向/单向无所谓了,反正连通是单向的(不过这样快啊)
    std::sort(A+1,A+1+n);
    long long ans=0;
    for(int i=1,x,val,size=sqrt(m); i<=n; ++i)
    {
        x=A[i].id, val=A[i].val;
        const std::vector<int> &X=vec[x];
        int l=X.size();
        for(int j=0,l=X.size(); j<l; ++j) Link[X[j]]=x;
        for(int j=0,l=X.size(),v; j<l; ++j)
        {
            v=X[j];
            if(dgr[v]<=size){
                for(int k=0,l2=vec[v].size(); k<l2; ++k)
                    if(Link[vec[v][k]]==x) ans+=val;
            }
            else{
                for(int k=0; k<l; ++k)
                    if(s[v].count(X[k])) ans+=val;//not s[to[k]].count(v)!!!
            }
        }
//      for(int j=H[x]; j; j=nxt[j]) Link[to[j]]=x;
//      for(int j=H[x],v; j; j=nxt[j])
//      {
//          v=to[j];
//          if(dgr[v]<=size){
//              for(int k=H[v]; k; k=nxt[k])
//                  if(Link[to[k]]==x) ans+=val;
//          }
//          else{
//              for(int k=H[x]; k; k=nxt[k])
//                  if(s[v].count(to[k])) ans+=val;//if(s[to[k]].count(v)) ans+=val;//WA
//          }
//      }
    }
    printf("%lld",ans);

    return 0;
}

\(Solution2\)

给定\(n\)个点\(m\)条边的图,对于每一条边,枚举两个端点中度数较少的端点的邻接点,判断是否构成三元环。这种做法的判断次数在\(m\)条边形成一个完全图时达到最大,即\(O(m\sqrt m)\)http://tieba.baidu.com/p/4524107534
对于任意两条边,我们可以直接枚举度数较小的。
然而这样写怎么TLE了。。本身复杂度不小加每次一定带个set的log?

//TLE
#include <set>
#include <cstdio>
#include <cctype>
#include <vector>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 250000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
const int N=1e5+5,M=250005;

int n,m,val[N],Link[N],dgr[N];
std::set<int> s[N];
std::vector<int> vec[N];
char IN[MAXIN],*SS=IN,*TT=IN;

inline int read()
{
    int now=0;register char c=gc();
    for(;!isdigit(c);c=gc());
    for(;isdigit(c);now=now*10+c-'0',c=gc());
    return now;
}
inline void AddEdge(int u,int v){
    ++dgr[u], vec[u].push_back(v), s[u].insert(v);
}
long long Calc(int x,int y)
{
    const std::vector<int> &X=vec[x];
    long long res=0; int v=std::max(val[x],val[y]);
    for(int i=0,l=X.size(); i<l; ++i) if(s[y].count(X[i])) res+=std::max(v,val[X[i]]);
    return res;
}

int main()
{
    n=read(), m=read();
    for(int i=1; i<=n; ++i) val[i]=read();
    for(int i=1,u,v; i<=m; ++i)
        u=read(), v=read(), val[u]>val[v]?AddEdge(u,v):AddEdge(v,u);//不建有向图的话答案/3就行了 
    long long ans=0;
    for(int x=1; x<=n; ++x)
    {
        const std::vector<int> &X=vec[x];
        for(int j=0,l=X.size(); j<l; ++j)
            ans+=dgr[x]<dgr[X[j]]?Calc(x,X[j]):Calc(X[j],x);
    }
    printf("%lld",ans);

    return 0;
}

转载于:https://www.cnblogs.com/SovietPower/p/9306259.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值