码迷,mamicode.com
首页 > 其他好文 > 详细

[BZOJ]2017省队十连测推广赛1

时间:2017-03-08 00:30:14      阅读:477      评论:0      收藏:0      [点我收藏+]

标签:操作   暴力   端点   can   快速乘   排列组合   pre   重构   inline   

听学长说有比赛就随便打一打。

 

A.普通计算姬

题目大意:给出一棵带权树,支持一下两种操作:1.修改一个点的权值;2.给出l,r,询问以点l为根的子树和、点l+1为根的子树和、点l+2为根的子树和……点r为根的子树和的总和。(点数、操作数不超过10^5)

思路:感觉是三题中最难的。给出的[l,r]区间在树上没有实际意义,不好利用数据结构维护。考虑若不修改,可以一遍dfs算出每个点对应的dfs序,这样每棵子树都对应一个dfs序的区间,前缀和一下就能O(1)查子树和,再按点的编号顺序把子树和前缀和一下就能O(1)回答询问,若要支持修改,我们可以分块重构,每次修改我们先只记录下有这次修改而不实际修改前缀和数组,询问时先利用之前的前缀和数组计算答案,再统计之前每一次修改对这次询问的贡献;每进行K次修改,我们就O(n)重建一遍前缀和数组并删掉记录下的修改。统计修改对询问的贡献可以这么做:将每个子树对应成dfs序区间,统计询问区间内有多少个dfs序区间包含修改点,可以用主席树求出询问区间内有多少dfs序区间右端点大等于修改点,减去有多少dfs序区间左端点大于修改点就得到包含修改点的区间个数,就能计算贡献了。总复杂度O(N^2/K+KNlogN),适当调整K的大小复杂度约为O(N(NlogN)^0.5)。

#include<cstdio>
#define ll unsigned long long
inline int read()
{
    int x;char c;
    while((c=getchar())<0||c>9);
    for(x=c-0;(c=getchar())>=0&&c<=9;)x=(x<<3)+(x<<1)+c-0;
    return x;
}
#define MN 100000
#define MK 50
#define ND 4000000
struct edge{int nx,t;}e[MN*2+5];
struct node{int l,r,s;}t[ND+5];
int n,h[MN+5],en,z[MN+5],l[MN+5],r[MN+5],cnt,cx[MN+5],cy[MN+5],cn,tn,rt[MN+5];
ll a[MN+5],b[MN+5];
inline void ins(int x,int y)
{
    e[++en]=(edge){h[x],y};h[x]=en;
    e[++en]=(edge){h[y],x};h[y]=en;
}
void dfs(int x,int fa)
{
    l[x]=++cnt;
    for(int i=h[x];i;i=e[i].nx)if(e[i].t!=fa)dfs(e[i].t,x);
    r[x]=cnt;
}
void build()
{
    for(int i=1;i<=n;++i)a[l[i]]=z[i];
    for(int i=1;i<=n;++i)a[i]+=a[i-1];
    for(int i=1;i<=n;++i)b[i]=b[i-1]+a[r[i]]-a[l[i]-1];
}
void ins(int pr,int pl,int x,int z)
{
    for(int l=0,r=n,mid,p=rt[pr]=++tn;;)
    {
        t[p].s=t[pl].s+z;
        if(l==r)return;
        int mid=l+r>>1;
        if(x<=mid)t[p].r=t[pl].r,p=t[p].l=++tn,pl=t[pl].l,r=mid;
        else t[p].l=t[pl].l,p=t[p].r=++tn,pl=t[pl].r,l=mid+1;
    }
}
int query(int pl,int pr,int x)
{
    int ans=0;
    for(int l=0,r=n,mid;;)
    {
        if(l==x)return ans+t[pr].s-t[pl].s;
        mid=l+r>>1;
        if(x<=mid)ans+=t[t[pr].r].s-t[t[pl].r].s,pl=t[pl].l,pr=t[pr].l,r=mid;
        else pl=t[pl].r,pr=t[pr].r,l=mid+1;
    }
}
int main()
{
    int m,i,t,x,y;ll ans;
    n=read();m=read();
    for(i=1;i<=n;++i)z[i]=read();
    for(i=1;i<=n;++i)ins(read(),read());
    dfs(e[h[0]].t,0);build();
    for(i=1;i<=n;++i)ins(i,rt[i-1],l[i]-1,-1),ins(i,rt[i],r[i],1);
    while(m--)
    {
        t=read();x=read();y=read();
        if(t==1)
        {
            cx[++cn]=l[x];cy[cn]=y-z[x];z[x]=y;
            if(cn==MK)cn=0,build();
        }
        if(t==2)
        {
            ans=b[y]-b[x-1];
            for(i=1;i<=cn;++i)ans+=(ll)query(rt[x-1],rt[y],cx[i])*cy[i];
            printf("%llu\n",ans);
        }
    }
}

 

B.文艺计算姬

题目大意:求一边有n个点,另一边有m个点,共n*m条边的二分图共有多少种生成树,答案对p取模。(n,m,p<=10^18)

思路:先用矩阵树定理暴力计算一部分答案,观察容易发现答案为n^(m-1)*m^(n-1),由于模数较大乘法会爆long long,要用类似快速幂的快速乘加上快速幂,复杂度为O(logp^2)。答案公式我还没想到比较好的证明方法,想到了会在这里补上(这类题目一般打表才是比较好的做法)。

矩阵树定理暴力的代码好像被误删了,本来想贴出来的……

#include<cstdio>
#define ll long long
ll n,m,p;
inline ll mod(ll x){return x>=p?x-p:x;}
ll mul(ll a,ll b)
{
    ll r=0;
    for(;b;b>>=1,a=mod(a<<1))if(b&1)r=mod(r+a);
    return r;
}
ll pow(ll a,ll b)
{
    ll r=1;
    for(;b;b>>=1,a=mul(a,a))if(b&1)r=mul(r,a);
    return r;
}
int main()
{
    scanf("%lld%lld%lld",&n,&m,&p);
    printf("%lld",mul(pow(n,m-1),pow(m,n-1)));
}

 

C.两双手

题目大意:一个棋子从(0,0)开始走,每次可以从(u,v)走到(u+Ax,u+Ay)或(u+Bx,u+By),有n个点不能走,问走到(Ex,Ey)有多少种方案。(0<=|Ax|,|Ay|,|Bx|,|By|,Ex,Ey,n<=500,Ax*By-Ay*Bx!=0)

思路:比较杂的数学应用吧。考虑容斥原理,走到(Ex,Ey)不经过禁止点的方案=总方案-至少经过一个禁止点的方案+至少经过两个-至少经过三个……可以用DP实现,为了让点有序方便DP,假设先把所有点旋转,使得向量(Ax,Ay)成为x轴,若(Bx,By)旋转后的纵坐标大于0,则一个点可以到另一个当且仅当该点旋转后纵坐标小于另一点旋转后纵坐标,反之同理,按照这个思路排序(实现上比较点积叉积等即可),我们就能保证只有排在前的能到达排在后的。经过奇数个点对答案贡献为负,偶数贡献为正,每次转移取负即可,令f[i]表示到第i个点的答案,则f[i]=Σ-f[j]*g[j][i] (j<i),其中g[j][i]为点j到点i的方案数,初始化(0,0)点的f值为-1即可。下面讨论计算g[j][i],设用了X条(Ax,Ay),Y条(Bx,By),暴力解方程x[j]+AxX+BxY=x[i],y[j]+AyX+ByY=y[i]就能算出X和Y,排列组合一下可以得到方案数,总算大功告成,总复杂度O(n^2)。

#include<cstdio>
#include<algorithm>
using namespace std;
#define MN 500
#define MX 500000
#define MOD 1000000007
int g[MN+5][MN+5],F[MX+5],R[MX+5],f[MN+5],a,b,c,d,e;
int inv(int x)
{
    int r=1,y=MOD-2;
    for(;y;y>>=1,x=1LL*x*x%MOD)if(y&1)r=1LL*r*x%MOD;
    return r;
}
struct node{int x,y;}p[MN+5];
bool cmp(node x,node y)
{
    int c1=a*x.y-b*x.x,c2=a*y.y-b*y.x;
    return c1==c2?a*x.x+b*x.y<a*y.x+b*y.y:e>0?c1<c2:c1>c2;
}
int main()
{
    int n,i,j,k,A,B,x,y,tx,ty;
    for(F[0]=i=1;i<=MX;++i)F[i]=1LL*F[i-1]*i%MOD;
    for(R[--i]=inv(F[MX]);i--;)R[i]=1LL*R[i+1]*(i+1)%MOD;
    scanf("%d%d%d",&tx,&ty,&n);p[n+1].x=tx;p[n+1].y=ty;
    scanf("%d%d%d%d",&a,&b,&c,&d);
    for(i=1;i<=n;++i)scanf("%d%d",&p[i].x,&p[i].y);
    e=a*d-b*c;sort(p,p+n+2,cmp);
    for(i=0;i<=n;++i)for(j=i;j++<=n;)
    {
        A=p[j].x-p[i].x;B=p[j].y-p[i].y;
        if((d*A-c*B)%(a*d-c*b))continue;
        x=(d*A-c*B)/(a*d-c*b);
        if((b*A-a*B)%(c*b-a*d))continue;
        y=(b*A-a*B)/(c*b-a*d);
        if(x<0||y<0)continue;
        g[i][j]=1LL*F[x+y]*R[x]%MOD*R[y]%MOD;
    }
    for(i=0;i<=n+1;++i)
    {
        if(!p[i].x&&!p[i].y)f[i]=-1;
        for(j=0;j<i;++j)f[i]=(f[i]-1LL*f[j]*g[j][i])%MOD;
        if(p[i].x==tx&&p[i].y==ty)return printf("%d",(f[i]+MOD)%MOD),0;
    }
}

 

[BZOJ]2017省队十连测推广赛1

标签:操作   暴力   端点   can   快速乘   排列组合   pre   重构   inline   

原文地址:http://www.cnblogs.com/ditoly/p/BZOJ-2017-1.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!