CF235C Cyclical Quest

本文详细介绍了如何使用后缀自动机(SAM)解决字符串匹配问题,特别是针对字符串的循环同构串查询。通过构建SAM并利用其特性,文章提供了一个高效的线性时间复杂度的解决方案。

题意:

给出一个字符串s;

n次询问某个字符串xi的循环同构串在s中出现多少次;

|s|,∑|xi|<=10^6,n<=10^5;


题解:

WJMZBMR场的SAM题。。。

感觉还没学多久的后缀自动机姿势已经忘光了。。。悲伤哦;

首先考虑如何查询一个xi串在s中出现了多少次,这个只要直接用s的后缀自动机的trans指针匹配,然后得到的结点的right值就是答案了;

那么一个串xi的所有循环同构串就是将其倍长之后,里面长度为|xi|的子串们;

答案就是在后缀自动机上匹配,每次在后面加一个字符再在前面减一个字符,累加right值;

在前面减字符这个过程不太容易实现,所以转化一下,变成在需要的时候延pre指针向上走一次;

具体来讲这个正确的原因就是pre指针组成了反向后缀树,那么这个延反向后缀树向上相当于减去了一些前缀(减去长度未必为1,所以要在需要的时候减去);

注意累加答案的时候,经过了一次的结点不能再计算一遍,直接跳过即可,匹配的长度不够的结点也不应累加进去;

时间复杂度大概是线性的吧= =


代码:


#include<queue>
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 2100000
#define S 26
using namespace std;
char s[N],str[N];
namespace SAM
{
	int son[N<<1][S],pre[N<<1],len[N<<1],right[N<<1],in[N<<1];
	int vis[N<<1];
	int last,tot;
	queue<int>q;
	int newnode()
	{
		return ++tot;
	}
	void init()
	{
		tot=0;
		last=newnode();
	}
	void Insert(int x)
	{
		int np=newnode(),p;
		right[np]=1;
		len[np]=len[last]+1;
		for(p=last;p&&!son[p][x];p=pre[p])
			son[p][x]=np;
		if(!p)
			pre[np]=1;
		else
		{
			int q=son[p][x];
			if(len[q]==len[p]+1)
				pre[np]=q;
			else
			{
				int nq=newnode();
				len[nq]=len[p]+1;
				pre[nq]=pre[q];
				memcpy(son[nq],son[q],sizeof(int)*S);
				pre[q]=pre[np]=nq;
				for(;son[p][x]==q;p=pre[p])
					son[p][x]=nq;
			}
		}
		last=np;
	}
	void Build()
	{
		int x,i;
		for(i=1;i<=tot;i++)
			in[pre[i]]++;
		for(i=1;i<=tot;i++)
			if(!in[i])
				q.push(i);
		while(!q.empty())
		{
			x=q.front(),q.pop();
			right[pre[x]]+=right[x];
			in[pre[x]]--;
			if(!in[pre[x]])
				q.push(pre[x]);
		}
	}
}

int main()
{
	int n,m,i,j,now,len,ans;
	scanf("%s",s+1);
	SAM::init();
	m=strlen(s+1);
	for(i=1;i<=m;i++)
		SAM::Insert(s[i]-'a');
	SAM::Build();
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	{
		scanf("%s",str+1);
		m=strlen(str+1);
		memcpy(str+m+1,str+1,sizeof(char)*m);
		for(j=1,ans=0,now=1,len=0;j<m+m;j++)
		{
			while(now&&SAM::son[now][str[j]-'a']==0)
				now=SAM::pre[now],len=min(len,SAM::len[now]);
			if(!now)	now=1,len=0;
			else
			now=SAM::son[now][str[j]-'a'],len=min(len,SAM::len[now])+1;
			if(len>=m)
			{
				while(SAM::len[SAM::pre[now]]>=m)
					now=SAM::pre[now],len=min(len,SAM::len[now]);
				if(len>=m&&SAM::vis[now]!=i)
				ans+=SAM::right[now],SAM::vis[now]=i;
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}



<think>首先,用户的问题是关于SIP协议中的OPTIONS请求和响应的交互内容分析,特别是Via、From、To、Call-ID、CSeq字段的解析。用户引用了几个参考内容,我需要基于这些引用来构建回答。 关键引用总结: - 引用[1]:SIP请求必须包含的头域:To、From、CSeq、Call-ID、MaxForwards、Via。这些是核心路由信息。 - 引用[2]:Via头字段的作用:记录请求的路由,帮助响应返回。代理添加Via字段,响应时复制并删除。 - 引用[3]:CSeq头域的作用:用于序列化请求,区分不同请求,避免混淆。 - 引用[4]:一个OPTIONS响应的实例,展示了字段如Via、To、From、Call-ID、CSeq。 - 引用[5]:SIP请求和响应都有的头域,包括协议细节。 用户要求分析SIP OPTIONS请求和响应的交互内容,聚焦于Via、From、To、Call-ID、CSeq字段的解析。 我的回答需要: - 使用中文。 - 结构清晰,逐步解决问题。 - 确保回答真实可靠,基于提供的引用。 - 在行内数学表达式使用$...$,但这里可能不需要数学表达式,因为主题是SIP协议。 - 独立公式使用$$...$$,但同样,这里可能不需要。 - 生成相关问题。 - 在引用的段落末尾添加引用标识,例如[^1]。 回答结构: 1. **介绍SIP OPTIONS请求**:简要说明OPTIONS请求的作用,例如查询服务器能力。 2. **解析请求中的字段**:详细解释Via、From、To、Call-ID、CSeq在OPTIONS请求中的作用和格式。 3. **解析响应中的字段**:同样解释这些字段在响应中的含义,基于引用[4]的实例。 4. **交互过程**:描述请求和响应如何通过这些字段交互,路由返回。 5. **生成相关问题**:在末尾添加相关问题。 确保引用正确: - 在回答中,当提到引用内容时,自然地添加引用标识。 例如: - 根据引用[1],SIP请求必须包含... [^1] - Via字段用于... [^2] 现在,构建内容: - **SIP OPTIONS请求**:用于查询服务器支持的方法和能力。请求行包含方法、RequestURI、SIP版本。 - **字段解析**: - **Via**:记录请求路径。请求时,UA添加自己的地址;代理添加在顶部。响应时,按顺序复制并路由回。[^2] - **From**:发起请求的UA地址。[^1] - **To**:目标UA地址。[^1] - **Call-ID**:唯一标识一个对话或会话。[^1] - **CSeq**:序列号,用于排序请求和响应,区分不同事务。[^3] - **响应实例分析**:基于引用[4],展示字段值并解释。 - **交互**:请求发送时,Via堆栈建立;响应返回时,代理使用Via堆栈路由。 相关问题: 1. SIP OPTIONS请求的常见应用场景是什么? 2. 如何确保SIP消息的路由正确性? 3. CSeq字段在SIP事务中的作用是什么? 最后,确保回答完整、专业。</think>### SIP OPTIONS请求响应分析 SIP(Session Initiation Protocol)协议用于多媒体会话的建立、修改和终止。OPTIONS请求是一种查询方法,用于询问服务器或用户代理(UA)支持的能力和方法,而不建立会话。例如,UA可以发送OPTIONS请求来查询对方支持的编解码器或SIP方法。请求和响应的交互依赖于核心头域进行路由和事务管理。下面,我将逐步解析OPTIONS请求和响应中的关键字段:Via、From、To、Call-ID、CSeq。这些字段在所有SIP请求中必须包含,提供路由、事务标识和序列控制的核心信息[^1]。 #### 1. **OPTIONS请求的字段解析** 当UA发送OPTIONS请求时,请求行指定方法(OPTIONS)、RequestURI(目标地址)和SIP版本(如SIP/2.0)。之后,头域提供详细信息: - **Via**:记录请求的传输路径,帮助响应路由回发送方。请求发起时,UA在Via中添加自己的地址(包括协议、版本、传输方式如UDP/TCP、端口和分支参数)。代理转发时,在Via列表顶部添加自己的地址。分支参数(branch)是Request-URI、To、From、Call-ID和CSeq的哈希值,用于唯一标识事务[^2]。例如: ``` Via: SIP/2.0/UDP client.example.com:5060;branch=z9hG4bK74gh5 ``` 这表示请求通过UDP从client.example.com的5060端口发出,分支值用于防重复。 - **From**:标识请求的发起方,包含URI和可选标签(tag)。标签用于区分同一Call-ID下的多个事务[^1]。例如: ``` From: "Alice" <sip:alice@example.com>;tag=1234 ``` 这表示请求由Alice发起,标签1234用于后续响应匹配。 - **To**:标识请求的目标方,格式类似From。在请求中,To可能不包含标签,直到响应中添加[^1]。例如: ``` To: <sip:bob@example.com> ``` 目标服务器在响应中会添加标签以完成对话。 - **Call-ID**:全局唯一标识符,用于关联同一会话或对话中的所有消息。通常基于时间戳和随机数生成,确保不同请求的Call-ID不重复[^1][^3]。例如: ``` Call-ID: a1b2c3d4@client.example.com ``` 这标识了整个交互的事务链。 - **CSeq**:序列号,由方法名(如OPTIONS)和数字组成,用于排序请求和响应,防止混淆(如区分INVITE和CANCEL)。数字递增确保事务顺序[^3]。例如: ``` CSeq: 101 OPTIONS ``` 这表示这是该Call-ID下的第101个请求,方法为OPTIONS。 完整的OPTIONS请求示例: ``` OPTIONS sip:bob@example.com SIP/2.0 Via: SIP/2.0/UDP client.example.com:5060;branch=z9hG4bK74gh5 From: "Alice" <sip:alice@example.com>;tag=1234 To: <sip:bob@example.com> Call-ID: a1b2c3d4@client.example.com CSeq: 101 OPTIONS Max-Forwards: 70 Contact: <sip:alice@client.example.com> Content-Length: 0 ``` #### 2. **OPTIONS响应的字段解析** 服务器收到OPTIONS请求后,返回响应(如200 OK),包含相同头域但值可能更新。响应复制请求的Via、From、To、Call-ID和CSeq,确保路由正确: - **Via**:响应按请求的Via列表逆序路由。服务器复制所有Via字段到响应中,然后发送到顶部Via地址。每个代理检查顶部Via是否匹配自身地址,匹配后删除该Via并转发到下一个[^2][^4]。例如,在引用[4]的响应中: ``` Via: SIP/2.0/UDP cavendish.kings.cambridge.example.com;tag=512A6;branch=z9hG4bK0834;received=192.0.0.2 ``` 这里,received参数表示请求实际来源IP,帮助NAT穿越。 - **From** 和 **To**:直接复制自请求,但响应中To添加标签(tag)以标识对话[^1][^4]。例如: ``` To: <sip:wiliamhopkins@cam.ac.uk>;tag=432 From: J.C. Maxwell <sip:james.maxwell@kings.cambridge.example.com>;tag=34 ``` 这确认了发起方和目标方,标签用于后续事务(如BYE)。 - **Call-ID**:完全复制请求的Call-ID,确保响应关联到同一事务[^1][^4]。例如: ``` Call-ID: 747469e729acd305 ``` 这保持了交互的连续性。 - **CSeq**:复制请求的CSeq(方法名和数字),用于匹配请求和响应。数字不变,表示这是对同一请求的响应[^3][^4]。例如: ``` CSeq: 29 OPTIONS ``` 这对应请求的CSeq,避免因网络延迟导致的混淆。 完整的OPTIONS响应示例(基于引用[4]): ``` SIP/2.0 200 OK Via: SIP/2.0/UDP cavendish.kings.cambridge.example.com;tag=512A6;branch=z9hG4bK0834;received=192.0.0.2 To: <sip:wiliamhopkins@cam.ac.uk>;tag=432 From: J.C. Maxwell <sip:james.maxwell@kings.cambridge.example.com>;tag=34 Call-ID: 747469e729acd305 CSeq: 29 OPTIONS Contact: <sip:william@tutors.cambridge.example.com>;audio;video Allow: INVITE, OPTIONS, ACK, BYE, CANCEL, REFER Content-Type: application/sdp Content-Length: ... ``` 响应体(如SDP)包含服务器支持的能力,如编解码器。 #### 3. **请求-响应交互过程** - **发送请求**:UA发送OPTIONS请求,Via堆栈从顶部开始记录路径(如UA → 代理1 → 代理2 → 服务器)。 - **处理响应**:服务器生成200 OK响应,复制所有Via字段。响应按Via逆序路由:从服务器 → 代理2(检查并删除顶部Via) → 代理1 → UA。分支参数和标签确保事务唯一性[^2][^5]。 - **关键作用**:Via确保响应可靠返回;Call-ID和CSeq防止消息重复或乱序;From和To标识端点。例如,CSeq数字递增可处理重传请求[^3]。 #### 注意事项 - OPTIONS请求不建立会话,仅用于能力查询,常用于网络诊断或服务发现。 - 字段错误(如Via不匹配)会导致响应丢弃,确保使用标准SIP库(如PJSIP)以避免实现问题[^2][^5]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值