CF 1220 D数论E图论/tarjan/dfs

3 篇文章 0 订阅
1 篇文章 0 订阅

D Alex and Julian

小a拿到一个集合就会把这里面的数当作步长, 在图上对编号之差为合法步长的点连边. 这样就会连无数条边. 问题是现在给出一个集合, 保留其中尽量多的数, 保证交给小a之后他会生成一个二分图.
很简单的分析之后我们就可以发现, 当只保留奇数作为步长的话, 是一定能构成二分图的. 因为从同一个点开始出发, 两个奇数想要再次相遇需要各跳奇数下. 而这样构成的环肯定是偶环, 这个图就会是二分图.
所以说只留奇数是可以的, 那么有没有其他合法更优解呢
那么我们试着找找只留奇数会有怎样的性质, 看看能不能将其推广.
我们发现, 两个不同的步长再次重合的位置是lcm处, 也就是说a跳lcm(a.b)/a下, b跳lcm(a,b)/b下就会重合, 那么我们就是要让lcm(a.b)/a和lcm(a.b)/b这两个数相加是偶数, 那么这个环就是偶环. 而要使两个数相加是偶数, 要么两个都是偶数, 要么两个都是奇数.
lcm(a,b)/a=a/gcd(a,b) lcm(a,b)/b=b/gcd(a,b), 显然除了gcd之后, 两个数肯定不可能都是偶数, 所以不存在都是偶数的情况.
那么我们只要思考, 满足什么样的性质的一组数, 两两之间除了gcd之后, 都是奇数呢?
显然gcd中包含多少个奇数因子是不重要的, 那么什么是重要的呢?
对, 是有多少个偶数! 确切的说, 是有多少个2! 如果这两个数的因子中2的个数不同, 那么这两个数和显然算出的a/gcd(a,b) a/gcd(a,b)会是一奇一偶!
所以只要保证这组数每个数含2的幂次相同即可. 所以, 简单地将所有的数按幂次分组, 取最大的组即可.

E Tourism

给出一个图, n(2e5)个点m(2e5)条边, 点上有权值, 从s点出发, 经过的点会贡献其权值, 重复经过只算1次, 每条边都可以重复走, 但前提是不能马上走刚刚走过的这条边. 问能取到的最大权值和是多少.
画一画就会发现, 如果有环的话, 这个环就能让你调转车头, 原路返回; 相反如果走到了一个死胡同, 就只能一头走到黑了.
所以说, ①所有环上的所有点, 都总能走到然后回到任意位置; ②所有的死胡同只能挑一个价值最大的走, 然后撞死在胡同里.
③此外, 从起点到环的桥, 环和环之间的桥(如果有的话), 也能够直接取得.
那么tarjan缩点把所有的环染色, 然后再dfs将③中提及的桥上的点染色, 这些染色的点是必会取到的贡献.
再一遍dfs对于每个死胡同算长度, 挑一个最大的就行了.
两遍dfs还是很优雅的, 可以再仔细看看

代码:

int n,m,s;
ll w[M];
int col[M];
int vis[M];

int stk[M];
int si=0;


// 第一遍dfs, 染起点到环以及环间的桥.
void dfs(int now,int fa) {
    if(col[now]==0)// 放入临时空间
        stk[si++]=now;
    else {
        for(int i=0; i<si; i++) {
            int tmp=stk[i];
            col[tmp]=1;
        }
        si=0;
    }

    // 注意判vis的位置
    if(vis[now])
        return;
    vis[now]=1;

    for(int i=head[now]; i!=-1; i=E[i].nxt) {
        int v=E[i].v;
        if(v==fa)
            continue;
        dfs(v,now);
    }
    if(si!=0)// 如果没被取出, 那么就是这个点染不到(*注意, 这里在递归dfs之后)
        si--;
}

ll tail=0;
ll ttt=0;// 临时变量

// 第二遍dfs, 寻找最大的死胡同(更新tail)
void dfs2(int now,int fa) {
    if(col[now]==0) {
        stk[si++]=now;
        if(vis[now]==0)
            ttt+=w[now];
    }

    if(vis[now]) {
        return;
    }
    vis[now]=1;

    for(int i=head[now]; i!=-1; i=E[i].nxt) {
        int v=E[i].v;
        if(v==fa)
            continue;
        dfs2(v,now);
    }
    if(col[now]==0) {
        checkMax(tail,ttt);
        if(si!=0) {
            ttt-=w[stk[si-1]];
            si--;
        }
    }
}


void init() {
    scanf("%d%d",&n,&m);
    init(n);
    for(int i=1; i<=n; i++)
        w[i]=read();
    for(int i=0; i<m; i++) {
        int u=read(),v=read();
        add(u,v);
        add(v,u);
    }
    scanf("%d",&s);
    solve(n);

    for(int i=1; i<=n; i++) {
        if(ver[id[i]].size()>=3)
            col[i]=1;
    }

    dfs(s,-1);
    si=0;
    memset(vis,0,sizeof(vis));
    dfs2(s,-1);

    ll ans=0;
    for(int i=1; i<=n; i++) {
        if(col[i])
            ans+=w[i];
    }
    printf("%I64d\n",ans+tail);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值