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

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

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;
    }
}
时间: 2024-10-31 21:41:30

[BZOJ]2017省队十连测推广赛1的相关文章

bzoj [ 2017省队十连测推广赛1 ] ( 4765 &amp;&amp; 4766 &amp;&amp; 4767 )题解

bzoj 4765 -- 分块+dfs序+树状数组: 考虑分块.将1~n分成sqrt(n)块,对每个点记录它在每个块中的祖先个数,修改一个点时枚举每一块修改. 查询[l,r]时如果一个块在[l,r]中,直接将其加入答案.显然只剩下O(sqrt(n))个点.求出树的dfs序,用树状数组维护就可以O(logn)求出答案. 时间复杂度O(n*sqrt(n)*logn) 代码: 1 #include<iostream> 2 #include<cstdio> 3 #include<cs

bzoj省选十连测推广赛

A.普通计算姬 题意:给丁一棵树,每个点有一个权值,用sum(x)表示以x为根的子树的权值和,要求支持两种操作: 1 u v  :修改点u的权值为v. 2 l  r   :  求∑sum[i] l<=i<=r n,m(操作数)<=10^5 题解:数据范围比较小,考虑分块重建的做法. 求出每个点的dfs序和子树的区间,这样就可以On建出所有节点的sum的前缀和. 然后每次修改操作都把操作存下来,每次查询先找出这段区间的和,再去操作里处理这些操作对这个查询的影响. 具体实现就是:把每个点的子

[Lydsy2017省队十连测]商店购物

SOL: 我们可以前面写背包,后面组合数. #include<bits/stdc++.h> #pragma GCC optimize("-O2") #define mo 1000000007 #define N 10000007 #define LL long long using namespace std; #define sight(x) ('0'<=x&&x<='9') inline void read(LL &x){ stati

[Lydsy2017省队十连测]最长路径

SOL: 同JZOJ5061 #include<bits/stdc++.h> #define LL long long #define N 3007 using namespace std; LL n,mo,c[N][N],f[N],g[N],anw[N]; inline LL qsm(LL x,LL y) { static LL anw; for (anw=1;y;y=y>>1,x=x*x%mo) if (y&1) anw=anw*x%mo; return anw; }

Noi2016十连测第二场-黑暗 (二项式定理/斯特林数+CDQ+NTT)

Noi2016十连测第二场-黑暗 (二项式定理/斯特林数+CDQ+NTT) 题意: n 个点的无向图,每条边都可能存在,一个图的权值是连通块个数的 m 次方,求所有可能的图的权值和. 考虑\(dp[i][j]\)表示\(j\)个点,权值为\(i\)次方 我们首先要预处理出\(n\)个点无向联通图的数量\(g[i]\),模板题:BZOJ-3456 题解 对于\(dp[i][j]\),枚举\(1\)号点所在的连通块大小为\(x\),那么可以得到的是\(dp[i][j]=\sum dp[k][j-x]

BZOJ NOI十连测 第一测 T2

思路:看到这题,就感觉是一道很熟悉的题目: http://www.cnblogs.com/qzqzgfy/p/5535821.html 只不过这题的K最多可以到N,而且边权不再只是1,考试的时候yy了一下做法: 找k次直径,第一次把边取反,要是第二次再取到同样的边,那就把它变成0,毕竟每条边只经过2次嘛,YY的很好,实际上,交上去,5分TAT 后来听以为神犇说不是取0,而是继续取反,每条边取一次就取反一次,woc.. 1 #include<cstdio> 2 #include<cmath

BZOJ NOI十连测 第一测 T1

思路:首先考虑t=1的情况,t等于1,那么所有位置的颜色相同,我们不用考虑概率的问题,那么,k+d*x在模d下都相等,我们考虑预处理一个数组s[i][j],代表d为i,起始位置为j的等差数列的和,这个可以证明,当模小于等于sqrt(n)的时候可以完美解决,时间复杂度为N^1.5,对于d大于sqrt(n)的情况,只需要暴力枚举就可以了. 再考虑t>=2的情况,我们选的颜色一定是颜色数最少的那个,颜色数最少的颜色的期望绝对是最小的,然后,我们分k的左边和k的右边进行计算,我们这里称呼k+d*x的位置

BZOJ NOI十连测 第二测 T2

思路:20%可以搜索.. 1 #include<algorithm> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<iostream> 6 #include<time.h> 7 #define ll long long 8 const ll Mod=998244353; 9 ll jc[300005],jcny[300005]; 10 int n

BZOJ NOI 十连测 哈夫曼树

[问题描述]有这样一个经典问题:? 给出一个长度为??的非负整数数组??.? 每次可以选择数组中两个不同位置的数????, ????(?? ?= ??),将它们删除,然后再向数组中加入一个新的元素,值为???? + ????.? 这样一次操作产生的代价是这个新元素的值,即???? + ????.? 例如当前数组中的数为?? = {1, 1, 3, 1},选择??1 = 1, ??4 = 1进行操作后,数组变为{1, 3, 2},代价为2.? 一共会进行?? − 1次操作,要求最小化代价之和.这道