2017省选前北京集训总结

感觉自己这一个月被各路神犇虐的飞起,但也收获蛮多了。

大概是省选前了吧,如果自己这一波省选跪了的话,可能也就退役了,那么还是要努力一点的。( 希望不会UPD退役消息

一个月没有碰过游戏了,看着感觉自己很刚啊,可能以前玩的太疯了吧

除了北京空气确实比四川干燥,还有学校食堂确实好些除了辣椒之外,也就这样了吧。

下面总结一下集训期间近二十场考试的题目。感觉难度是个余弦函数图像。。。。

Contest 1 T1

题意 : 将n1个A,n2个B,n3个C,n4个D拼成一个序列,且序列内相邻字母不同的方案数。

数据范围 n<=1000

题解 : 考虑先加入A,B,那么我们可以得到一串AB间杂的序列,那么我们在这个序列中AA,BB间必须插入C,D。

而我们插入C,D只有可能四种形式: CDC , CDCD , DCD ,DCDC ,二四种序列实际是等价的,而我们插入1,3种序列都会造成C,D数量产生差值,而C,D的总差值是确定的,所以我们枚举第一种序列数量就可以知道第三种序列的个数,那么我们实际就是求把这些序列插入AB序列中的方案数,当然,我们必须保证AB中每个相邻元素相同的位置必须插入一种序列。考虑我们只用枚举在多少对A中插入了B就可以知道相邻相同的区间个数(注意特殊处理B加在左右端点的情况),所以我们只用先枚举在多少对A中插入了B,然后枚举在多少不必插入的区间插入,再枚举C这种形式的序列数量,然后把这四种序列插入AB中的方案数。处理的时候我们可以对每个1,3形式的序列先插一个对应字母进去,2,4形式插入CD或DC,然后我们再把剩余的数向序列里随便插。

复杂度看似$n^3$实际后一个枚举只和我们要插入多少个区间相关,所以可以预处理。

时间复杂度 $O(n^2)$

代码 : 

#include<bits/stdc++.h>
#define MOD 1000000007
#define LL long long
using namespace std;
 
inline int Max(int a,int b) {return a>b?a:b;}
inline int Min(int a,int b) {return a<b?a:b;}
inline int Abs(int a) {return a>0?a:-a;}
inline int Sqr(int a) {return a*a;}
 
#define MAXN 4015
 
int a,b,c,d;int C[MAXN][MAXN],f[MAXN][MAXN],twp[MAXN];
 
void Pre(int n) {
    for(int i=0;i<=n;i++) C[i][0]=1;
    for(int i=1;i<=n;i++) 
        for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
    f[0][0]=1;
    for(int i=0;i<=n;i++) 
        for(int j=1;j<=n;j++) 
            if(!i) f[i][j]=f[i][j-1];
            else f[i][j]=(f[i-1][j]+f[i][j-1])%MOD;
    twp[0]=1;
    for(int i=1;i<=n;i++) twp[i]=twp[i-1]*2%MOD;
}
 
LL ans,dv[MAXN];
void Solve(int n) {
    for(int j=0;j<=c;j++) {
        int s1=j,s2=d+j-c,re;
        if(s2<0||s2>d||s1+s2>n) continue;
        re=c-j;re-=n-s1-s2;
        if(re<0) continue;
        (dv[n]+=((LL)f[re][n]*twp[n-s1-s2])%MOD*((LL)C[n][s1]*C[n-s1][s2]%MOD))%=MOD;
    }
}
 
int main() {
    cin>>a>>b>>c>>d;
    Pre(4005);
    if(a<b) swap(a,b);if(c<d) swap(c,d);
    for(int i=1;i<=c+d;i++) Solve(i);
    for(int x,p=0;p<4;p++) {
        x=__builtin_popcount(p);
        for(int i=Min(a-1,b-x);i>(x?-1:0);i--) {
            int ne=a-1-i+b-x-i;int une=a+b+1-ne;
            for(int j=0;j<=une;j++) 
                (ans+=(dv[j+ne]*C[une][j])%MOD*((LL)f[b-x-i][x+i]*C[a-1][i]%MOD))%=MOD;
        }
    }
    printf("%lld
",ans);
    return 0;
}
View Code

Contest1 T2

题意 :一个人在每个小时可以吃饭或睡觉,每个小时干每件事都有不同的满意值,一个人在连续k个小时内吃饭时间不能少于$m_e$小时,睡觉不能少于$m_s$小时,问最大的满意值是多少。

数据范围 : k,n <= 1000 ; 

题解:我们要考虑题目的约束条件可以转换为每k个小时内$m_e le EatTime le k - m_s$那么我们设$S_i$代表在$i$时刻做什么事,吃饭为1,睡觉为0.

那么我们有 $ m_e le S_1 + S_2 + S_3 + ... + S_k le k - m_s $ 我们令 $0 le y le k - m_e - m_s$ 

使得 $ S_1 + S_2 + S_3 + ... + S_k + y_1 = k - m_s $  我们把等式两两相减,得到 $ S_1 - S_{k - 1} + y_1 - y_2 = 0 $

然后将所有式子加起来会发现流量平衡,所以我们把等式看成点,变量正进负出跑费用流即可。

代码 : 没写

Contest1 T3 

题意 : 现在有三天,每天给你一堆操作,有末尾添加一个字符和删除末尾字符两种操作。设字符串集合$S_i$为第$i$天出现过的所有字符串的集合。

数据范围 : 出现字符串集合最大的一天集合大小不超过1e6

定义:传统编辑,你只能通过末尾添加字符和末尾删除字符来改变一个串。两个串的传统编辑距离为其中一个串通过传统编辑变换到另一个串的最小操作数。

    非传统编辑,将一个串直接变为另一个串,记为一次操作。

第一天 :你要输出$S_1$里所有字符串两两间的编辑距离和

第二天 :为了计算$S_1$中的串变换到$S_2$中的串的编辑距离,你需要选定$A' subset S_1 , B' subset S_2$,对于$A subset S_1 , B subset S_2$,AB间的距离为A传统编辑到A'再非传统编辑到B'再传统编辑到B的操作次数。你要输出$S_1 + S_2$中字符串两两间的编辑距离和的最大值和最小值。

第三天 :为了计算$S_1$中的串变换到$S_2$中串的距离,你需要选定A',B',B'',C'。距离计算为A 传统 -> A' 非传统 -> B' 传统 -> B'' 非传统 -> C' 传统 -> C的操作次数,你要输出$S_1 + S_2 + S_3$中串两两间编辑距离和的最大值和最小值。(注意,在第三天你计算$S_1 -> S_2$时,选定点就是A',B',不能再重新选,B''和C'同理。)

题解 : 

第 i 问实际上就是问你用$ i - 1$条边将这 i 棵字典树连起来,两两距离的最值。前两问很简单,第三问我们推一下式子,不难发现最大最小时A'和C'分别取整棵树的最值点即可,那么问题就只与$Dist(B',B'')$和B',B''有关,最小值显然B',B''应该取同一点,而最大时我们可以去看51nod fancy cat的题解(不会QAQ

代码 : 无

Contest2 T1

题意 :定义$f_0(n)$表示有序正整数对$(p , q)$满足$pq = n$且$gcd(p , q) = 1$,定义$f_{r+1}(n) = sum limits_{uv = n} frac{f_{r}(u)+f_{r}(v)}{2}$

其中$(u , v)$为有序正整数对(不用互质)。

q组询问,每次给出n,r,求$f_r(n) mod (1e9+7)$的值。

数据范围 :r,n,q <= 1e6

题解 : 设$t = {p_{1}}^{a1} * {p_{2}}^{a2} * ... * {p_{k}}^{ak}$ 那么显然有$f_0(t) = 2^k$ 

而$f_r(n)$的递推式推一下就可以得到$f_r(n) = sum limits_{d|n} f_{r-1}(d) $

$f_0$是个积性函数,$f_r$就也是积性函数,也就是说我们可以把n质因数分解掉求每个质因子的幂次对应的函数值,我们又可以发现$f_r(p^k)$的值与p无关只与k有关,那么就可以愉快dp了。

$dp[r][k] = sum limits_{i = 0}^{k} dp[r - 1][k]$

代码 :

#include<bits/stdc++.h>
#define MOD 1000000007
#define LL long long
#define eps 1e-9
using namespace std;
 
inline int read() {
    int ret=0,f=1;char c=getchar();
    while(c>'9'||c<'0') {if(c=='-') f=-f;c=getchar();}
    while(c<='9'&&c>='0') {ret=ret*10+c-'0';c=getchar();}
    return ret;
}
 
#define int int
inline int Max(int a,int b) {return a>b?a:b;}
inline int Min(int a,int b) {return a<b?a:b;}
inline int Abs(int a) {return a>0?a:-a;}
inline int Sqr(int a) {return a*a;}
#undef int
 
#define MAXN 1000006
int i;
 
LL fpow(int a,int p) {
    LL ret=1,d=a;
    while(p) {
        if(p&1) ret=ret*d%MOD;
        p>>=1;d=d*d%MOD;
    }
    return ret;
}
 
int q;LL ans,fac[MAXN];int C[MAXN][30];
bool mk[MAXN];int pri[MAXN],bk[MAXN],cnt;
 
void Pre(int n) {
    fac[0]=1;fac[1]=1;
    for(i=2;i<=n;i++) {
        fac[i]=fac[i-1]*i%MOD;
        if(!mk[i]) pri[++cnt]=i,bk[i]=i;
        for(int j=1;j<=cnt&&i*pri[j]<=n;j++) {
            mk[i*pri[j]]=1;bk[i*pri[j]]=pri[j];
            if(i%pri[j]==0) break;
        }
    }
    for(i=0;i<20;i++) C[0][i]=1;
    for(i=1;i<=n;i++) {
        for(int j=0;j<20;j++) 
            C[i][j]=(C[i-1][j]+(j>0?C[i][j-1]:0))%MOD;
    }
}
 
int k[MAXN],top,r;
void Query(int n) {
    top=0;ans=1;int t=0,mx=0;
    while(n>1) {
        if(t==bk[n]) n/=bk[n],k[top]++;
        else t=bk[n],n/=bk[n],k[++top]++;
    }
    for(i=1;i<=top;i++) mx=Max(mx,k[i]);
    if(!r) ans=fpow(2,top);
    else for(i=1;i<=top;i++) 
        ans=(ans*((2*C[r][k[i]])%MOD-C[r-1][k[i]]+MOD)%MOD)%MOD;
    for(i=1;i<=top;i++) k[i]=0;
}
 
int main() {
    Pre(1000000);
    q=read();
    for(int n,i=1;i<=q;i++) {
        r=read();n=read();
        Query(n);
        printf("%lld
",ans);
    }
    return 0;
}
View Code

Contest2 T2

题意 : 唯一一道交互题。有n个盒子,盒子里有一些球和一个容量上限$c_i$,Alice和Bob玩一个游戏,每次要从一个盒子里拿一个球或放一个球。要求出现过一次的局面不能出现第二次,让你的程序输出0或1表示你要先手还是后手,然后你要用你的程序玩赢裁判程序。

数据范围 :

设$X = prod limits_{i = 0}^{n}$

n <= 15 , X <= 50000

题解 : 好好的一道交互题然而是一道网络流的博弈论题。题目保证局面数不超50000,那么局面间暴力连边黑白染色,然后去看最大匹配,如果完全匹配,那么先手走匹配边即可,如果不完全匹配则必然一边比另一边多一个点,则多一个点的那一边为后手必胜,则等评委输入它的操作后把这个点删除后跑一次匹配即可。少一个点的那边必胜,直接走匹配边即可。

代码 : 

#include"alice.h"
#include<bits/stdc++.h>
#define LL long long
#define eps 1e-9
using namespace std;
 
#define int int
inline int Max(int a,int b) {return a>b?a:b;}
inline int Min(int a,int b) {return a<b?a:b;}
inline int Abs(int a) {return a>0?a:-a;}
inline int Sqr(int a) {return a*a;}
#undef int
 
#define MAXN 50005
 
int k,fr,sz;
struct state{
    int x[20];
    bool operator < (const state &b) const {
        for(int i=1;i<=k;i++) 
            if(x[i]==b.x[i]) continue;
            else return x[i]<b.x[i];
        return x[1]<b.x[1];
    }
}st,id[MAXN];
map<state,int> mp;
int c[MAXN],pi=1,co[MAXN];bool perfect,vis[MAXN];
 
const int S=MAXN-2,T=MAXN-3;
struct edge{
    int to,next,f,from;bool ev;
}e[MAXN*40];int head[MAXN],cnt=1;
inline void insert(int a,int b,int f) {
    e[++cnt].next=head[a];head[a]=cnt;e[cnt].f=f;e[cnt].ev=1;e[cnt].from=a;e[cnt].to=b;
    e[++cnt].next=head[b];head[b]=cnt;e[cnt].f=0;e[cnt].ev=0;e[cnt].from=b;e[cnt].to=a;
}
 
int d[MAXN],bk[MAXN],cur[MAXN];queue<int> q;
int Bfs() {
    memset(d,-1,sizeof(d));
    q.push(S);d[S]=0;
    while(!q.empty()) {
        int now=q.front();q.pop();
        for(int i=head[now];i;i=e[i].next) {
            if(!(~d[e[i].to])&&e[i].f>0) {
                q.push(e[i].to);d[e[i].to]=d[now]+1;
            }
        }
    }
    return ~d[T];
}
inline int Agment() {
    for(int i=T;i!=S;i=e[bk[i]].from) {
        e[bk[i]].f--;e[bk[i]^1].f++;
    }
    return 1;
}
int FDfs(int v) {
    int ret=0;
    if(v==T) {return Agment();}
    for(int &i=cur[v];i;i=e[i].next) 
        if(e[i].f&&d[e[i].to]==d[v]+1) {
            bk[e[i].to]=i;
            if(ret=FDfs(e[i].to)) return ret;
        }
}
 
int flow;
int MaxFlow() {
    while(Bfs()) {
        for(int i=1;i<=sz;i++) cur[i]=head[i];
        cur[S]=head[S];cur[T]=head[T];
        while(FDfs(S)) flow++;
    }
    return flow;
} 
 
void Dfs(int p,state now) {
    if(p>k) {
        mp[now]=++sz;id[sz]=now;
        return;
    }
    for(int i=0;i<=c[p];i++) {
        now.x[p]=i;
        Dfs(p+1,now);
    }
}
void Extend(int p,int op=0) {
    if(p==fr&&!op) return;
    int sum=0;state t=id[p];
    for(int i=1;i<=k;i++) sum+=t.x[i];
    co[p]=sum&1;
    if(sum&1) insert(S,p,1);
    else {insert(p,T,1);return;}
    for(int i=1;i<=k;i++) {
        if(t.x[i]<c[i]) {
            t.x[i]++;
            insert(p,mp[t],1);
            t.x[i]--;
        }
        if(t.x[i]) {
            t.x[i]--;
            insert(p,mp[t],1);
            t.x[i]++;
        }
    }
}
 
bool init(int n, const int *d, const int *p) {
    k=n;vis[S]=vis[T]=1;
    for(int i=1;i<=n;i++) st.x[i]=p[i];
    for(int i=1;i<=n;i++) c[i]=d[i],pi*=(c[i]+1);
    Dfs(1,id[0]);fr=mp[st];vis[fr]=1;
    for(int i=1;i<=sz;i++) Extend(i);
    if((pi&1)==0) {
        Extend(fr,1);
        MaxFlow();
        return 1;
    }
    if(MaxFlow()==(pi-1)/2) return 0;
    Extend(fr,1);MaxFlow();
    if(co[fr]) {
        for(int i=head[fr];i;i=e[i].next) 
            if(e[i].ev&&e[i].f==0) return 1;
    }
    else for(int i=head[fr];i;i=e[i].next) 
            if(!e[i].ev&&e[i].f==1) return 1;
    return 0;
}
 
state up;
Data getNextMove(Data in) {
    Data ret;
    if(in.id) {st.x[in.id]+=in.ops=='+'?1:-1;fr=mp[st];}
    if(co[fr]) {
        for(int i=head[fr];i;i=e[i].next) 
            if(e[i].ev&&e[i].f==0) {up=id[e[i].to];break;}
    }
    else {
        for(int i=head[fr];i;i=e[i].next) 
            if(!e[i].ev&&e[i].f==1) {up=id[e[i].to];break;} 
    }
    for(int i=1;i<=k;i++) 
        if(up.x[i]!=st.x[i]) {
            ret.id=i;ret.ops=up.x[i]>st.x[i]?'+':'-';
        }
    st=up;
    return ret;
}
View Code

Contest2 T3

题意 :给一个有根树,有点权,求$sum limits_{x = 1}^{n - 1} sum limits_{y = x + 1}^{n} sum limits_{k = 1}^{Dist(x,y)+1} 路径上前k大点权$

数据范围 : 点权,n <= 1e5

题解 :直接上图吧。。打字太累了。。

大概是把每个点对算贡献,点u对点v会有val(v)的贡献当且仅当val(v) > val(u)且u,v在同一路径上,那么就去统计这种路径的条数。具体看代码吧,使用的三个树状数组维护的。

代码 :

#include<bits/stdc++.h>
#define low(x) ((x)&(-(x)))
#define LL long long
#define eps 1e-9
#define MOD 1000000007
using namespace std;
 
#define int int
inline int Max(int a,int b) {return a>b?a:b;}
inline int Min(int a,int b) {return a<b?a:b;}
inline int Abs(int a) {return a>0?a:-a;}
inline int Sqr(int a) {return a*a;}
#undef int
 
#define MAXN 100005
 
struct edge{
    int to,next;
}e[MAXN*2];int head[MAXN],cnt=1;
inline void insert(int a,int b) {
    e[++cnt].next=head[a];head[a]=cnt;e[cnt].to=b;
    e[++cnt].next=head[b];head[b]=cnt;e[cnt].to=a;
}
struct node{
    int id,val;
}x[MAXN];
inline bool cmp(const node &a,const node &b) {
    return a.val<b.val;
}
 
int fr[MAXN],se[MAXN],sz[MAXN],tot;
int n;LL ans,all;
struct fent{
    LL c[MAXN*2];
    void Add(int p,int v) {
        for(int i=p;i<=tot;i+=low(i)) c[i]+=v;
    }
    LL Query(int l,int r) {
        LL ret=0;
        for(int i=l-1;i;i-=low(i)) ret-=c[i];
        for(int i=r;i;i-=low(i)) ret+=c[i];
        return ret%MOD;
    }
}bit[3];
 
void Dfs(int v) {
    fr[v]=++tot;sz[v]++;
    for(int i=head[v];i;i=e[i].next) 
        if(!fr[e[i].to]) {
            Dfs(e[i].to);
            sz[v]+=sz[e[i].to];
        }
    se[v]=++tot;
}
 
void Cal(int num,int v) {
    LL bin=bit[0].Query(fr[num],se[num]),
    fal=bit[1].Query(1,fr[num]),
    sfal=bit[2].Query(1,fr[num]),
    ins=0,GG;
    for(int i=head[num];i;i=e[i].next) {
        if(sz[e[i].to]>sz[num]) continue;
        GG=bit[0].Query(fr[e[i].to],se[e[i].to]);
        (ins+=(n-sz[e[i].to])*GG)%MOD;
    }
    (ans+=(sz[num]*(all-bin-sfal)%MOD+sz[num]*fal%MOD+ins)*v%MOD)%=MOD;
}
 
int main() {
    scanf("%d",&n);
    for(int a,b,i=1;i<n;i++) {scanf("%d%d",&a,&b);insert(a,b);}
    for(int i=1;i<=n;i++) {scanf("%d",&x[i].val);x[i].id=i;}
    Dfs(1);
    for(LL al,ad,i=1;i<=n;i++) {
        al=1;
        for(int j=head[i];j;j=e[j].next) {
            ad=sz[i]>sz[e[j].to]?sz[e[j].to]:n-sz[i];
            (ans+=(ad*al)%MOD*x[i].val)%=MOD;al+=ad;
        }
    }
    sort(x+1,x+1+n,cmp);
    for(int now,i=1;i<=n;i++) {
        now=x[i].id;
        Cal(now,x[i].val);
        all+=sz[now];
        bit[0].Add(fr[now],sz[now]);
        for(int j=head[now];j;j=e[j].next) {
            if(sz[e[j].to]>sz[now]) continue;
            bit[1].Add(fr[e[j].to],n-sz[e[j].to]);
            bit[1].Add(se[e[j].to],sz[e[j].to]-n);
        }
        bit[2].Add(fr[now],sz[now]);
        bit[2].Add(se[now],-sz[now]);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

Contest3 T1

题意 :给一个大小为n有根树,每条边上有一个字母,定义两个点i,j的lcp(i,j) = 从i开始到根路径所形成的字符串与从j开始到根的字符串的lcp长度。

q组询问,每次给出一个点集,要求点集里所有点对的lcp的和

数据范围 : n <= 1e5 , 所有点集大小和 <=2*n

题解 :考试时读错题意理解成从根到点形成的路径的lcp,那样就直接上虚数搞一搞就可以了。不过是从点到根的路径,怎么办?套个广义后缀自动机就成了读错题意的版本了,然后虚数一搞就可以了。

代码 : 无

Contest3 T2

题意 :给一个n个点的二叉堆,一些节点上有&k_i&个单位的食物,一些节点上有一些生物,生物一共有m个。输入依次给你这些生物的位置,这些生物一开始都处于睡眠状态,然后会以给你的次序依次醒来,每只生物醒后都需要一个单位的食物,你需要输出每个生物醒后,所有生物觅食所走的距离和最小为多少。

数据范围 :n,m <= 1e5

题解 :显然每只生物醒来后显然去找离自己最近的食物最优,但是怎么定义这个最近距离呢?我们对于一条u->v的边,如果已经有一个生物经过这条边那么我们就认为v->u的权值为-2,因为我们走这条边显然可以和从这条边来的生物互换从而减少2的全局消耗,由于树高只有log,所以每次找出一条路径暴力爬树高用线段树维护一下最近食物的距离。当然还有一种更优雅的方式是去树形dp,具体我们只需要维护D[i],D[i]表示这个子树从根节点出发到最近的有食物的点。由于树高log。那么每个生物每次走过后,我们可以暴力修改它走过的点的D[i]值,更新每条边的费用。这样做的时间复杂度就是mlog(n)的了。

代码 : (线段树版本

 
#include<bits/stdc++.h>
#define INF 1000
#define low(x) ((x)&(-(x)))
#define LL long long
#define eps 1e-9
using namespace std;
 
#define int int
inline int Max(int a,int b) {return a>b?a:b;}
inline int Min(int a,int b) {return a<b?a:b;}
inline int Abs(int a) {return a>0?a:-a;}
inline int Sqr(int a) {return a*a;}
#undef int
 
#define MAXN 100005
 
int n,m,food[MAXN],pos[MAXN],ans,edge[MAXN][2];//0 :up ; 1 :down
int dfn[MAXN*2],apr[MAXN][2],dep[MAXN];bool vis[MAXN];
 
 
struct bag{
    int a,b;
    bag() {}
    bag(int _,int __) : a(_),b(__) {}
    inline bag Merge(const bag &x) {
        bag c;
        c.a=Min(a,x.a);
        c.b=c.a==a?b:x.b;
        return c;
    }
};
     
struct SegTree{
    struct node{
        int mn,add,mnid;
    }x[MAXN*8];int id;
     
    inline void Updata(int num) {
        x[num].mn=Min(x[num<<1].mn,x[num<<1|1].mn);
        x[num].mnid=x[num].mn==x[num<<1].mn?x[num<<1].mnid:x[num<<1|1].mnid;
        x[num].mn+=x[num].add;
    }
     
    void Build(int l,int r,int num) {
        if(l==r) {
            x[num].mn=food[dfn[l]]?dep[dfn[l]]:INF+dep[dfn[l]];
            x[num].mnid=dfn[l];
            return;
        }
        int mid=(l+r)>>1;
        Build(l,mid,num<<1);
        Build(mid+1,r,num<<1|1);
        Updata(num);
    }
    void AddTag(int nl,int nr,int l,int r,int num,int v) {
        if(nl==l&&nr==r) {x[num].add+=v;x[num].mn+=v;return;}
        int mid=(l+r)>>1;
        if(nl>mid) AddTag(nl,nr,mid+1,r,num<<1|1,v);
        else if(nr<=mid) AddTag(nl,nr,l,mid,num<<1,v);
        else {
            AddTag(nl,mid,l,mid,num<<1,v);
            AddTag(mid+1,nr,mid+1,r,num<<1|1,v);
        }
        Updata(num);
    }
    bag Query(int nl,int nr,int l,int r,int num) {
        if(nl==l&&nr==r) {return bag(x[num].mn,x[num].mnid);}
        int mid=(l+r)>>1;bag ret;
        if(nl>mid) ret=Query(nl,nr,mid+1,r,num<<1|1);
        else if(nr<=mid) ret=Query(nl,nr,l,mid,num<<1);
        else ret=Query(nl,mid,l,mid,num<<1).Merge(Query(mid+1,nr,mid+1,r,num<<1|1));
        ret.a+=x[num].add;
        return ret;
    }
}st;
 
void Dfs(int v,int d) {
    if(v>n) return;
    dep[v]=d;
    dfn[++dfn[0]]=v;apr[v][0]=dfn[0];
    Dfs(v<<1,d+1);Dfs(v<<1|1,d+1);
    apr[v][1]=++dfn[0];
}
 
bag gt;bool fir=1;
int main() {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&food[i]);
    for(int i=1;i<=m;i++) scanf("%d",&pos[i]);
    Dfs(1,0);st.Build(1,dfn[0],1);
    for(int len,mnp,dol,u,tmp,tmid,i=1;i<=m;i++) {
        tmp=INF;u=pos[i];tmid=0;len=0;
        while(u) {
            gt=st.Query(apr[u][0],apr[u][1],1,dfn[0],1);
            dol=gt.a-st.Query(apr[u][0],apr[u][0],1,dfn[0],1).a+(food[u]?0:INF);
            if(dol+len<tmp)  tmp=dol+len,tmid=u,mnp=gt.b;
            len+=edge[u][1]?-1:1;
            u>>=1;
        }
        u=pos[i];ans+=tmp;
        while(u!=tmid) {
            if(edge[u][1]) edge[u][1]--;
            else if(!edge[u][0]++) 
                st.AddTag(apr[u][0],apr[u][1],1,dfn[0],1,-2);
            u>>=1;
        }u=mnp;
        while(u!=tmid) {
            if(edge[u][0]) {
                if(!(--edge[u][0]))
                    st.AddTag(apr[u][0],apr[u][1],1,dfn[0],1,2);
            }
            else edge[u][1]++;
            u>>=1;
        }
        if(!(--food[mnp])) st.AddTag(apr[mnp][0],apr[mnp][0],1,dfn[0],1,INF);
        if(fir) {printf("%d",ans);fir=0;}
        else printf(" %d",ans);
    }
    printf("
");
    return 0;
}
View Code

Contest3 T3

题意 :二维平面上n个点,每个点可以跳到以自身为左下角&(a_i,b_i)&为右上角的矩形中的任意一个点,问起点任选,最多能跳多少次。

数据范围 : n <= 1e5 ,坐标范围 <=2e5

题解 : sb kd-tree维护矩形最大值即可。

代码 :无

Contest4 T1

题意 : 已知n,k和数组s。输出i从1到n所有的$sum limits_{j = 1}^{i} ( sum limits_{t = j}^{i} s_t)^k mod 1000000007$

数据范围 : n <= 50000 , k <= 100

题解 :将s求一个前缀和,那么k次方下面就是两个前缀和相减,二项式打开后合并同类项

Contest4 T2

题意 : 给你一个n个点的树,问你从u走到v的期望步数,可以走回头路。多次询问。

数据范围 :n <=1e5,q<=2e5

题解 :我们可以建立期望方程去做,由于是棵树,手解一下发现可以用倍增维护,随便维护一下。

代码 :

#include<bits/stdc++.h>
#define low(x) ((x)&(-(x)))
#define LL long long
#define eps 1e-9
using namespace std;
 
#define int int
inline int Max(int a,int b) {return a>b?a:b;}
inline int Min(int a,int b) {return a<b?a:b;}
inline int Abs(int a) {return a>0?a:-a;}
inline int Sqr(int a) {return a*a;}
#undef int
 
#define MAXN 100005
 
int deg[MAXN];
struct edge{
    int to,next;
}e[MAXN*2];int head[MAXN],cnt;
inline void Insert(int a,int b) {
    deg[a]++;deg[b]++;
    e[++cnt].next=head[a];head[a]=cnt;e[cnt].to=b;
    e[++cnt].next=head[b];head[b]=cnt;e[cnt].to=a;
}
 
LL bz[MAXN][30],val[MAXN][30],dep[MAXN];
int n,m;LL ans;
 
void Dfs(int v,int d) {
    dep[v]=d;val[v][0]=deg[v];
    for(int i=head[v];i;i=e[i].next) 
        if(!val[e[i].to][0]) {
            Dfs(e[i].to,d+1);
            val[v][0]+=val[e[i].to][0];
            bz[e[i].to][0]=v;
        }
}
int Lca(int u,int v) {
    while(dep[u]<dep[v]) swap(u,v);
    for(int i=20;~i;i--) 
        if(dep[bz[u][i]]>=dep[v]) 
            u=bz[u][i];
    for(int i=20;~i;i--) 
        if(bz[u][i]!=bz[v][i]) {
            u=bz[u][i];v=bz[v][i];
        }
    return u==v?u:bz[u][0];
}
LL Getsum(int u,int v) {
    LL ret=0;
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=19;~i;i--) 
        if(dep[bz[u][i]]>=dep[v]) {
            ret+=val[u][i];u=bz[u][i];
        }
    return ret;
}
 
int main() {
    scanf("%d",&n);
    for(int a,b,i=1;i<n;i++) {
        scanf("%d%d",&a,&b);
        Insert(a,b);
    }
    Dfs(1,1);
    for(int j=1;j<=20;j++) for(int i=1;i<=n;i++) {
        bz[i][j]=bz[bz[i][j-1]][j-1];
        val[i][j]=val[i][j-1]+val[bz[i][j-1]][j-1];
    }
    scanf("%d",&m);
    for(int a,b,lca,i=1;i<=m;i++) {
        scanf("%d%d",&a,&b);
        lca=Lca(a,b);
        ans=Getsum(a,lca);
        ans-=Getsum(b,lca);
        ans+=(dep[b]-dep[lca])*val[1][0];
        printf("%lld
",ans);
    }
    return 0;
}
View Code

Contest4 T3

题意 :给定一棵有权无根树,求一条路径满足路径上边权的最小值与路径边权之和的乘积尽量大,输出最大乘积。

数据范围 :n<=3e5,边权小于52501

题解 : 直接把边权排序之后依次加入,维护直径就可以了。

代码 :

#include<bits/stdc++.h>
#define low(x) ((x)&(-(x)))
#define LL long long
#define eps 1e-9
using namespace std;
 
#define int long long
inline int Max(int a,int b) {return a>b?a:b;}
inline int Min(int a,int b) {return a<b?a:b;}
inline int Abs(int a) {return a>0?a:-a;}
inline int Sqr(int a) {return a*a;}
#undef int
 
#define MAXN 300005
struct adedge{
    int from,to,val;
}ae[MAXN];
inline bool cmp(const adedge &a,const adedge &b) {
    return a.val>b.val;
}
struct edge{
    int to,next,w;
}e[MAXN*2];int head[MAXN],cnt;
inline void Insert(int a,int b,int w) {
    e[++cnt].next=head[a];head[a]=cnt;e[cnt].to=b;e[cnt].w=w;
    e[++cnt].next=head[b];head[b]=cnt;e[cnt].to=a;e[cnt].w=w;
}
 
LL val[MAXN];bool vis[MAXN];int sz[MAXN],bll[MAXN],fa[MAXN],dep[MAXN];
void Dfs(int v,LL w,int d) {
    vis[v]=1;dep[v]=d;
    sz[v]++;val[v]=w;
    for(int i=head[v];i;i=e[i].next) 
        if(!vis[e[i].to]) {
            Dfs(e[i].to,w+e[i].w,d+1);
            sz[v]+=sz[e[i].to];
            fa[e[i].to]=v;
        }
}
void HDfs(int v,int bl) {
    bll[v]=bl;int mx=0,mxid=0;
    for(int i=head[v];i;i=e[i].next) 
        if(sz[e[i].to]>mx&&!bll[e[i].to]) 
            mx=sz[e[i].to],mxid=e[i].to;
    if(mxid) HDfs(mxid,bl);
    for(int i=head[v];i;i=e[i].next) 
        if(!bll[e[i].to]) HDfs(e[i].to,e[i].to);
}
LL Dist(int u,int v) {
    LL ret=val[u]+val[v];
    if(dep[u]<dep[v]) swap(u,v);
    while(bll[u]!=bll[v]) {
        if(dep[bll[u]]<dep[bll[v]]) swap(u,v);
        u=fa[bll[u]];
    }
    ret-=2*(dep[u]<dep[v]?val[u]:val[v]);
    return ret;
}
 
int bln[MAXN];
struct line{
    int x,y;LL len;
    void Merge(const line &b) {
        LL mx=0,mxx,mxy,t;
        if(len>mx) mx=len,mxx=x,mxy=y;
        if(b.len>mx) mx=b.len,mxx=b.x,mxy=b.y;
        if((t=Dist(x,b.x))>mx) mx=t,mxx=x,mxy=b.x;
        if((t=Dist(x,b.y))>mx) mx=t,mxx=x,mxy=b.y;
        if((t=Dist(y,b.x))>mx) mx=t,mxx=y,mxy=b.x;
        if((t=Dist(y,b.y))>mx) mx=t,mxx=y,mxy=b.y;
        len=mx;x=mxx;y=mxy;
    }
}train[MAXN];
 
int Father(int v) {
    int k=v,b=v;
    while(bln[v]!=v) v=bln[v];
    while(bln[k]!=v) {
        k=bln[k];bln[b]=v;b=k;
    }
    return v;
}
 
int n;LL ans;
 
int main() {
    scanf("%d",&n);
    for(int i=1;i<=n;i++) {
        bln[i]=i;train[i].len=0;
        train[i].x=train[i].y=i;
    }
    for(int i=1;i<n;i++) {
        scanf("%d%d%d",&ae[i].from,&ae[i].to,&ae[i].val);
        Insert(ae[i].from,ae[i].to,ae[i].val);
    }
    Dfs(1,0,1);HDfs(1,1);
    sort(ae+1,ae+n,cmp);
    for(int a,b,i=1;i<n;i++) {
        a=Father(ae[i].from);
        b=Father(ae[i].to);
        train[a].Merge(train[b]);
        bln[b]=a;
        ans=Max(ans,train[a].len*ae[i].val);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

Contest5 T1

题意 :给你n个数,你要选出一个子集,使得子集异或和为0,且去掉任意一个元素后异或和不为0,问方案数。

数据范围 :n<=50,a[i]<=2^13

题解 :考虑dp,我们要求的条件可以转化为取反之后or全为1,设f[i][j]表示前i个数or值为j的方案数,直接dp会有不合法状态,我们就考虑容斥掉不合法状态,即强制一些状态不合法然后扣去。

具体题解

然而暴力跑得飞起233

代码 :无

Contest5 T2

题意:给你一个n个点的树,边上有一个字符,问你在给定的等价条件下,树中两个字符串是否相似。等价条件:每次询问时给出一些'a'='c','b'='c'之类的条件,具有传递性。树中字符串:字符串[a,b]定义为树中a到b最短路径上边上字符顺次写下得到的字符串。相似:两个字符串如果最多有一处不同则是相似的。

数据范围 :n<=1e5,单次询问等价条件个数<=10

题解 :哈希+树链剖分

代码:无

Contest5 T3

题意:给你一个字符串S,你需要维护这个字符串S并支持两种操作:

1、在字符串S末尾插入一个字符。

2、记字符串T为字符串S从第 l 个字符到第 r 个字符所构成的子串。询问字符串T中最长的子串使得该子串在T中出现过至少两次(例如:T="ababa",最长的子串应为aba,长度为3),并输出它的长度。如果不存在这样的子串,则输出0。

数据范围: 1n,q50000 强制在线

题解 : 不考虑插入的话。。。我们从1到n加入每个字符。。然后在加入第r个字符的时候回答所有形如[l:r]的询问。。以下的当前串均指s[1:r]。。

用后缀树(严格上来说是后缀自动机的parent树)维护。。记根到节点x形成的字符串为str[x]。。

考虑在每个后缀树节点x上维护一个last[x]表示str[x]在当前串中最后一次出现的右端点。。

每当加入一个字符s[r]的时候parent树上会加入一个叶节点u,此时我们应该把根到u的路径上的last都赋值为r。。

再仔细思考一下。。。会发现根据原last(记作last'[x])和r可以更新答案。。。(因为[last'[x]-len[x]+1:last'[x]]和[r-len[x]+1:r]是str[x]的最后两次出现。。。于是用一个线段树就可以维护答案了)

链赋值对应着LCT的基本操作access。。于是我们用LCT来维护这个last。。同一棵splay里的last一样。。。

于是就能维护答案了。。。

在线插入很简单。。因为答案是维护在线段树上的。。。把线段树可持久化就行了。。。

代码 : 无

原文地址:https://www.cnblogs.com/ihopenot/p/6640424.html