Codeforces Gym 101234D Forest Game

177 篇文章 1 订阅
87 篇文章 1 订阅

我们枚举两个点,考虑它们之间的贡献。如果 u v之间的距离为 d ,那么u v 1的贡献当且仅当这条路径上 v 被第一个删除,概率是1d+1。因此我们只需要求出这些点两两之间的距离。点分治的时候用FFT计算就可以了,复杂度 O(nlog2n)

#include<cstdio>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
#define LL long long
const int maxn=400010,p=1000000007;
const double pi=acos(-1);
struct Complex
{
    double a,b;
    Complex operator + (const Complex &c) const
    {
        return (Complex){a+c.a,b+c.b};
    }
    Complex operator - (const Complex &c) const
    {
        return (Complex){a-c.a,b-c.b};
    }
    Complex operator * (const Complex &c) const
    {
        return (Complex){a*c.a-b*c.b,a*c.b+b*c.a};
    }
    Complex operator / (const double &x) const
    {
        return (Complex){a/x,b/x};
    }
}f[maxn],g[maxn],w[maxn];
vector<int> to[maxn];
int size[maxn],val[maxn],vis[maxn],cnt[maxn],c1[maxn],tot[maxn],rev[maxn],
n;
int inc(int x,int y)
{
    x+=y;
    return x>=p?x-p:x;
}
int dec(int x,int y)
{
    x-=y;
    return x<0?x+p:x;
}
int pow(int b,int k)
{
    int ret=1;
    for (;k;k>>=1,b=(LL)b*b%p)
        if (k&1) ret=(LL)ret*b%p;
    return ret;
}
int dfs2(int u,int fa,int S)
{
    int ret=-1,v;
    size[u]=1;
    val[u]=0;
    for (vector<int>::iterator it=to[u].begin();it!=to[u].end();++it)
        if (!vis[*it]&&*it!=fa)
        {
            v=dfs2(*it,u,S);
            if (ret==-1||val[v]<val[ret]) ret=v;
            size[u]+=size[*it];
            val[u]=max(val[u],size[*it]);
        }
    val[u]=max(val[u],S-size[u]);
    if (ret==-1||val[u]<val[ret]) ret=u;
    return ret;
}
void dfs3(int u,int fa)
{
    size[u]=1;
    for (vector<int>::iterator it=to[u].begin();it!=to[u].end();++it)
        if (!vis[*it]&&*it!=fa)
        {
            dfs3(*it,u);
            size[u]+=size[*it];
        }
}
int dfs4(int u,int fa,int d)
{
    int ret=d;
    c1[d]++;
    for (vector<int>::iterator it=to[u].begin();it!=to[u].end();++it)
        if (!vis[*it]&&*it!=fa)
            ret=max(ret,dfs4(*it,u,d+1));
    return ret;
}
void fft(Complex *a,int m,int t,int flag)
{
    int x;
    Complex t1,t2;
    for (int i=0;i<m;i++)
        if (rev[i]>i) swap(a[i],a[rev[i]]);
    for (int i=0;i<t;i++)
        for (int j=0;j<m;j+=1<<(i+1))
        {
            x=0;
            for (int k=j;k<j+(1<<i);k++)
            {
                t1=a[k];
                t2=a[k+(1<<i)]*w[x];
                a[k]=t1+t2;
                a[k+(1<<i)]=t1-t2;
                x+=flag*(1<<(t-i-1));
                if (x<0) x+=m;
            }
        }
    if (flag==-1)
        for (int i=0;i<m;i++) a[i]=a[i]/m;
}
void dfs1(int u,int S)
{
    int mx=0,m1,l,t,r;
    for (int i=1;i<=S;i++) cnt[i]=0;
    cnt[0]=1;
    r=dfs2(u,-1,S);
    vis[r]=1;
    dfs3(r,-1);
    for (vector<int>::iterator it=to[r].begin();it!=to[r].end();++it)
        if (!vis[*it])
        {
            for (int i=0;i<=size[*it];i++) c1[i]=0;
            m1=dfs4(*it,r,1);
            l=1,t=0;
            while (l<=2*m1) l<<=1,t++;
            for (int i=0;i<=m1;i++) f[i]=(Complex){(double)c1[i],0.0};
            for (int i=m1+1;i<l;i++) f[i]=(Complex){0,0};
            for (int i=0;i<l;i++) w[i]=(Complex){cos(2*pi*i/l),sin(2*pi*i/l)};
            for (int i=0;i<l;i++)
            {
                rev[i]=0;
                for (int j=0;j<t;j++)
                    rev[i]|=((i>>j)&1)<<(t-j-1);
            }
            fft(f,l,t,1);
            for (int i=0;i<l;i++) f[i]=f[i]*f[i];
            fft(f,l,t,-1);
            for (int i=0;i<l;i++) tot[i]=dec(tot[i],((LL)(f[i].a+0.5))%p);
            for (int i=0;i<=m1;i++) cnt[i]+=c1[i];
            mx=max(mx,m1);
        }
    l=1,t=0;
    while (l<=2*mx) l<<=1,t++;
    for (int i=0;i<=mx;i++) f[i]=(Complex){(double)cnt[i],0.0};
    for (int i=mx+1;i<l;i++) f[i]=(Complex){0,0};
    for (int i=0;i<l;i++) w[i]=(Complex){cos(2*pi*i/l),sin(2*pi*i/l)};
    for (int i=0;i<l;i++)
    {
        rev[i]=0;
        for (int j=0;j<t;j++)
            rev[i]|=((i>>j)&1)<<(t-j-1);
    }
    fft(f,l,t,1);
    for (int i=0;i<l;i++) f[i]=f[i]*f[i];
    fft(f,l,t,-1);
    for (int i=0;i<l;i++) tot[i]=inc(tot[i],((LL)(f[i].a+0.5))%p);
    for (vector<int>::iterator it=to[r].begin();it!=to[r].end();++it)
        if (!vis[*it]) dfs1(*it,size[*it]);
}
int main()
{
    //freopen("g.in","r",stdin);
    int ans=0,x,y;
    scanf("%d",&n);
    for (int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        to[x].push_back(y);
        to[y].push_back(x);
    }
    dfs1(1,n);
    for (int i=0;i<n;i++)
        ans=inc(ans,(LL)tot[i]*pow(i+1,p-2)%p);
    for (int i=2;i<=n;i++) ans=(LL)ans*i%p;
    printf("%d\n",ans);
}
CodeForces - 616D是一个关于找到一个序列中最长的第k好子段的起始位置和结束位置的问题。给定一个长度为n的序列和一个整数k,需要找到一个子段,该子段中不超过k个不同的数字。题目要求输出这个序列最长的第k好子段的起始位置和终止位置。 解决这个问题的方法有两种。第一种方法是使用尺取算法,通过维护一个滑动窗口来记录\[l,r\]中不同数的个数。每次如果这个数小于k,就将r向右移动一位;如果已经大于k,则将l向右移动一位,直到个数不大于k。每次更新完r之后,判断r-l+1是否比已有答案更优来更新答案。这种方法的时间复杂度为O(n)。 第二种方法是使用枚举r和双指针的方法。通过维护一个最小的l,满足\[l,r\]最多只有k种数。使用一个map来判断数的种类。遍历序列,如果当前数字在map中不存在,则将种类数sum加一;如果sum大于k,则将l向右移动一位,直到sum不大于k。每次更新完r之后,判断i-l+1是否大于等于y-x+1来更新答案。这种方法的时间复杂度为O(n)。 以上是两种解决CodeForces - 616D问题的方法。具体的代码实现可以参考引用\[1\]和引用\[2\]中的代码。 #### 引用[.reference_title] - *1* [CodeForces 616 D. Longest k-Good Segment(尺取)](https://blog.csdn.net/V5ZSQ/article/details/50750827)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [Codeforces616 D. Longest k-Good Segment(双指针+map)](https://blog.csdn.net/weixin_44178736/article/details/114328999)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^koosearch_v1,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值