loli的毒瘤hu测 T2&&T3

T2.tree(矩阵树定理+gauss)

这里写图片描述
这里写图片描述

分析:
这道题的爆搜真的恶心。。。
可以放出来吓吓人:

const ll p=1e9+7;
const int N=100005;
struct node{
    int x,y;
};
node t[N][31];
int n,m,Q[N],deep[N],tot=0;
ll ans=0;
map<ll,int> mp;

int fa[50];

int find(int x) {
    if (fa[x]!=x) fa[x]=find(fa[x]);
    return fa[x];
}

int pd(int x) {                         //并查集判断是否是一棵树
    for (int i=1;i<=n;i++) fa[i]=i;
    for (int i=1;i<n;i++) {
        int f1=find(t[x][i].x);
        int f2=find(t[x][i].y);
        if (f1==f2) return 0;
        fa[f1]=f2;
    }
    return 1;
}

int P[50],du[50],a[50];
struct node2{
    int y,nxt;
};
node2 e[100];
int st[50],tet=0;
bool vis[N];

void prufer(int x) {                  //prufer+hash
    int o=0;
    for (int i=1;i<=n;i++) du[i]=0,st[i]=0,vis[i]=0; tet=0;
    for (int i=1;i<n;i++) {
        int u=t[x][i].x;
        int w=t[x][i].y;
        du[u]++; du[w]++;
        tet++; e[tet].y=w; e[tet].nxt=st[u]; st[u]=tet;
        tet++; e[tet].y=u; e[tet].nxt=st[w]; st[w]=tet;
    }
    for (int o=1;o<=n-2;o++) {
        int now=0;
        for (int i=1;i<=n;i++) if (du[i]==1&&!vis[i]) {now=i;break;}
        vis[now]=1;
        for (int i=st[now];i;i=e[i].nxt) 
            if (!vis[e[i].y]) {
                du[e[i].y]--;
                a[o]=e[i].y; 
            }
    }
}

int hash(int x) {
    prufer(x);
    ll t=0;
    for (int i=1;i<=n-2;i++)
        t=t*(ll)131+a[i];
    if (mp[t]) return 0;
    mp[t]=1;
    return 1;
}

void bfs() {
    int tou,wei;
    tou=wei=0;
    hash(1);                      //原树
    Q[++wei]=1; deep[wei]=0;
    while (tou<wei) {
        tou=(tou+1)%N;
        int now=tou;
        for (int i=1;i<n;i++) {
            node way=t[now][i];
            for (int x=1;x<=n;x++)
                for (int y=x+1;y<=n;y++) {
                    if ((x==way.x&&y==way.y)||(x==way.y&&y==way.x)) continue;
                    t[now][i].x=x; t[now][i].y=y;
                    if (pd(now)) {
                        if (hash(now)) {
                            if (deep[now]+1<m) {
                                wei=(wei+1)%N;
                                for (int i=1;i<n;i++) t[wei][i]=t[now][i];
                                deep[wei]=deep[now]+1;
                            }
                            ans++; ans%=p;
                        }
                    }
                }
            t[now][i]=way;
        }
    }
}
10%保证k=n-1

显然把原树修改成某棵数需要的最小步数就是在新树种且不再原树中的边数
那么当k=n-1时,其实就是要算n个点的有编号无根树数量
有编号无根树?Prufer编码!
那么答案就是 nn2 n n − 2

20%保证k=n-2

当k=n-2时,我们只需要计算出需要的最小步数恰好为 n1 n − 1 的树的个数就好了
也就是说新树的所有边都不在原树中,可以直接用矩阵树定理计算

70%

矩阵树定理计算的是每个生成树的树边权值之积的和
我们可以把原树上的边权值设为1,不在原树上的边权值设为x
那么跑矩阵树定理就会得到一个多项式, xk x k 前的系数就是恰好有 k k 条不再原树上的边的树的个数了
时间复杂度O(n5)

100%

70%算法的瓶颈主要在于求行列式的时候要做多项式乘法
考虑带n个x的值进去跑矩阵树定理,得到n个点值之后再差值求出原多项式即可
时间复杂度 O(n4) O ( n 4 )

tip

给出舒老师的标程
路过的还不快来%?

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define ll long long
#define o 85
using namespace std;
const ll Mod=1e9+7;
ll ans[o];
int n,K,a[o];
ll A[o][o],F[o][o],G[o][o],B[o];
ll Abs(ll a){
    return a<0?-a:a;
}
ll ksm(ll a,ll b,ll p){
    ll answer=1;
    for(;b;b>>=1,a=a*a%p) if(b&1) answer=answer*a%p;
    return answer;
}
ll Calc(int n){ 
    ll answer=1;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            ll A=F[i][i],B=F[j][i];
            while(B){
                ll t=A/B;A%=B;swap(A,B);
                for(int k=i;k<=n;k++) F[i][k]=(F[i][k]-F[j][k]*t%Mod+Mod)%Mod;
                for(int k=i;k<=n;k++) swap(F[i][k],F[j][k]);
                answer=-answer; 
            } 
        }
        if(!F[i][i])return 0;
        answer=answer*F[i][i]%Mod;
    }
    return (answer%Mod+Mod)%Mod;
}
void GS(int n){
    for(int i=0;i<=n;i++){
        int k=i;
        for(int j=i+1;j<=n;j++) if(Abs(G[k][i])<Abs(G[j][i])) k=j;
        for(int j=0;j<=n;j++) swap(G[i][j],G[k][j]); swap(B[i],B[k]);
        ll inv=ksm(G[i][i],Mod-2,Mod);
        for(int j=0;j<=n;j++) G[i][j]=G[i][j]*inv%Mod; B[i]=B[i]*inv%Mod;
        for(int j=0;j<=n;j++){
            if(i==j||!G[j][i])continue;
            ll t=G[j][i];
            for(int k=0;k<=n;k++) G[j][k]=(G[j][k]-G[i][k]*t%Mod+Mod)%Mod;
            B[j]=(B[j]-B[i]*t%Mod+Mod)%Mod;
        }
    }
    for(int i=0;i<=n;i++) ans[i]=(B[i]*ksm(G[i][i],Mod-2,Mod)%Mod+Mod)%Mod;
}
int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    scanf("%d%d",&n,&K);
    for(int i=2;i<=n;i++){
        scanf("%d",&a[i]);a[i]++;
        A[i][a[i]]=A[a[i]][i]=1;
    } 
    for(ll t=0;t<=n;t++){
        ll xi=t+1,zoom=1;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++) F[i][j]=!A[i][j]? -xi:-A[i][j];
            F[i][i]=0;
            for(int j=1;j<=n;j++) if(i!=j) F[i][i]-=F[i][j];
        }
        B[t]=Calc(n-1);
        for(int i=0;i<=n;i++,zoom=zoom*xi%Mod) G[t][i]=zoom;
    }
    GS(n);
    ll answer=0;
    for(int i=0;i<=K;i++) answer=(answer+ans[i])%Mod;
    answer=(answer%Mod+Mod)%Mod;
    printf("%lld\n",answer);
    return 0;
}

T3.sequence

这里写图片描述
这里写图片描述

分析:

解法一(50%)

我们对每个查询区间维护的一个集合,按照全职从小到大一次把每个元素插入所有包含ta的查询集合中
由于是升序插入的我们只需要知道每个集合中插入顺序相邻的每两个元素的差的最小值
可以把查询区间看做二维平面上的一个点,然后用KDtree维护
时间复杂度 O(nlogn+nm) O ( n l o g n + n m )

解法二(20~50%)

直接用莫队算法,维护一个平衡树来查询前驱后继,再用一个堆维护差值最小值
假设莫队是按 l/k l / k 分组的,那么时间复杂度为 O((n2k+mk)logn) O ( ( n 2 k + m k ) l o g n ) ,取 k=mm k = m m 时可以得到最优复杂度 O((nm)logn) O ( ( n m ) l o g n )

解法三(50~70%)

考虑优化解法二,瓶颈在于需要查毒,如果只删除的话,维护链表就可以做到 O(1) O ( 1 ) 查询前驱后继了
假设当前这组询问的左端点都在 [x,y] [ x , y ] 之间
我们可以先用链表对每个 r r 求出ar在区间 [y,r] [ y , r ] 的前驱后继
那么接下来对每个询问 [l,r] [ l , r ] ,我们需要对 i=l..y i = l . . y 求出 ai a i 在区间 [i,r] [ i , r ] 内的前驱后继
我们把同组的询问按照右端点从大到小排序,然后同样维护一个链表,那么右端点可以直接删,左端点我们每次删到 y y 的位置,求出答案后暴力撤销回去即可
时间复杂度O(nm)

解法六(50%)

我们对每个 i i ,考虑有哪些j满足 j>i,aj>=ai j > i , a j >= a i ,并且 ajai a j − a i 可能成为答案( aj<ai a j < a i 的用同样方法再做一遍就可以了)
假设我们现在找到了某个可能的点对 (i,j) ( i , j ) ,下一个可能的 j'(j'>j) j ′ ( j ′ > j ) 必须满足 aj'<aj a j ′ < a j
也就是说我们是要找 i i 后面的一个不小于ai的递减序列
由于数据随机,这个递减序列的长度是期望 O(logw) O ( l o g w )
因此我们可以先找出这样的点对,然后用离线树状数组做一个二维数点就好了
寻找点对显然可以使用数据结构解决,不过还有一种更简洁的做法
fi,j f i , j 表示在 i i 后面且不小于ai的递减序列的第 j j 项,类似地设gi,j表示在 i i 后面且小于ai的递增序列的第 j j
p=fi,j1,可以发现 fi,j f i , j 就是 gp g p 中不小于 ai a i 且最靠前的一项
因此我们就可以直接递推求出 f f g
随机数据下时间复杂度为 O((nlogw+m)logn) O ( ( n l o g w + m ) l o g n )

解法七(100%)

我们延续解法六的思路,尝试能否找出一些不依赖数据随机的性质
实际上,我们寻找下一个可能的 j' j ′ 的时候,还需要满足 aj'ai<ajaj' a j ′ − a i < a j − a j ′
否则 (j,j') ( j , j ′ ) 就会优于 (i,j') ( i , j ′ )
可以发现,每找到一个新的 j j ajai就会减小至少一半,因此即便在非随机数据下可能的点对最多也只有 O(nlogw) O ( n l o g w )
寻找点对的过程中需要查询 [j+1..n] [ j + 1.. n ] 内第一个 ai<ap<ai+aj2 a i < a p < a i + a j 2 p p
这个可以用可持久化线段树解决,也可以把元素从大到小插入线段树来做
时间复杂度O((nlogw+m)logn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值