【除草】Hackerrank思考题

23 篇文章 0 订阅
4 篇文章 0 订阅

matrix tree

矩阵aij,表示i和j的lca的点权值,询问矩阵的行列式

考虑首先将列按dfs序,依次开始高斯消元,假设是1-2-3,第一行第一列必定全都是w[1],因此可以直接消元,消完之后考虑2,此时所有跟2的lca为1的点必定不再2的子树中,假设是i号点,则a2i=ai2=w[1]-w[1]=0,因此这些行是不需要用2这一行去减的,而跟2的lca不为1的点,lca必定等于2,即a2j=aj2=w[2]-w[1],因此也可以直接减,此时考虑3,若3是2的兄弟,其实情况和2相同,若是2的子节点,那么所有lca(3,i)=1就有a3i=ai3=0,因为被1消过,却不受2的影响,而lca(3,i)=2的情况,在2消之前,a3i=ai3=w[2]-w[1],而a2i=ai2=w[2]-w[1],故2消过之后,a3i=ai3=0,情况又跟2的情况类似,如此可以归纳一下,每次都只用把子树所在的行减一次。

最后的结果可以观察主对角线就是w[1]*pi(w[i]-w[fa[i]])

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define next nex
const int mo=1000000007;
using namespace std;
long long ans;
int n,top;
long long w[500000];
int tail[500000],ss,v[500000],st[500000];
int next[2000000],sora[2000000];
void origin()
{
    ss=n;
    for (int i=1;i<=n;i++) tail[i]=i,next[i]=0;
}
void link(int x,int y)
{
    ++ss,next[tail[x]]=ss,tail[x]=ss,sora[ss]=y,next[ss]=0;
    ++ss,next[tail[y]]=ss,tail[y]=ss,sora[ss]=x,next[ss]=0;
}
void bfs(int s)
{
    int h,r,ne,na;
    h=r=0;
    st[r=1]=s,v[s]=1;
    ans=w[s];
    for (;h<r;) {
        ne=st[++h];
        for (int i=ne;next[i];) {
            i=next[i],na=sora[i];
            if (!v[na]) {
                v[na]=1;
                st[++r]=na;
                ans=(ans*(w[na]-w[ne]))%mo;
            }
        }
    }
}
void dfs(int x)
{
    v[x]=1;
    st[x]=++top;
    for (int i=x,ne;next[i];) {
        i=next[i],ne=sora[i];
        if (!v[ne]) ans=(ans*(w[ne]-w[x])%mo),dfs(ne);
    }
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%lld",&w[i]);
    origin();
    for (int i=1;i<=n-1;i++) {
        int x,y;
        scanf("%d%d",&x,&y);
        link(x,y);
    }
//    bfs(1);
    top=0;
    ans=w[1];
    dfs(1);
    for (int i=1;i<=n;i++) v[i]=0;
    for (int i=1;i<=n;i++)
        if (!v[i]) {
            int x=i;
            v[x]=1;
            if ((abs(x-st[x])&1)!=0) ans*=-1;
            for (x=st[x];x!=i;v[x]=1,x=st[x]) 
                if ((abs(x-st[x])&1)!=0) ans*=-1;
        }
    cout<<(ans+mo)%mo<<endl;
    return 0;
}

Esp

询问一列数,任选k个数的和的积。

观察一下其实就是求pi(1+ai*X)的每一项系数是多少。

直接分治成两半,用FFT合并即可。

一个实现细节就是FFT合并完之后要记得降次,否则会有越来越多前导零。

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <vector>
#include <cmath>
const int mo=100003;
const long double pi=acos(-1.0);
using namespace std;
struct Complex {
    long double x, y;

    inline Complex (long double real = 0, long double imag = 0) : x(real), y(imag) {}

    long double &real() {
        return x;
    }

    long double &imag() {
        return y;
    }
    
    void print()
    {
        cout<<"real="<<x<<" imag="<<y<<endl;
    }
}Pa[2000000],Pb[2000000],Pc[2000000];
int ans[500000];
vector <int> tmp;
int st[20][2][500000];
int n,q,a[2000000];
inline Complex operator+(const Complex &a, const Complex &b) {
    return Complex(a.x + b.x, a.y + b.y);
}

inline Complex operator-(const Complex &a, const Complex &b) {
    return Complex(a.x - b.x, a.y - b.y);
}

inline Complex operator*(const Complex &a, const Complex &b) {
    return Complex(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x);
}

inline void sincos(long double theta,long double &a,long double &b)
{
	a=sin(theta);
	b=cos(theta);
}

void FFT(Complex P[], int n, int oper)
{
    for (int i = 1, j = 0; i < n - 1; i++) {
		for (int s = n; j ^= s >>= 1, ~j & s;);
		if (i < j) {
			swap(P[i], P[j]);
		}
	}
	Complex unit_p0;
	for (int d = 0; (1 << d) < n; d++) {
		int m = 1 << d, m2 = m * 2;
		long double p0 = pi / m * oper;
//		sincos(p0, &unit_p0.y, &unit_p0.x);
		sincos(p0,unit_p0.y,unit_p0.x);
		for (int i = 0; i < n; i += m2) {
			Complex unit = 1;
			for (int j = 0; j < m; j++) {
				Complex &P1 = P[i + j + m], &P2 = P[i + j];
				Complex t = unit * P1;
				P1 = P2 - t;
				P2 = P2 + t;
				unit = unit * unit_p0;
			}
		}
	}
}

void calc(int l,int r,int e,int ans[])
{
    if (l==r) {
        ans[0]=2,ans[1]=1,ans[2]=a[l]%mo;
        return ;
    }
    int mid=(l+r)>>1;
    calc(l,mid,e+1,st[e][0]);
    calc(mid+1,r,e+1,st[e][1]);
    int la=st[e][0][0],lb=st[e][1][0],N;
    for (N=1;N<=la+lb+1;N<<=1) ;
    for (int i=0;i<la;i++) Pa[i]=Complex(st[e][0][i+1],0);
    for (int i=la;i<N;i++) Pa[i]=Complex(0,0);
    for (int i=0;i<lb;i++) Pb[i]=Complex(st[e][1][i+1],0);
    for (int i=lb;i<N;i++) Pb[i]=Complex(0,0);
    FFT(Pa,N,1),FFT(Pb,N,1);
    for (int i=0;i<N;i++) Pc[i]=Pa[i]*Pb[i];
    FFT(Pc,N,-1);
    ans[0]=N;
    for (int i=0;i<N;i++) {
        int x=((long long)((Pc[i].x/N)+0.5))%mo;
        ans[i+1]=x;
    }
    for (;ans[0]>1 && !ans[ans[0]];ans[0]--) ;
//    cout<<l<<' '<<r<<' '<<N<<endl;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++) scanf("%d",&a[i]);
    calc(1,n,0,ans);
    scanf("%d",&q);
    for (int i=1;i<=q;i++) {
        int k;
        scanf("%d",&k);
        printf("%d\n",ans[k+1]);
    }
    return 0;
}


swap permutation

询问一个排列,相邻交换严格k次,任意交换最多k次得到的排列个数是多少

考虑将第i个数放入前i-1个数的方案

用f[i][j]表示前i个数相邻交换严格j次的答案是多少,由于i在i-1个数中能插的位置是i个,然后又最多只能用j次交换往前,因此f[i][j]=f[i-1][j]+f[i-1][j-1]+...+f[i-1][j-i+1]

然后考虑到i和i+1换两次就换回来不变了,因此最后统答案的时候,将与k同奇偶的j的f[n][j]累和即可

第二问用g[i][j]表示前i个数用了严格j次交换的方案是多少

同样考虑把i插进去,首先是不动就是g[i-1][j],然后是一次可以与前i-1个数任意交换,因此就是g[i-1][j-1]*(i-1)

则g[i][j]=g[i-1][j]+g[i-1][j-1]*(i-1)

最后统答案的时候就把比k小的j的g[n][j]累和即可

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
const int mo=1000000007;
using namespace std;
int n,k;
int f[5000][5000],s[5000][5000];
long long g[5000][5000];
int main()
{
    scanf("%d%d",&n,&k);
    f[0][0]=1;
    for (int j=0;j<=k;j++) s[0][j]=f[0][0];
    for (int i=1;i<=n;i++)
        for (int j=0;j<=k;j++) {
            int L=j-i,R=j;
            if (L>=0)
                f[i][j]=(s[i-1][R]-s[i-1][L]+mo)%mo;
            else f[i][j]=s[i-1][R];
            if (j!=0) s[i][j]=(s[i][j-1]+f[i][j])%mo;
            else s[i][j]=f[i][j];
        }
    int ans=0;
    for (int j=0;j<=k;j++)
        if  ((j&1)==(k&1)) ans=(ans+f[n][j])%mo;
    printf("%d ",ans);
    g[0][0]=1;
    for (int i=1;i<=n;i++)
        for (int j=0;j<=k;j++) {
            g[i][j]=g[i-1][j];
            if (j) g[i][j]=(g[i][j]+g[i-1][j-1]*(i-1))%mo;
        }
    ans=0;
    for (int j=0;j<=k;j++)
        ans=(ans+g[n][j])%mo;
    printf("%d\n",ans);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值