首页 > 其他分享 >【做题纪要】冲刺NOIP逆天题单之字符串篇

【做题纪要】冲刺NOIP逆天题单之字符串篇

时间:2024-04-16 15:14:56浏览次数:25  
标签:putchar NOIP temp int text 逆天题 len char 纪要

幽默题目,冲刺NOIp全是SA和SAM,冲刺NOIp一不小心把p冲没了,成NOI了

甚至有上个题单没有出现的CF评分*3300,幽默

Yet Another LCP Problem

题意

给出一个长度为 \(n\) 的字符串 \(S\) 和 \(q\) 次询问,每次询问给出一个集合 \(A\) 和 \(B\) 求 \(\sum\limits_{i\in A}\sum\limits_{j\in B}\text{LCP}(i,j)\)

逆天冲刺NOIP题单可做题之九(字符串之一)

看到这道题第一想法就是用 \(\text{SA}\),毕竟已经出现求后缀的 \(\text{LCP}\) 等字样了

考虑如何 \(\text{SA}\),先排序然后求 \(\text{height}\) 数组没啥好说的,突然发现这不是我们 [AHOI2013]差异

直接建 \(\text{ST}\) 表,然后跑单调栈似乎就过去了,复杂度 \(O(n \log n)\) 完全可过

首先我们需要知道一个定理

\[\text{LCP}(x,y)=\min_{x+1\le i\le y}\{h_i\} \]

拿到这个定理就很明显了吧这道题,直接挂 \(\text{ST}\) 表就能 \(\text O(1)\) 查询 \(\text{LCP}\) 了

但是如果我们用这个定理直接去暴力枚举肯定寄了,复杂度是 \(\text O(n^2)\) 的,今天你和 \(\text{LCP}\) 只能活一个

我们发现其实就是求几个区间的 \(\min\) 之和,然后随便用单调栈一类的维护一下就过了,参考 [AHOI2013]差异

点击查看代码
namespace Solve{
    inline int read(){
        int s=0,w=1;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
        return s*w;
    }
    inline void write(const int x){
        if(x>=10) write(x/10);
        putchar(x%10+'0');
    }
    inline void print(int x,string s){
        if(x<0) putchar('-');
        write(x<0?-x:x);
        int len=s.size();
        for_(i,0,len-1) putchar(s[i]);        
    }
    int n,Q,l[N],k[N];
    char s[N];
    namespace SA{
        int sa[N],rk[N],oldrk[N],oldsa[N],w,cnt[N],key[N];
        int height[N];
        inline bool cmp(int x,int y,int w){
            return (oldrk[x]==oldrk[y])&&(oldrk[x+w]==oldrk[y+w]);
        }
        inline void Init(char *s){
            int n=strlen(s+1),m=127,tot;
            for_(i,1,n) 
                rk[i]=s[i],
                ++cnt[rk[i]];
            for_(i,1,m) 
                cnt[i]+=cnt[i-1];
            _for(i,n,1) sa[cnt[rk[i]]--]=i;
            for(int w=1;;w<<=1,m=tot) {
                tot=0;
                _for(i,n,n-w+1) 
                    oldsa[++tot]=i;
                for_(i,1,n)
                    if(sa[i]>w) 
                        oldsa[++tot]=sa[i]-w;
                memset(cnt,0,sizeof(cnt));
                for_(i,1,n) 
                    ++cnt[key[i]=rk[oldsa[i]]];
                for_(i,1,m) 
                    cnt[i]+=cnt[i-1];
                _for(i,n,1) 
                    sa[cnt[key[i]]--]=oldsa[i];
                memcpy(oldrk+1,rk+1,n*sizeof(int));
                tot=0;
                for_(i,1,n)
                    rk[sa[i]]=((cmp(sa[i],sa[i-1],w))?(tot):(++tot));
                if(tot==n)
                    break;
            }
        }
        inline void Init_H(char *s){
            int tot=0,n=strlen(s+1);
            for_(i,1,n){
                if(rk[i]==0) continue;
                if(tot) --tot;
                while(s[i+tot]==s[sa[rk[i]-1]+tot]) ++tot;
                height[rk[i]]=tot;
            }
        }
    }
    using namespace SA;
    class ST{
    public:
        int Min[500000][20],Log[N];
        inline void init(){
            for_(i,1,n) {
                Log[i]=log(i)/log(2);
            }
            for_(i,1,n) {
                Min[i][0]=height[i];
            }
            for_(j,1,Log[n]) {
                for_(i,1,n-(1<<j)+1) {
                    Min[i][j]=min(Min[i][j-1],Min[i+(1<<j-1)][j-1]);
                }
            }
        }
        inline int Query(int l,int r){
            return min(Min[l][Log[r-l+1]],Min[r-(1<<(Log[r-l+1]))+1][Log[r-l+1]]);
        }
    }TrEE;
    PII a[N];
    inline bool Cmp(PII a,PII b){
        return (a.first==b.first)?(a.second<b.second):(a.first<b.first);
    }
    int top,q[N],c[N], Sum,ans,Ans[N];
    inline void solve(int m,int M){
        int cnt=0;
        for_(i,1,M) 
            a[++cnt]=mp(rk[read()],1);
        for_(i,1,m) 
            a[++cnt]=mp(rk[read()],0);
        sort(a+1,a+1+cnt,Cmp);
        ans=top=Sum=0;
        a[cnt+1]=mp(-1,0);
        int r=cnt;
        while(a[r].second==1) r--;
        _for(i,r,1){
            if(i!=r){
                int rk=TrEE.Query(a[i].first+1,a[i+1].first),s=a[i+1].second^1;
                while(rk<=q[top]&&top){ 
                    Sum-=c[top]*q[top],
                    s+=c[top--];
                }
                q[++top]=rk,c[top]=s,Sum+=1ll*s*rk;
            }
            if (a[i].second) 
                ans+=Sum;
            else if(a[i+1].first==a[i].first) 
                ans+=n-sa[a[i].first]+1;
        }
        for_(i,2,cnt) 
            if(a[i].first==a[i-1].first) 
                swap(a[i],a[i-1]);
        top=Sum=0;r=1;
        while(a[r].second==1) 
            r++;
        for_(i,r,cnt){
            if(i!=r){
                int rk=TrEE.Query(a[i-1].first+1,a[i].first),s=a[i-1].second^1;
                while((rk<=q[top])&&(top!=0)) {
                    Sum-=c[top]*q[top];
                    s+=c[top--];
                }
                q[++top]=rk,c[top]=s,Sum+=1ll*s*rk;
            }
            if(a[i].second) ans+=Sum;
        }
    }
    inline void In(){
        n=read(),Q=read();
        scanf("%s",s+1);
        Init(s);
        Init_H(s);
        TrEE.init();
        for_(i,1,Q){ 
            solve(read(),read());
            Ans[i]=ans;
        }
        for_(i,1,Q){
            print(Ans[i],"\n");
        }
    }
}

Minimax

逆天冲刺 NOIP 题单可做题之十(字符串之二)

逆天分讨题,看到就难受

我们要考虑最小化前缀函数且字符串最小,也就是尽可能不要让前缀和后缀相同,同时最小化因此我们考虑分类讨论

  • 如果字符串内所有字符都是相同的

    直接输出最小的情况即可,因为前后调转不会产生影响

  • 如果所有字符串都是不同的

    直接输出最小的情况即可,因为前后调转同样不会产生影响

  • 当字符串内有字符出现且仅出现了一次

    此时我们发现其可以让字典序最小的只出现了一次的字符放在最前面,此时我们可以发现其 \(f\) 为最小

  • 如果当前字符串内最小的字符出现了小于一半的次数

    我们先把最小值存到第一位和第二位,然后后面按照当前(指放目前的次小值,最小值)的情况用次小值,最小值的顺序放即可

  • 如果当前字符串内最小的字符出现了大于一半的次数且只由两种字符形成

    我们先用一个最小值存到第一位,然后后面放所有次小值,最后放所有最小值即可

  • 最小的字符出现了大于一半的次数而且出现次数大于等于三次

    我们先放一个最小值,然后放一个次小的隔开,接下来把所有最小值放完,放一个次次小值,接下来按照字典序排即可

由于代码太长了我压了一下

点击查看代码
inline void In(){
    t=read();
	while(t--){
		int cnt=0;
        memset(c,0,sizeof(c));
        cin>>s;len=s.size();
        for_(i,0,len-1){a[i+1]=(int)(s[i]-'a');c[(int)(s[i]-'a')]+=1;}
        sort(a+1,a+len+1);
        if(a[1]==a[len]){put(s,'\n');continue;}
        int f=1;for_(i,1,len-1){if(a[i]==a[i+1]){f=0;break;}}
        if(f){for_(i,1,len)putchar(char(a[i]+'a'));puts("");continue;}
        int temp=-1;
        for_(i,0,25){if(c[i]==1){temp=i;break;}}	
        if(temp!=-1){putchar(char(temp+'a'));c[temp]--;for_(i,0,25){for_(j,1,c[i])putchar((char)(i+'a'));}puts("");continue;}
        temp=-1;
        for_(i,0,25){if(c[i]!=0){temp=i;break;}}
        if(c[temp]*2<=len+2){putchar((char)(temp+'a'));putchar((char)(temp+'a'));c[temp]-=2;while(c[temp]>=1){int temp2=-1;for_(i,temp+1,26){if(c[i]!=0){temp2=i;break;}}putchar((char)(temp2+'a'));putchar((char)(temp+'a'));c[temp2]--;c[temp]--;}for_(i,0,25){for_(j,1,c[i]){putchar((char)(i+'a'));}}puts("");continue;}
        int temp2=-1;for_(i,temp+1,25){if(c[i]!=0){temp2=i;break;}}
        if(c[temp]+c[temp2]==len){putchar((char)(temp+'a'));c[temp]--;for_(i,1,c[temp2]){putchar((char)(temp2+'a'));}for_(i,1,c[temp]){putchar((char)(temp+'a'));}puts("");continue;}
        temp2=-1;for_(i,temp+1,26){if(c[i]!=0){temp2=i;break;}}
        int temp3=-1;for_(i,temp2+1,25)if(c[i]!=0){temp3=i;break;}putchar((char)(temp+'a'));c[temp]--;putchar((char)(temp2+'a'));c[temp2]--;
        for_(i,1,c[temp]){putchar((char)(temp+'a'));}
        c[temp]=0;putchar((char)(temp3+'a'));c[temp3]--;for_(i,0,25)for_(j,1,c[i])putchar((char)(i+'a'));puts("");
	}
}

CF955D Scissors

思路

首先考虑对于传进来的两个串 \(s_1\) 和 \(s_2\) ,预处理出 \(s_2\) 的每个前缀在 \(s_1\) 中出现的最早位置和 \(s_2\) 每个后缀在 \(s_1\) 中出现的最晚位置

然后直接扫一遍就行了,复杂度 \(\text O(n)\)

代码

轻微压行

点击查看代码
namespace solve{
    const int N=5e5+5;
    int mod=1e9,base=131;
    int n,m,k,ans1,ans2,lm[N],rm[N];
    char s[N],t[N];
    class Hash{
     public:
        int Hash[N], poww[N];
        inline void Init(char *s) {int len=strlen(s+1);poww[0]=1;for_(i,1,len) {Hash[i]=(Hash[i-1]*base+s[i]-'a'+1) % mod;poww[i]=poww[i-1]*base%mod;}}
        inline int get(int l, int r){return ((Hash[r]-Hash[l-1]*poww[r-l+1])%mod+mod)%mod;}
    }S,T;
    inline bool Judge(){
        if (n<(k<<1)||m>(k<<1)) return 0;
        S.Init(s),T.Init(t); int pos=k;
        for_(i,1,m) lm[i]=n+1;
        for_(i,1,min(m,k)){while(pos<=n&&S.get(pos-i+1,pos)!=T.get(1,i)) pos++;if(S.get(k-i+1,k)==T.get(1,i)) pos=k;lm[i]=pos;} pos=n-k+1;
        for_(i,1,min(m,k)){ while(pos && S.get(pos,pos+i-1)!=T.get(m-i+1,m)) pos--; if(S.get(n-k+1,n-k+i)==T.get(m-i+1,m)) pos=n-k+1; rm[m-i+1]=pos; }
        for_(i,1,n-m+1){ if(S.get(i,i+m-1)==T.get(1,m)){if(k>=i&&n-k+1<=i+m-1) continue;ans1=min(max(1ll,i-k+1),n-k+1-k);ans2=max(k+1,min(n-k+1,i));return 1;}}
        for_(i,1,m-1){ if(lm[i]<rm[i+1]&&lm[i]<=n&&rm[i+1]){ans1=lm[i]-k+1;ans2=rm[i+1];return 1;} }
        return 0;
    }
    inline void In() {
        srand(time(0));mod+=rand();
        base+=rand()%10;read(n,m,k);
        FastI>>(s+1)>>(t+1);
        if(Judge()) 
            write("Yes\n",ans1," ",ans2);
        else 
            write("No");
    }
}
using namespace solve;

CF1037H Security

题意

给定一个字符串 \(s\) 和 \(q\) 次询问,每次询问给出一个 \(l_i,r_i\) 和 \(x_i\),你需要选出一个满足以下条件的字符串 \(str\)。

  • \(str\) 是 \(s[l_i\sim r_i]\) 的子串。

  • \(str > x_i\)。

  • \(str\) 是所有满足以下条件内字符串字典序最小的。

输出 \(str\) 的长度,如果不存在 \(str\) 则输出 \(-1\)。

思路

先把 \(s\) 和所有的询问 \(x_i\) 串都连起来,用小于 \(a\) 的字符连接,连成字符串 \(S\)

枚举 \(T\) 的前缀,判断区间中是否存在一个子串满足以下条件

  • 这个子串等于这个前缀加上一个字母

  • 加上的字母大于 \(T\) 的前缀的后面一个字符

如果前缀为 \(T\) 本身,那么后面一个字符是极小的。

在 \(\text {SA}\) 上二分出一个区间,满足区间中的后缀与 \(T\) 的 \(\text{LCP}\) 长度等于正在枚举的 \(T\) 的前缀长度 \(len\),记录上次的二分结果

求这个区间中满足 \(sa_i\in [l,r-len]\) 的最小的 \(i\) ,用主席树或者扫描线维护,复杂度 \(\text O(|S| \log |S|)\)

代码

点击查看代码
	class SegTree{
	public:
		int t[N*4],d;
		inline void build(int n){
			for(d=1;d<n;d<<=1);
			memset(t,0x7f,sizeof(t));
		}
		inline int ask(int l,int r){
			int mn=2e9;
			for(l=l+d-1,r=r+d+1;l^r^1;l>>=1,r>>=1){
				if(l&1^1) mn=min(mn,t[l^1]);
				if(r&1) mn=min(mn,t[r^1]);
			}
			return mn;
		}
		inline void add(int x,int v){
			for(int i=x+d;i;i>>=1)
				t[i]=v;
		}
	}T;
	pair<int,int> q[N];
	namespace ST_Table{
		int val[N],l[N],ans[N],ansl[N],Log[N*2],ST[20][N*2];
		inline int askmin(int l,int r){
			int k=Log[r-l+1];
			return min(ST[k][l],ST[k][r-(1<<k)+1]);
		}
	}
	using namespace ST_Table;
	namespace Solve{
		class node{
		public:
			int w,l,r,id,len;
		};
		vector<node> que[N*2];	
		inline void ask(int l,int r,int val,int slen,int id){
			int w,cnt2=a.rk[val];
			for(int j=1<<20;j;j>>=1)
				((cnt2+j<=len)&&(askmin(a.rk[val]+1,cnt2+j)>=(min(slen,r-l)+1)))&&(
					cnt2+=j
				);
			_for(i,min(slen,r-l),0){
				w=cnt2;
				for(int j=1<<20;j;j>>=1)
					((w+j<=len)&&(askmin(cnt2+1,w+j)>=i))&&(
						w+=j
					);
				que[cnt2+1].push_back(node{w,l,r-i,id,i});
				cnt2=w;
			}
		}
		inline void solve(){
			T.build(l[0]);
			int tp,id;
			_for(i,len,1){
				(a.sa[i]<=l[0])&&(T.add(a.sa[i],i),0);
				int len1=que[i].size();
				for_(j,0,len1-1)
					(que[i][j].len>ansl[id=que[i][j].id]&&(tp=T.ask(que[i][j].l,que[i][j].r))<=que[i][j].w)&&(
						ans[id]=a.sa[tp],ansl[id]=que[i][j].len,
						0
					);
			}
		}
	}
	using namespace Solve;
	inline void In(){
		memset(ansl,-1,sizeof(ansl));
		int m;
		FastI>>stp>>m;
		l[0]=strlen(stp);
		for_(i,0,l[0]-1)
			s[++len]=stp[i];
		s[++len]='z'+1;
		for_(i,1,m){
			read(q[i].first,q[i].second);
			FastI>>stp;
			l[i]=strlen(stp),val[i]=len+1;
			for_(j,0,l[i]-1)
				s[++len]=stp[j];
			s[++len]='a'-1;
		}
		a.build(s,len,'z'+1);
		for_(i,2,len)
			ST[0][i]=a.height[i];
		for(int j=1;(1<<j)<=len;j++)
			for_(i,1,len-(1<<j)+1)
				ST[j][i]=min(ST[j-1][i],ST[j-1][i+(1<<j-1)]);
		for_(i,2,len)
			Log[i]=Log[i>>1]+1;
		for_(i,1,m)
			ask(q[i].first,q[i].second,val[i],l[i],i);
		solve();
		for_(i,1,m){
			if(ans[i]){
				for_(j,ans[i],ans[i]+ansl[i])
					write(char(s[j]));
				write("\n");
			}
			else 
				write("-1\n");
		}
	}

标签:putchar,NOIP,temp,int,text,逆天题,len,char,纪要
From: https://www.cnblogs.com/Vsinger-LuoTianYi/p/18108368

相关文章

  • P3956 [NOIP2017 普及组] 棋盘
    /*这题本身不难,但是我写难了就是一个bfs,没了但是我的写法恰好犯了一个错误hark数据35110211311220330答案是4而我能输出30-1-110-11-10原因是我先走到了(2,2)这时候......
  • [NOIP2018] 旅行 题解
    明显要以\(1\)为起点。原图是树这种情况下,走路不能回头,只能用\(dfs\)的思路走。当然肯定每次都走较小的那棵子树,\(vector\)存图后排序即可达到这种效果。时间复杂度\(O(n\logm)\)。原图是基环树明显可以分别考虑将所有边断掉后的情况,取字典序最小的。时间复杂度\(O(......
  • P1155 [NOIP2008 提高组] 双栈排序
    P1155[NOIP2008提高组]双栈排序有思维的二分图染色题。对于“双”类的题目,我们通常分开考虑单个时的性质。对于一个栈,有一个基本的定理:若出现\(i<j<k\),有\(a_k<a_i<a_j\),那么一定不合法,即没有合法的出栈顺序使之有序。对于两个栈,我们相当于把序列分成两部分,使每部分之间......
  • 洛谷题单指南-数学基础问题-P1069 [NOIP2009 普及组] 细胞分裂
    原题链接:https://www.luogu.com.cn/problem/P1069题意解读:一个数s代表细胞经过一天分裂的个数,则经过t天后个数为st,要计算经过几天后能整除m1m2,也就是st%m1m2==0,有多个s,要计算天数最少就可以满足条件的。解题思路:直接求st%m1m2显然不可取,会超出整数最大范围,高精度也不是好......
  • 博欧会议纪要-2024年4月12日
    排产系统:第一视角:关注订单各项完成情况,把系统分为三部分,预排产(作为判断排产合理性的工具)、正式排产(关注排产时间,查看各种拖期问题)、实时监控(关注资源的变更,比如人员请假、设备损坏等),注重生产组织框架。具体实施:1、完善总览表,加上汇总统计,统计拖期订单数量,再排数量等,用各种......
  • [题解] [NOIP2011] 聪明的质检员
    [NOIP2011]聪明的质监员题目描述小T是一名质量监督员,最近负责检验一批矿产的质量。这批矿产共有\(n\)个矿石,从\(1\)到\(n\)逐一编号,每个矿石都有自己的重量\(w_i\)以及价值\(v_i\)。检验矿产的流程是:给定\(m\)个区间\([l_i,r_i]\);选出一个参数\(W\);对......
  • [题解] <NOIP2017> 时间复杂度
    [题解]NOIP2017时间复杂度题目描述小明正在学习一种新的编程语言A++,刚学会循环语句的他激动地写了好多程序并给出了他自己算出的时间复杂度,可他的编程老师实在不想一个一个检查小明的程序,于是你的机会来啦!下面请你编写程序来判断小明对他的每个程序给出的时间复杂度是否正......
  • 洛谷题单指南-数学基础问题-P1072 [NOIP2009 提高组] Hankson 的趣味题
    原题链接:https://www.luogu.com.cn/problem/P1072题意解读:求有多少个x,满足x和a0​的最大公约数是a1​,x和b0​的最小公倍数是b1,多组数据。解题思路:枚举法:因为x和a0​的最大公约数是a1​,x和b0​的最小公倍数是b1,所以x不大于b1​。枚举x有两种思路:1、x是a1的倍数,最多需要枚举......
  • 2024.04.11NOIP模拟赛 #1 记录
    2024.04.11NOIP模拟赛#1记录AT_arc160_e[ARC160E]MakeBiconnected给你一棵\(n\)个节点由无向边组成的二叉树,树上每个点有权值\(w_i\)。你可以把两个点之间连无向边,如果将\(u\)与\(v\)连边,代价是\(w_u+w_v\)。请给出一种连边方式,使得连边后,图中去掉任何一个点仍然......
  • 洛谷题单指南-数学基础问题-P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题
    原题链接:https://www.luogu.com.cn/problem/P1029题意解读:已知x,y,求有多少对p、q,使得p、q的最大公约数为x,最小公倍数为y。解题思路:枚举法即可。枚举的对象:枚举p,且p必须是x的倍数,还有p<=yq的计算:q=x*y/p,q要存在,必须x*y%p==0,且gcd(p,q)==x100分代码:#include......