[清橙A1203]珠链分割-二分-树形DP

珠链分割

试题来源

  2010中国国家集训队命题答辩

问题描述

  Antonio制作了一串非常大的珠链。这串珠链由N颗珠子构成,并通过N条细线串连起来。每条细线能够串起两颗不同的珠子,而且任意两颗不同的珠子之间最多只会有一条细线相连。与此同时,这串珠链还满足一个性质,那就是从任意一颗珠子出发,通过一条或者多条细线,可以到达其中所有的珠子。

  Antonio意识到在它的珠链中,有的节点通过若干条细线还能够走回本身(同一条细线不能经过两次)。所有满足这个性质的节点的集合被称为环,这些节点本身被称作环上的节点。如在右图中,红色标记的节点的集合就构成这个图的环,在这个图中共有6个环上的节点。
  现在,Antonio准备把他的珠链进行一下分割。首先,他给每一颗珠子都赋上了一个1~100(含)的正整数,代表这颗珠子的价值。然后,他通过下面的步骤进行分割:
  1、 切断若干条细线,把整个珠链分割成K个部分。每个部分都满足连通性(i.e. 从该部分内任意一个节点出发通过一条或者多条细线可以到达该部分中的所有其他节点)。
  2、 选择K个部分中的一个,把这个部分中所有环上的节点的价值分别平方。
  然后,Antonio会把这K个部分送给他的K个好友,且每个好友的满意度等于他/她收到的所有珠子的价值的总和。Antonio希望每个好友都能对他的礼物充分满意,所以他希望最大化满意度最低的好友的满意度。请你设计一个程序,向Antonio指出这个值。

输入格式

  由于输入规模可能会很大,故采用两种模式进行输入。
  
  输入模式A:
  输入文件第一行有且仅有一个字母A,表示用A模式进行输入。
  第二行,两个用空格隔开的正整数,N和K。含义如试题描述中所述。
  接下来一行,N个用空格隔开的正整数,均不超过100,表示每个珠子的价值。
  接下来N行,每行两个整数,代表细线连接的两颗珠子的编号。珠子按照第三行输入的顺序从1到N进行编号。

  输入模式B:
  输入文件第一行有且仅有一个字母B,表示用B模式进行输入。
  第二行,两个用空格隔开的正整数,N和K。含义如试题描述中所述。
  在输入模式B中,图的结构是固定的。所有的节点按照1~N依次编号,而细线则为(1,2), (2,3), …, (N-1,N), …, (N,1),即一个首尾相连的大环。
  第三行,四个正整数,V,A,B,C,每个数都不超过109. 依照下列规则产生一个长度为N的数列:
  P1=V mod C
  Pj=(APj-1+B)mod C,(1 < j ≤ N)
  然后通过P数列确定每个珠子的价值。编号为j的珠子的价值

  Value[j]=(pj mod 97)+1, (1 ≤ j ≤ N)
  (注意:你必须要使用64位整数来产生P数列。对于Pascal选手来说,你必须使用int64类型,对于C++选手来说,你必须使用long long类型)
  另外,建议C++选手使用scanf()进行读入,以减小读入过程的时间消耗。
  

输出格式

  输出文件有且仅有一行,只包含一个整数,表示最小的满意度所可能取到的最大值。输入数据保证这个数不超过231-1。
  

样例输入

A
6 4
12 2 2 2 12 12
1 2
2 3
3 4
4 2
3 5
4 6

样例输出

12

样例输入

A
5 3
8 8 2 1 1
1 2
2 3
3 1
3 4
3 5

样例输出

6

样例输入

B
5 2
1 1 1 10000000000

样例输出

16

样例输入

B
100 10
1039203 4398234 3930493 1000000003

样例输出

542

数据规模和约定

N<=300000,K<=10000

样例说明

hint

特别提醒

  只有一个部分中节点的权值可以被平方,请仔细观察样例3。
  只有环上的节点的权值才能够被平方,请仔细观察样例2。
  节点需要分别被平方,请仔细观察样例1。


出题人真是个人才……
不说了放张图
令人悲伤
(XZK为本机房一dalao,详见左侧友链第一个)


思路:
首先二分答案。
设二分值为 lim l i m 问题变成判断是否存在合法方案,使得整棵基环树能分成 k k 个块。

简化问题,考虑是棵树时怎么做。
注意树的情况下是无法平方的。
考虑树形dp,记f[u]表示节点 u u 及其子树能分成的最多的块数,g[u]表示在分块方案为 f[u] f [ u ] 的情况下当前和 u u 相连的、未被划入任何一块的所有点的点权和。

可以发现转移方程:
f[u]=vch[u]f[v]
g[u]=vch[u]g[v] g [ u ] = ∑ v ∈ c h [ u ] g [ v ]
并且在每个点结束子树的转移后,还有一条方程:
f[u]+=1,g[u]=0    if g[u]lim f [ u ] + = 1 , g [ u ] = 0         i f   g [ u ] ≥ l i m
最后取出 f[root] f [ r o o t ] 作为答案即可~

考虑多了一个环如何做。
依旧先不考虑平方。

考虑把环单独取出,展开成一条链并倍长。
对环上的每个点的不属于环的子树做一次树形dp。
注意,当处理到环上的节点自身,也就是一次树形dp的根节点时,不使用最后一条方程,即 g[u]lim g [ u ] ≥ l i m 时生效的方程。
然后,把环上每个节点的 g[u] g [ u ] 当做点权。
于是问题变成了,是否存在方案把这个环分成 kk=kf[u] k k = k − ∑ f [ u ] 份。

从左到右,对于每个点 l l ,预处理出右边最近的一个满足i=lrg[i]lim的位置 r r ,并令fa[l] r+1 r + 1
这样做的意思是,如果从 l l 开始,贪心地选取一个合法块,那么下一个可以选择的最近的块的起始位置为r+1

接着,枚举一个 l l ,找到最近的满足i=lrg[i]lim的位置 r r 。选择这个块。
那么现在还需要kk1块,考虑对刚才记录的 fa f a 数组做一个倍增,从 r+1 r + 1 开始跳 kk1 k k − 1 步,跳到的位置正好是贪心地选取最短的 kk1 k k − 1 个块后下一个块开头的位置。
令这个位置为 pos p o s ,同时令环长为 len l e n
pos<=l+len p o s <= l + l e n ,即 pos p o s l l 之间的距离不超过一个环长,那么便是找到了一个合法方案。

考虑存在平方时的做法。
只需要把最后枚举的l r r 的限制改成i=lrg[i]+(v[i]1)v[i]lim就可以了!

复杂度 O(nlogAnslogn) O ( n l o g A n s l o g n )
另外这题由于有 O(nlogAns) O ( n l o g A n s ) 的做法,因此需卡常.
咱是通过提交试了一个较小的二分上界,就跑过去了~

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<algorithm>

using namespace std;

#pragma GCC optimized("O2")
typedef long long ll;
const int N=300009;
const int K=21;

int n,k,len,len2;
int to[N<<1],nxt[N<<1],beg[N],tot;
int fa[K][N<<1],stk[N],ring[N<<1],top;
bool vis[N],onring[N];
int v[N],f[N],g[N],h[N];

inline void add(int u,int v)
{
    to[++tot]=v;
    nxt[tot]=beg[u];
    beg[u]=tot;
}

inline bool input()
{
    static char s[5];
    scanf("%s%d%d",s+1,&n,&k);
    if(s[1]=='A')
    {
        for(int i=1;i<=n;i++)
            scanf("%lld",&v[i]);
        for(int i=1,u,v;i<=n;i++)
        {
            scanf("%d%d",&u,&v);
            add(u,v);add(v,u);
        }
        return 1;
    }
    else
    {
        ll p,a,b,c;
        scanf("%lld%lld%lld%lld",&p,&a,&b,&c);
        p%=c;len=n;
        for(int i=1;i<=n;i++)
        {
            v[i]=(p%97)+1,p=(a*p+b)%c;
            ring[i]=i;onring[i]=1;
        }
        return 0;
    }
}

inline bool find(int u,int fa)
{
    vis[u]=1;stk[++top]=u;
    for(int i=beg[u];i;i=nxt[i])
        if(to[i]!=fa)
        {
            if(vis[to[i]])
            {
                while(top)
                {
                    onring[ring[++len]=stk[top]]=1;
                    if(stk[top]==to[i])break;top--;
                }
                return 0;
            }
            else if(!find(to[i],u))
                return 0;
        }
    vis[u]=0;top--;
    return 1;
}

inline void dfs(int u,int fa,int lim)
{
    f[u]=0;g[u]=v[u];
    for(int i=beg[u];i;i=nxt[i])
        if(to[i]!=fa && !onring[to[i]])
        {
            dfs(to[i],u,lim);
            f[u]+=f[to[i]];
            g[u]+=g[to[i]];
        }
    if(fa && g[u]>=lim)
        f[u]++,g[u]=h[u]=0;
    else if(fa==0)
        h[u]=g[u]+v[u]*(v[u]-1);
}

bool flag=0;

inline bool check(int lim)
{
    int kk=k;
    if(len==n && flag)goto pass;
    flag=1;
    for(int i=1;i<=len;i++)
    {
        dfs(ring[i],0,lim);
        kk-=f[ring[i]];
        if(kk<=0)return true;
    }
    pass:;

    ll sum=0;
    int *faa=fa[0];
    for(int l=1,r=0;l<=len2;sum-=g[ring[l]],l++)
    {
        while(r<len2 && sum<lim)
            r++,sum+=g[ring[r]];
        if(sum<lim)
        {
            memset(fa[0]+l,0,sizeof(int)*(len2-l+3));
            break;
        }
        if(r+1<=l+len)
            faa[l]=r+1;
        else
            faa[l]=0;
    }

    for(int i=1;i<K;i++)
        for(int j=1;j<=len2;j++)
            fa[i][j]=fa[i-1][fa[i-1][j]];

    sum=0;
    for(int l=1,r=0,pos=0;l<=len;sum-=h[ring[l]],l++)
    {
        while(r<len2 && sum<lim)
            r++,sum+=h[ring[r]];
        if(sum<lim)break;

        pos=r+1;
        for(int i=K-1;i>=0;i--)
            if((kk-1)&(1<<i))
                pos=fa[i][pos];
        if(pos && pos<=l+len)return true;
    }

    return false;
}

int main()
{
    if(input())find(1,0);
    for(int i=1;i<=len;i++)
        ring[i+len]=ring[i];
    len2=len*2;

    ll l=0,r=2000000ll,mid,ans=0;
    while(l<=r)
    {
        mid=l+r>>1ll;
        if(check(mid))
            ans=mid,l=mid+1ll;
        else
            r=mid-1ll;
    }

    printf("%lld\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值