noip模拟题7

T1:匹配

image
##思路:
  首先,这道题既可以用KMP,也可以用hash
  先说KMP,首先要注意的一点是:KMP的next数组求出的是boder,即既是这个串的真后缀又是真前缀,所以,对于以下类似的样例,他的输出为0,但实际是3

A:abc B:ab 增添的字符:c

  所以说,要把两个串按照A在前,B在后的顺序接起来,再跳next数组。
  再说hash,先求出两个串的hash数组,然后枚举长度匹配即可,注意ans初值一定要赋成0,对每一个匹配成功的长度取max即可。

T2:回家

思路

  这道题我在考场上想出正解了,但是Tarjan忘了怎么打了,直接WA,这题一看就是Tarjan判割点,但要注意的是,并不是所有的割点都会是答案。考虑下面的例子,节点编号为1~10;

连边有:
(1,2),(1,3),(2,4),(3,4),(4,5),(4,10);
(5,6)(10,6),(6,7),(6,8),(7,9),(8,9);

  可以发现,4,6都是割点,但答案只有4,因为6虽然是割点,但从1到10不会经过6,所以6不是必经点,由此我们发现,并不是所有割点都是必经点,要判断。
  这里有一个技巧,感谢信队提供(%%信队)的技巧。
  就是在Tarjan回溯的时候加一个判断,判断这个割点是否与n有关。详见代码。

上代码:

#include<bits/stdc++.h>
using namespace std;
namespace Decleration{
    #define ll long long
    #define rr register 
    const int SIZE=2e5+5;
    int T,n,m;
    int last[SIZE];
    struct edge{int t,last;}a[SIZE<<2];
    int read(){
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9'){
            if(c_read=='-') y_read=-1;
            c_read=getchar();}
        while(c_read<='9'&&c_read>='0'){
            x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
            c_read=getchar();}
        return x_read*y_read;}};
using namespace Decleration;
namespace Tarjan{
    int dfn[SIZE],low[SIZE];
    bool pd[SIZE],dot[SIZE];
    int cnt,no,root;
    void tarjan(int x){
        dfn[x]=low[x]=++cnt;
        int flag=0;
        for(int i=last[x];i!=-1;i=a[i].last){
            int y=a[i].t;
            if(!dfn[y]){
                tarjan(y);
                low[x]=min(low[x],low[y]);
                if(low[y]>=dfn[x]){                   
                   flag++;
                   if((x!=root||flag>1)&&pd[y]) dot[x]=1;}
                if(pd[y]) pd[x]=1;
                //假如说x的儿子与n有关,就是说他能通到n,那他就是1,因为pd[n]是一                
                //pd[n]会把pd[y]更新为1,由此上推,所有能通到n的点的pd都会变成
                //1,此时如果他是割点,那他就是必经点   
            }
            else low[x]=min(low[x],dfn[y]);
        }}
};
using namespace Tarjan;
void Pre_Deal(){
    memset(last,-1,sizeof(last));
    memset(dfn,0,sizeof(dfn));
    memset(low,0,sizeof(low));
    memset(pd,0,sizeof(pd));
    memset(dot,0,sizeof(dot));
    cnt=no=root=0;}
int main(){
    T=read();
    while(T--){
        n=read(),m=read();
        Pre_Deal();
        int num=0;
        for(rr int i=1;i<=m;i++){
            int u=read(),v=read();
            if(u==v) continue;
                a[++num].t=u;
                a[num].last=last[v];
                last[v]=num;
                a[++num].t=v;
                a[num].last=last[u];
                last[u]=num;}
        pd[n]=1;
        tarjan(1);        
        for(rr int i=2;i<n;i++)
            if(dot[i]) no++;
        printf("%d\n",no);
        if(no){
            for(rr int i=2;i<n;i++)
                if(dot[i]) printf("%d ",i);}
        printf("\n");}
}

T3:寿司

在这里插入图片描述

基本思路:

  首先一个贪心策略:对于每一个R我们都让他与最近的R靠近。
  考虑到这是一个环,我们将转为序列,利用类似于滚动窗口的方式,将RB串循环右移,直到恢复初始的RB串,一共循环len次。
  对于每一个右移出的结果,我们记l[i]为每一个R的左边的B的数量,r[i]为右边的,sumb为B的总数,sumr为R的总数。
  定义x[i]=l[i]-r[i],有:

ans=min( ∑ i = 1 s u m r \sum_{i=1}^{sumr} i=1sumrmin(l[i],r[i]));

  考虑求和的优化,有:

∑ i = 1 s u m r \sum_{i=1}^{sumr} i=1sumrmin(l[i],r[i])
= ∑ i = 1 s u m r \sum_{i=1}^{sumr} i=1sumr((l[i]+r[i]-|x[i]|)/2)

  显然有

l[i]+r[i]=sumb;

  记sum= ∑ i = 1 s u m r \sum_{i=1}^{sumr} i=1sumrx[i];

原式=(sumb*sumr-sum)>>1;

  那么就有:

ans=min((sumr*sumb-sum)>>1);

  sumb与sumr为定值但sum要实时维护,详见代码。

上代码:

#include<bits/stdc++.h>
using namespace std;
namespace Decleration{
    #define ll long long
    #define rr register 
    const int SIZE=1e6+4;    
    int T;
    char s[SIZE];
    int x[SIZE];
    int l[SIZE],r[SIZE];
    int num0,num1;
   
    inline int read(){
        rr int x_read=0,y_read=1;
        rr char c_read=getchar();
        while(c_read<'0'||c_read>'9'){
            if(c_read=='-') y_read=-1;
            c_read=getchar();}
        while(c_read<='9'&&c_read>='0'){
            x_read=(x_read<<3)+(x_read<<1)+(c_read^48);
            c_read=getchar();}
        return x_read*y_read;}
    };
using namespace Decleration;
int main(){    
    T=read();
    while(T--){
        memset(s,0,sizeof(s));
        memset(x,0,sizeof(x));
        memset(l,0,sizeof(l));
        memset(r,0,sizeof(r));
        scanf("%s",s+1);
        int len=strlen(s+1);
        int num=0; 
        ll sumb=0,sumr=0,sum=0;
        num1=num0=0; 
        //num1为x[i]中负数的个数,num0为正数
        priority_queue<int,vector<int>,less<int> > q;
        //大根堆维护x[i]中的负值
        for(rr int i=1;i<=len;i++)
            if(s[i]=='B') sumb++;
            else num++,l[num]=sumb,sumr++;
        for(rr int i=1;i<=num;i++){
            r[i]=sumb-l[i],x[i]=l[i]-r[i];                
            sum+=abs(x[i]);
            if(x[i]>=0) num0++;//??
            else num1++,q.push(x[i]);}      
        ll ans=LONG_LONG_MAX;
        int delta=0;
        //delta必须有,因为无法直接适时改变堆里的元素
        //是整个q集合元素里的偏移值,就是他会加上的值
        ans=min(ans,(sumb*sumr-sum)/2);
        for(rr int i=len;i>=1;i--){
            if(s[i]=='B'){
                delta+=2;   
                sum+=(num0*2-num1*2);
                //每个移到序列首的B都会使所有的l[i]加1,r[i]减一,则x[i]会+2
                //对于负的x[i],|x[i]|-2,对于非负的x[i],|x[i]|+2;
                while(!q.empty()&&q.top()+delta>=0){
                    num0++,num1--;
                    if(q.top()+delta==1) sum+=2;
                    //-1是特例,加2后绝对值不变,要减去
                    q.pop();
                    //大根堆的作用是适时将变为正值的x[i]排除
                }
               }
            else num0--,num1++,q.push(-sumb-delta);
            //delta会一直递增下去,所以-sumb要-delta
            ans=min(ans,(sumb*sumr-sum)/2);
            } 
        printf("%lld\n",ans);
    }
}

2021.6.11 现役

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值