9.26数据结构练习赛

没睡醒的状态下迷迷糊糊地考了rank1,大概是因为做到原题了吧,看来OI还是需要题海战术的(o^∇^o)ノ

加帕里的聚会
256MB / 1s ; japari.cpp / c / pas / in / out
【题目描述】
加帕里公园里有n个区域,n-1条道路将它们连接到了一起,形成了一个树的结构。开始时,第i个区域有Ai个friends,但是由于砂之星的作用,有时从x区域到y区域的简单路径上的所有区域的friends数量都会增加v,有时从x区域到y区域的简单路径上所有区域的friends数量都会变成v。
有时,从x区域到y区域的简单路径上所有区域的friends想要聚会,聚会时需要从所有参加聚会的friends中选择一部分作为staff。加帕里的friends都很喜欢质数,因此她们希望staff和非staff的参与者的数量都是质数。
请你告诉她们,每次聚会是否存在一种方案满足她们的要求。

【输入格式】
第一行两个整数n,m,表示friends数量和事件数量。
接下来一行n个数,第i个数表示Ai。
接下来n-1行,每行2个数x,y,表示x和y之间有一条无向道路。
接下来m行,每行第一个整数opt表示事件类型。
若opt=1,接下来三个整数x,y,v,表示从x区域到y区域的简单路径上的所有区域的friends数量都增加v。
若opt=2,接下来三个整数x,y,v,表示从x区域到y区域的简单路径上的所有区域的friends数量都变为v。
若opt=3,接下来两个整数x,y,表示从x区域到y区域的简单路径上的所有区域的friends进行聚会。

【输出格式】
对于每个3事件,若可以让staff和非staff的数量都为质数,输出一行SUGOI,否则输出一行TANOSHI。

【样例数据】
japari1.in
3 4
1 2 1
2 1
3 2
2 2 1 4
1 3 3 -1
2 2 2 2
3 1 3
japari1.out
SUGOI
japari2.in
4 6
2 4 0 0
2 1
3 2
4 3
2 1 4 2
1 4 4 9
1 3 2 -2
2 4 2 5
3 1 4
3 4 4
japari2.out
TANOSHI
SUGOI

【样例解释】
在第一个样例中,三个区域的friends数量依次为4、2、0,询问的friends和为6,可以分成两组,每组的friends数量都为3。
在第二个样例中,四个区域的friends数量依次为2、5、5、5,第一个询问的friends和S为17,无法分成两组。第二个询问的friends和S为5,可以分为两组,每组的friends数量分别为2、3。

【数据范围】
对于30%的数据,n,m≤5000。
对于另30%的数据,对于i>1,第i个区域与第i-1个区域直接相连。
对于100%的数据,1≤n,m≤100000,1≤x,y≤n,一直满足0≤Ai,S≤10000000。在增加事件中v可能为负数。

题解:裸的树链剖分,注意线段树两种互相会产生冲突的标记下传的顺序。虽说是道烦人的码农题,但是确实巩固了基础(ง •̀_•́)ง。
P.S.建议标记初始值都赋成-INF

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define root 1,1,n
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
const int MAX=1e7+4,MAXN=1e5+4,INF=0x3f3f3f3f;
bool vis[MAX],ok[MAX];
int n,m,prime[MAX/10],tot=0;
int top[MAXN],dep[MAXN],fa[MAXN],son[MAXN],siz[MAXN],head[MAXN],edge=0;
int tid[MAXN],rk[MAXN],num[MAXN],tim=0;
struct EDGE {
    int v,nxt;
}e[MAXN<<1];
inline void adde(int u,int v) {
    e[edge].nxt=head[u],e[edge].v=v,head[u]=edge++;
    e[edge].nxt=head[v],e[edge].v=u,head[v]=edge++;
}
/*------------pre_process------------*/
inline void linear_shaker() {
    memset(ok,false,sizeof(ok));
    memset(vis,false,sizeof(vis));
    for (register int i=2;i<MAX;++i) {
        if (!vis[i]) prime[++tot]=i;
        for (int j=1;j<=tot&&i*prime[j]<MAX;++j) {
            vis[i*prime[j]]=true;
            if (i%prime[j]==0) break;
        }
    }
    for (int i=4;i<MAX;i+=2) ok[i]=true;
    for (int i=1;i<=tot;++i) ok[prime[i]+2]=true;
}
/*------------DCP------------*/
inline void dfs1(int p,int father,int d) {
    dep[p]=d,fa[p]=father,siz[p]=1;
    for (int i=head[p];~i;i=e[i].nxt) {
        int v=e[i].v;
        if (v==father) continue;
        dfs1(v,p,d+1);
        siz[p]+=siz[v];
        if (son[p]==-1||siz[v]>siz[son[p]])
            son[p]=v;
    }
}
inline void dfs2(int p,int tp) {
    top[p]=tp,tid[p]=++tim,rk[tid[p]]=p;
    if (son[p]==-1) return ; 
    dfs2(son[p],tp);
    for (int i=head[p];~i;i=e[i].nxt) {
        int v=e[i].v;
        if (v!=fa[p]&&v!=son[p])
            dfs2(v,v);
    }
}
/*------------Segment Tree------------*/
int sum[MAXN<<2],set[MAXN<<2],laz[MAXN<<2];//add/change
inline void pushup(int rt) {
    sum[rt]=sum[rt<<1]+sum[rt<<1|1];
}
inline void pushdown(int rt,int len) {
    if (set[rt]!=-INF) {
        sum[rt<<1]=set[rt]*(len-(len>>1));
        sum[rt<<1|1]=set[rt]*(len>>1);      
        set[rt<<1]=set[rt<<1|1]=set[rt];
        laz[rt<<1]=laz[rt<<1|1]=0;
        set[rt]=-INF;
    }
    if (laz[rt]!=-INF) {
        sum[rt<<1]+=laz[rt]*(len-(len>>1));
        sum[rt<<1|1]+=laz[rt]*(len>>1);
        if (laz[rt<<1]!=-INF) laz[rt<<1]+=laz[rt];
        else laz[rt<<1]=laz[rt];
        if (laz[rt<<1|1]!=-INF) laz[rt<<1|1]+=laz[rt];
        else laz[rt<<1|1]=laz[rt];
        laz[rt]=-INF;
    }
}
void build(int rt,int l,int r) {
    set[rt]=-INF,laz[rt]=-INF;
    if (l==r) {sum[rt]=num[rk[l]];return ;}
    int mid=(l+r)>>1;
    build(lson),build(rson);
    pushup(rt);
}
void add(int rt,int l,int r,int L,int R,int val) {
    if (L<=l&&r<=R) {
        sum[rt]+=val*(r-l+1);
        if (laz[rt]==-INF) laz[rt]=val;
        else laz[rt]+=val;
        return ;
    }
    pushdown(rt,r-l+1);
    int mid=(l+r)>>1;
    if (L<=mid) add(lson,L,R,val);
    if (mid<R) add(rson,L,R,val);
    pushup(rt);
}
void change(int rt,int l,int r,int L,int R,int val) {
    if (L<=l&&r<=R) {
        sum[rt]=val*(r-l+1);
        laz[rt]=-INF,set[rt]=val;
        return ;
    }
    pushdown(rt,r-l+1);
    int mid=(l+r)>>1;
    if (L<=mid) change(lson,L,R,val);
    if (mid<R) change(rson,L,R,val);
    pushup(rt);
}
int query(int rt,int l,int r,int L,int R) {
    if (L<=l&&r<=R) return sum[rt];
    pushdown(rt,r-l+1);
    int mid=(l+r)>>1,res=0;
    if (L<=mid) res+=query(lson,L,R);
    if (mid<R) res+=query(rson,L,R);
    return res;
}
void modify(int x,int y,int val,int type) {
    while (top[x]!=top[y]) {
        if (dep[top[x]]<dep[top[y]]) x^=y^=x^=y;
        if (type==1) add(root,tid[top[x]],tid[x],val);
        else change(root,tid[top[x]],tid[x],val);
        x=fa[top[x]];
    }
    if (dep[x]<dep[y]) x^=y^=x^=y;
    if (type==1) add(root,tid[y],tid[x],val);
    else change(root,tid[y],tid[x],val);
}
int ask(int x,int y) {
    int res=0;
    while (top[x]!=top[y]) {
        if (dep[top[x]]<dep[top[y]]) x^=y^=x^=y;
        res+=query(root,tid[top[x]],tid[x]);
        x=fa[top[x]];
    }
    if (dep[x]<dep[y]) x^=y^=x^=y;
    res+=query(root,tid[y],tid[x]);
//  printf("%d\n",res);
    return res;
}
inline int read() {
    int x=0,f=1;char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
int main() {
    freopen("japari.in","r",stdin);
    freopen("japari.out","w",stdout);
//  printf("memory==%d\n",sizeof(prime)+sizeof(ok)<<1);
    linear_shaker();
    memset(head,-1,sizeof(head));
    memset(son,-1,sizeof(son));
    n=read(),m=read();
    for (register int i=1;i<=n;++i) num[i]=read();
    for (register int i=1;i<n;++i) {
        int u=read(),v=read();
        adde(u,v);
    }
    dfs1(1,0,0);
    dfs2(1,1);
    build(root);
    for (register int cv=0;cv<m;++cv) {
        int type=read();
        switch (type) {
            case 1:{
                int x=read(),y=read(),v=read();
                modify(x,y,v,1);
                break;
            }
            case 2:{
                int x=read(),y=read(),v=read();
                modify(x,y,v,2);
                break;
            }
            case 3:{
                int x=read(),y=read();
                int ans=ask(x,y);
                puts(ok[ans]?"SUGOI":"TANOSHI");
                break;
            }
        }
    }
    return 0;
}

黑白熊的密码
256MB / 1s ; monokuma.cpp / c / pas / in / out
【题目描述】
苗木诚发现了脱出装置,它可以帮助所有人逃脱希望之峰学园。但是要想使用它,必须输入一个十进制的n位数密码(没有前导0)。为了让游戏变得更加有趣,黑白熊提供了m条信息,其中第i条信息指出这个数的第Li位到Ri位和第li位到ri位完全相同。
苗木诚决定随机选择一个满足要求的n位数输入。请你告诉他他碰对密码的概率是多少。

【输入格式】
第一行两个整数n,m,表示数的位数和黑白熊提供的信息数。
接下来m行,每行四个正整数Li,Ri,li,ri。

【输出格式】
输出一行一个整数,表示苗木诚碰对密码的概率,对998244353取模。

【样例数据】
monokuma.in
4 2
1 2 3 4
3 3 3 3
monokuma.out
144190851

【样例解释】
答案是1/90在模998244353意义下的值。

【数据范围】
对于30%的数据,n,m≤2000。
对于100%的数据,1≤n,m≤100000,1≤Li≤Ri≤n,1≤li≤ri≤n,且保证Ri-Li=ri-li。

题解:见本人bzoj4569的博客,大概就是每对区间用倍增操作使合并n次优化为合并logn次,再将所有的区间能合并的合并一次,最后统计答案,求逆元。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=1e5+2,MOD=998244353;
int n,m;
int fa[MAXN*20],id[MAXN][20],mk[MAXN*20][2],pw[20],tot=0,cnt=0,ans;
int find(int x) {
    return fa[x]?fa[x]=find(fa[x]):x;
}
int merge(int x,int y) {
    x=find(x);
    y=find(y);
    if (x!=y) fa[y]=x;
}
inline int read() {
    int x=0;char c=getchar();
    while (c<'0'||c>'9') c=getchar();
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x;
}
int fpow(int a,int b,int p) {
    int ret=1;
    while (b) {
        if (b&1) ret=(ll)ret*a%p;
        b>>=1,a=(ll)a*a%p;
    }
    return ret;
}
int main() {
    freopen("monokuma.in","r",stdin);
    freopen("monokuma.out","w",stdout);
    memset(fa,0,sizeof(fa));
    pw[0]=1;for (int i=1;i<=17;++i) pw[i]=pw[i-1]*2;
    n=read(),m=read();
    if (n==1) ans=10;
    else {
        for (register int i=1;i<=n;++i)
            for (int j=0;j<=17;++j)
                id[i][j]=++tot,mk[tot][0]=i,mk[tot][1]=j;
        for (int i=1;i<=m;++i) {
            int l1=read(),r1=read(),l2=read(),r2=read();
            for (int j=17;j>=0;--j)
                if (l1+pw[j]-1<=r1) {
                    merge(id[l1][j],id[l2][j]);
                    l1+=pw[j];l2+=pw[j];
            }
        }
        for (int j=17;j>0;--j)
            for (register int i=1;i<=n;++i) {
                int k=find(id[i][j]);
                int s=mk[k][0];
                merge(id[s][j-1],id[i][j-1]);
                merge(id[s+pw[j-1]][j-1],id[i+pw[j-1]][j-1]);
            }
        for (register int i=1;i<=n;++i)
            if (!fa[id[i][0]]) ++cnt;
        ans=9;
        for (register int i=1;i<cnt;++i) ans=(ll)ans*10%MOD;
    }
    printf("%d\n",fpow(ans,MOD-2,MOD));
    return 0;
}

初音未来的巡游
128MB / 1s ; cruise.cpp / c / pas / in / out
【题目描述】
Miku决定在n个地点中选择一些地点进行巡游。这n个地点由n-1条道路连接,两两之间有且仅有一条路径可以互相到达。Miku希望在这些道路中选择一些放上葱,使得Miku可以选择一种方式只经过有葱的道路而巡游完所有她选择的地点(一条道路可以被多次经过,起点任选)。
Miku想知道她至少需要准备多少葱。由于她的巡游计划可能更改,她希望你能在更改后即时回答她的疑问。

【输入格式】
第一行两个整数n,m,表示地点数和事件数。
第2至n行,每行两个整数x,y,表示地点x和地点y之间有一条无向道路。
接下来一行n个0/1数,若第i个数为1则表示i号地点初始时被选,为0则表示不选。
接下来一行m个整数,依次表示修改事件。第i个数Ai表示Ai号地点的状态发生变化,即若当前被选则改为不选,当前不选则改为被选。

【输出格式】
输出m行,第i行一个整数表示第i次事件后初音最少需要准备多少葱。

【样例数据】
cruise.in
5 8
1 2
1 3
2 4
2 5
1 0 0 1 0
5 4 2 1 2 5 3 2
cruise.out
3
2
2
1
0
0
0
2

【数据范围】
对于30%的数据,n,m≤3000。
对于另30%的数据,开始时所有地点都不选,保证修改操作的地点当前是不选状态。
对于100%的数据,1≤n,m≤200000,1≤x,y,Ai≤n。

题解:可以证明(目前本蒟蒻只能直观理解)答案的两倍为按DFS序排序后相邻两个选择点在树上的距离之和加上首尾点的树上距离。预处理LCA,使用set维护选择的点,进行修改时根据DFS序的前驱和后继点更新答案即可。可以参考bzoj3991,几乎是一样的。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
const int MAXN=200005;
int n,m;
int head[MAXN],edge=0,ans=0,root;
struct EDGE {
    int v,nxt;
}e[MAXN<<1];
int tid[MAXN],rk[MAXN],dep[MAXN],tim=0,f[20][MAXN];
int has[MAXN];
inline int read() {
    int x=0,f=1;char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
    while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
    return x*f;
}
inline void adde(int u,int v) {
    e[edge].nxt=head[u],e[edge].v=v,head[u]=edge++;
    e[edge].nxt=head[v],e[edge].v=u,head[v]=edge++;
}
void dfs(int p,int fa) {
    tid[p]=++tim,rk[tim]=p;
    for (int i=head[p];~i;i=e[i].nxt) {
        int v=e[i].v;
        if (v^fa)
            f[0][v]=p,dep[v]=dep[p]+1,dfs(v,p);
    }
}
inline void da() {
    for (int j=1;(1<<j)<=n;++j)
        for (int i=1;i<=n;++i)
            f[j][i]=f[j-1][f[j-1][i]];
}
inline int lca(int x,int y) {
    if (dep[x]<dep[y]) x^=y^=x^=y;
    int t=dep[x]-dep[y];
    for (int i=0;i<=18;++i)
        if (t&(1<<i)) x=f[i][x];
    if (x==y) return x;
    for (int i=18;~i;--i)
        if (f[i][x]^f[i][y]) x=f[i][x],y=f[i][y];
    return f[0][x];
}
inline int dis(int x,int y) {
//  printf("%d %d %d\n",x,y,lca(x,y));
    return dep[x]+dep[y]-(dep[lca(x,y)]<<1);
}
set<int> s;
set<int>::iterator it;
inline int ne(int x) {
    it=s.find(tid[x]);
    return ++it==s.end()?0:rk[*it];
}
inline int pr(int x) {
    it=s.find(tid[x]);
    return it==s.begin()?0:rk[*--it];
}
inline void add(int x) {
    s.insert(tid[x]);
    int l=pr(x),r=ne(x);
    if (l) ans+=dis(l,x);
    if (r) ans+=dis(r,x);
    if (l&&r) ans-=dis(l,r);
}
inline void del(int x) {
    int l=pr(x),r=ne(x);
    if (l) ans-=dis(l,x);
    if (r) ans-=dis(r,x);
    if (l&&r) ans+=dis(l,r);
    s.erase(tid[x]);
}
int main() {
    freopen("cruise.in","r",stdin);
    freopen("cruise.out","w",stdout);
    memset(has,false,sizeof(has));
    memset(head,-1,sizeof(head));
    n=read(),m=read();
    for (register int i=1;i<n;++i) {
        int u=read(),v=read();
        adde(u,v);
    }
    dep[1]=0;
    dfs(1,0);
    da();
    for (register int i=1;i<=n;++i) {
        int x=read();
        if (x) add(i),has[i]=1;
    }
    while (m--) {
        int x=read();
        if (has[x]) del(x);
        else add(x);
        has[x]^=1;
        printf("%d\n",s.size()?(ans+dis(rk[*s.begin()],rk[*--s.end()]))>>1:0);
//      cout<<"size=="<<s.size()<<endl;
    }
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值