NOIP 模拟 921

DAY 1

优美的字符串

题目描述

给出一个只有'a'和'b'的字符串,希望通过最少的操作后使得所有'b'都在'a'前面。一种操作:每次选取一个'ab'子串变成'bba'。

答案对1e9+7取模,如果不能得到输出-1.

n<=1e6

题解

可以发现当两个'a'在一起的时候前面的'a'不可能变到后面去(废话),所以可以得出答案一定。

考虑先移动后面的'a',可以发现一个'a'移动的次数就是后面'b'的个数,并且这次移动后'b'的个数翻倍。

所以就记录现在'b'的个数和答案就OK了。

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=1000005;
const int mod=1000000007;
int n;
char s[maxn];
ll ans,cnt;//cnt:现在b的个数(包括变换产生的 

int main(){
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);
    scanf("%s",s+1);
    n=strlen(s+1);
    for(int i=n;i;i--){
        if(s[i]=='a'){
            ans=(ans+cnt)%mod;//移动cnt就好 
            cnt=2*cnt%mod;//每移动一次都会再带来一个b 
        }
        else cnt=(cnt+1)%mod;
    }
    printf("%lld",ans%mod);
    return 0;
}
string

数字谜题

诶,我做过。

为了让这一部分有意义,再介绍一下组合数的方法:

首先预处理还是一样的,我们需要找出有哪些在[1,1000]内的数可以通过k-1步到达1,然后加入一个vector。

然后对于加入的每个数进行组合数版的dfs,因为我们知道一共需要多少个1.

just like number


城市规划

题目描述

给出一个n个点n条边的图,保证联通。问删去一条边之后最长的路径最短是多少。

对于100% 数据,3<=n<=200000; 1<=w<=10e9

题解

CF835F

考虑暴力就是枚举环上的边,删去之后找直径,求最小的直径。(加上个卡时就看人品了)

考虑这就是一个基环树,想象将环摆在中间,环上的每个节点有一个树。

考虑对于删去一条边后什么会成为直径:每颗树的直径或者两个两棵树中到根最长链+两个根的距离。

第一个是无论我们删哪个边都不变的。

所以就考虑第二个怎么维护。

先把环按顺序存下来,假设有m个点,维护每个点到环上第一个点的距离qzh,环上每个点的树中到根最长长度l,如果删去$s_{i},s_{i+1}$的连边:

设x<y

在选取1..i的两个点,(lx-qzhx)+(ly+qzhy)

在i+1...m,和上面一样

在两部分分别选一个点,tot为环的边权和,tot+(lx+qzhx)+(ly-qzhy)

为了快速的求解,记录pre为第一种情况的前缀最大值,suf为第二种情况的后缀最大值,对于第三种就记录lx+qzhx,lt-qzhy的前缀最大值即可。

#include<ctime>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=200005;
const ll inf=10000000000000000;
int n,cnt,rt,head[maxn];
int top,s[maxn],dis[maxn];//存环和点到下个点的距离 
ll ret,f[maxn];//ret:树中的最大直径 
bool vis[maxn],circle[maxn];
struct edge{
    int x,y,next;
    ll val;
}e[maxn<<1];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x= f ? -x : x ;
}

void print(ll x){
    if(x<0) putchar('-'),x=-x;
    if(x>9) print(x/10);
    putchar(x%10+'0');
}

ll max(ll x,ll y){return x>y ? x : y ;}
ll min(ll x,ll y){return x<y ? x : y ;}

inline void add(int x,int y,int val){
    e[++cnt]=(edge){x,y,head[x],val};
    head[x]=cnt;
}

int dfs(int x,int fa){//找环 
    if(vis[x]) {rt=x;return 1;}
    vis[x]=true;
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].y;
        if(y==fa) continue;
        int tmp=dfs(y,x);
        if(tmp){
            if(tmp==1){
             s[++top]=x;
             dis[top]=e[i].val;
             circle[x]=true;
             if(x!=rt) return 1;
          }
          return 2;
        }
    }
    return 0;
}

ll tot,l[maxn],qzh[maxn];//每颗树中到根最长的链,环上权值和
ll pre[maxn],suf[maxn];
ll cpre[maxn],csuf[maxn];

void dfs1(int x,int fa,int root){
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].y;
        if(y==fa||circle[y]) continue;
        f[y]=f[x]+e[i].val;
        if(l[root]<f[y]) l[root]=f[y],rt=y;
        dfs1(y,x,root);
    }
}

void dfs2(int x,int fa,int root){
    for(int i=head[x];i;i=e[i].next){
        int y=e[i].y;
        if(y==fa) continue;
        if(circle[y]&&y!=root) continue;
        f[y]=f[x]+e[i].val;
        if(ret<f[y]) ret=f[y];
        dfs2(y,x,root);
    }
}

void find_d(int x){//树的直径 
    f[x]=0;
    dfs1(x,0,x);
    f[rt]=0;
    dfs2(rt,0,x);
}

void solve(){
    dfs(1,0);
    for(int i=1;i<=top;i++) find_d(s[i]);
    for(int i=2;i<=top;i++){
        qzh[s[i]]=qzh[s[i-1]]+dis[i];
        tot+=dis[i];
    }
    tot+=dis[1];
    
    ll cur=l[s[1]]-qzh[s[1]];
    for(int i=2;i<=top;i++){
        pre[s[i]]=max(pre[s[i-1]],cur+l[s[i]]+qzh[s[i]]);
        cur=max(cur,l[s[i]]-qzh[s[i]]);
    }
    
    cur=l[s[top]]+qzh[s[top]];
    for(int i=top-1;i;i--){
        suf[s[i]]=max(suf[s[i+1]],cur+l[s[i]]-qzh[s[i]]);
        cur=max(cur,l[s[i]]+qzh[s[i]]);
    }
    
    cpre[s[1]]=l[s[1]]+qzh[s[1]];
    for(int i=2;i<=top;i++)
      cpre[s[i]]=max(cpre[s[i-1]],l[s[i]]+qzh[s[i]]);
    
    csuf[s[top]]=l[s[top]]-qzh[s[top]];
    for(int i=top-1;i;i--)
      csuf[s[i]]=max(csuf[s[i+1]],l[s[i]]-qzh[s[i]]);
    
    ll ans=inf;
    for(int i=1;i<top;i++){
        ll tmp=ret;
        tmp=max(tmp,pre[s[i]]);
        tmp=max(tmp,suf[s[i+1]]);
        tmp=max(tmp,cpre[s[i]]+csuf[s[i+1]]+tot);
        ans=min(ans,tmp);
    }
    printf("%lld",ans);
}

int main(){
    bool opt=true;
    read(n);
    for(int i=1;i<=n;i++){
        int x,y;
        ll val;
        read(x);read(y);read(val);
        add(x,y,val);
        add(y,x,val);
    }
    solve();
    return 0;
}
city

但是CF没过,T了没看懂。


DAY 2

修路

有一个n个点m条边的无向图,边权为1,现加入一条边,问有多少种加边的方案不能使得s到t的距离变短。

加边(i,j)和(j,i)算一种,加边原图不能有。

对于100% 数据,2<=n<=1000; n-1<=m<=2000;1<=S,T<=n; S!=T。保证图联通。

题解

一开始想的很简单,因为n很小,求出两两之间的最短距离,用bfs求一次是O(n)的吧。

然后枚举边(i,j),判断是否有这条边就看dis[i][j]是否为1,为了检验这条路的影响,所以一定到走这条路,所以看s-i-j-y和s-j-i-y是否都不行即可。

但是发现每次固定的是s,t,而且枚举的边权为1,所以只要知道s到每个点和t到每个点的距离拼接即可,不过在判断是否原图有的时候需要多开一个数组记录。

#include<ctime>
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;

const int maxn=1006;
const int maxm=2006;
int n,m,dx,dy;
int cnt,head[maxn];
int dis[2][maxn];
bool mp[maxn][maxn];
struct edge{
    int x,y,next;
}e[maxm<<1];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

void add(int x,int y){
    e[++cnt]=(edge){x,y,head[x]};
    head[x]=cnt;
}

void bfs(int k,int st){
    queue<int> q;
    dis[k][st]=0;
    q.push(st);
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].y;
            if(dis[k][y]==-1){
                dis[k][y]=dis[k][x]+1;
                q.push(y);
            }
        }
    }
}

int main(){
    freopen("road.in","r",stdin);
    freopen("road.out","w",stdout);
    read(n);read(m);read(dx);read(dy);
    for(int i=1;i<=m;i++){
        int x,y;
        read(x);read(y);
        add(x,y);
        add(y,x);
        mp[x][y]=mp[y][x]=true;
    }
    memset(dis,-1,sizeof(dis));
    bfs(0,dx);bfs(1,dy);
    int now=dis[0][dy];
    cnt=0;
    for(int i=1;i<n;i++)
     for(int j=i+1;j<=n;j++)
      if(!mp[i][j])
          if(dis[0][i]+1+dis[1][j]>=now&&dis[0][j]+1+dis[1][i]>=now)
              cnt++;
    printf("%d",cnt);
}
road

神奇的集合

题目描述

有n个神奇的集合,编号为1-n,开始都为空,先进行两种操作:

将x加入编号[l,r]的集合,如果一个集合本来就有x,那么该集合所有元素个数加倍(并且就不用将这个x加进去

查询[l,r]的集合的元素和,对998244353取模。

对于100% 数据,1<=n; q<= 200000,1<=opt<=2,1<=l<=r<=n,1<=x<=n

题解

暴力就是各种分治。不过可以发现一点就是如果这个集合本来有一个元素,那么这次就+1,不然就*2.

所以开n颗线段树维护对于元素i每个位置的情况,再开一个答案线段树。

考虑区间修改,如果当前区间都没有x,就区间+1,并且记录区间有了x;如果都有x,就区间*2;不然就继续递归。

一个区间最多被赋值一次,所以时间复杂度O(nlogn)(???)

#include<map>
#include<ctime>
#include<queue>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
const int maxn=200005;
const int mod=998244353;
int n,m,cnt;
int root[maxn],ls[maxn<<5],rs[maxn<<5];
ll tag[maxn<<5],mul[maxn<<5],sum[maxn<<5];
int num[maxn<<5];
bool lazy[maxn<<5];

template<class T>inline void read(T &x){
    x=0;int f=0;char ch=getchar();
    while(!isdigit(ch)) {f|=(ch=='-');ch=getchar();}
    while(isdigit(ch)) {x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    x = f ? -x : x ;
}

void print(ll x){
    if(x<0) x=-x,putchar('-');
    if(x>9) print(x/10);
    putchar(x%10+'0');
}

void update1(int rt){
    sum[rt]=(sum[ls[rt]]+sum[rs[rt]])%mod;
}

void update2(int rt){
    num[rt]=num[ls[rt]]+num[rs[rt]];
}

void put_lazy(int &rt,int l,int r){
    if(!rt) rt=++cnt;
    lazy[rt]=true;
    num[rt]=r-l+1;
}

void push_down2(int rt,int l,int r){
    int mid=(l+r)>>1;
    put_lazy(ls[rt],l,mid);
    put_lazy(rs[rt],mid+1,r);
    lazy[rt]=false;
}

void put_mul(int &rt,ll val){
    if(!rt) {rt=++cnt;mul[rt]=1;}
    sum[rt]=sum[rt]*val%mod;
    mul[rt]=mul[rt]*val%mod;
    tag[rt]=tag[rt]*val%mod;
}

void put_tag(int &rt,int l,int r,ll val){
    if(!rt) {rt=++cnt;mul[rt]=1;}
    sum[rt]=(sum[rt]+val*(r-l+1))%mod;
    tag[rt]=(tag[rt]+val)%mod;
}

void push_down1(int rt,int l,int r){
    int mid=(l+r)>>1;
    if(mul[rt]!=1){
        put_mul(ls[rt],mul[rt]);
        put_mul(rs[rt],mul[rt]);
        mul[rt]=1;
    }
    if(tag[rt]){
        put_tag(ls[rt],l,mid,tag[rt]);
        put_tag(rs[rt],mid+1,r,tag[rt]);
        tag[rt]=0;
    }
}

void modify(int &rt1,int &rt2,int l,int r,int a_l,int a_r){
    if(!rt1) {rt1=++cnt;mul[rt1]=1;}
    if(!rt2) rt2=++cnt;
    if(a_l<=l&&r<=a_r){
        if(!num[rt2]) {put_lazy(rt2,l,r),put_tag(rt1,l,r,1);return ;}
        else if(num[rt2]==r-l+1) {put_mul(rt1,2);return ;}
    }
    int mid=(l+r)>>1;
    if(lazy[rt2]) push_down2(rt2,l,r);
    push_down1(rt1,l,r);
    if(a_l<=mid) modify(ls[rt1],ls[rt2],l,mid,a_l,a_r);
    if(mid<a_r) modify(rs[rt1],rs[rt2],mid+1,r,a_l,a_r);
    update1(rt1);
    update2(rt2);
}

ll query(int rt,int l,int r,int a_l,int a_r){
    if(a_l<=l&&r<=a_r) return sum[rt];
    push_down1(rt,l,r);
    ll ret=0;
    int mid=(l+r)>>1;
    if(a_l<=mid) ret+=query(ls[rt],l,mid,a_l,a_r);
    if(mid<a_r) ret+=query(rs[rt],mid+1,r,a_l,a_r);
    return ret%mod;
}

int main(){
    freopen("multiset.in","r",stdin);
    freopen("multiset.out","w",stdout);
    read(n);read(m);
    for(int i=1;i<=m;i++){
        int opt,l,r;
        read(opt);read(l);read(r);
        if(opt==1){
            int x;read(x);
            modify(root[0],root[x],1,n,l,r);
        }
        else printf("%lld
",query(root[0],1,n,l,r));
    }
}
multiset

还有空间开大点。


缩树游戏

给出一个n个点的树,每次等概率选一条边(x,y)删去,并且等概率留下其中一个节点,这个节点和所有与x和y连边的点连边。对于每个点求最后剩下的是它的概率。

2<=n<=50

题解

......

 

原文地址:https://www.cnblogs.com/sto324/p/11573625.html