【计数】CF283E

博客探讨了如何解决CF283E问题,采用从图论的角度出发,利用计数和图的性质。文章首先介绍了问题的思路,指出问题本质上是一个计数问题,涉及图论和翻转操作。接着,作者提出了从三元组总数减去非三元环的数量来求解,并详细解释了如何计算这两个部分。在效率方面,通过引入线段树以优化O(nm)的时间复杂度。最后,博主展示了如何使用线段树进行状态维护和区间异或操作来实现解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

scanf毁我青春呀!!!!

思路

一眼显然计数,二眼显然图论(这显然是一张竞赛图),看到翻转想DS。

先考虑怎样计数。正向思考发现很难维护。

考虑倒着找答案——先算出所有三元组的个数,再减去非三元环的情况数

所有三元组数量很容易算,就是 Cn3C_n^3Cn3

注意到非三元环有一个性质,有且仅有一个点出度为2。

那就记 did_idi 表示 iii 号点的出度,非三元环答案即为 ∑i=1nCdi2\sum^{n}_{i=1}C_{d_i}^2i=1nCdi2。因为从每个点所连的点中选两个,这三个点必然不在一个环,并且有且仅有这一种情况。

于是我们得到了 O(nm)\mathcal{O(nm)}O(nm)的做法,但显然不够,主要问题在于处理翻边的时候不能快速处理。于是考虑用DS来维护。

发现我们关心的东西并不是那些点被当前点连了边,是有多少点连了边。

发现它满足可加性,于是想到线段树。

好像二维线段树也行,但实际上我们只需要普通线段树就可以了。

先考虑对单点如何维护。设当前点为 uuu,我们要维护一个01串 SSS

SiS_iSi表示 uuuiii 是否有边。那么修改操作显然是异或一下即可,答案统计就是区间和。

然后考虑如何通过上一个点的答案来算当前点的答案,可以再做一次上一个的操作把上次操作抵消了,再把这个点本来该做的操作再做一遍,最后把答案统计一边即可。

代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 5;
int n , m , s[N] , ans;
struct nod{
	int l , r;
};
vector <nod> v1[N] , v2[N];
struct node{
	int p , l , r , sum , tag;
}tr[N << 2];
int ls(int p)
{
	return p << 1;
}
int rs(int p)
{
	return p << 1 | 1;
}
void build(int p , int l , int r)
{
	tr[p].l = l;tr[p].r = r;
	if(l == r)return ;
	int mid = (l + r) >> 1;
	build(ls(p) , l , mid);
	build(rs(p) , mid + 1 , r);
}
void push_up(int p)
{
	tr[p].sum = tr[ls(p)].sum + tr[rs(p)].sum; 
}
void push_down(int p)
{
	if(tr[p].tag)
	{
		tr[ls(p)].tag ^= 1;tr[rs(p)].tag ^= 1;
		tr[p].tag = 0;
		int mid = (tr[p].l + tr[p].r) >> 1;
		tr[ls(p)].sum = mid - tr[p].l + 1 - tr[ls(p)].sum;
		tr[rs(p)].sum = tr[p].r - mid - tr[rs(p)].sum;
	}
}
void modify(int p , int l , int r , int nl , int nr)
{
	if(l >= nl && r <= nr)
	{
		tr[p].tag ^= 1;
		tr[p].sum = r - l + 1 - tr[p].sum;
		return ;
	}
	push_down(p);
	int mid = (l + r) >> 1;
	if(nl <= mid)modify(ls(p) , l , mid , nl , nr);
	if(mid < nr)modify(rs(p) , mid + 1 , r , nl , nr);
	push_up(p);
}
int query(int p , int l , int r , int nl , int nr)
{
	int res = 0;
	if(l >= nl && r <= nr)
	{
		return tr[p].sum;
	}
	push_down(p);
	int mid = (l + r) >> 1;
	if(nl <= mid)res += query(ls(p) , l , mid , nl , nr);
	if(mid < nr)res += query(rs(p) , mid + 1 , r , nl , nr);
//	push_up(p);
	return res;
}
signed main()
{
	cin >> n >> m;
	ans = n * (n - 1) * (n - 2) / 6;
	for(int i = 1;i <= n;i++)cin >> s[i];
	sort(s + 1 , s + 1 + n);
	for(int i = 1;i <= m;i++)
	{
		int l , r;
		scanf("%lld%lld",&l,&r);
		if(l > s[n] || r < s[1])continue;
		l = lower_bound(s + 1 , s + 1 + n , l) - s;
		r = upper_bound(s + 1 , s + 1 + n , r) - s - 1;
		v1[l].push_back((nod){l , r});
		v2[r + 1].push_back((nod){l , r});
	}
	build(1 , 1 , n);
	for(int i = 1;i <= n;i++)
	{
		for(int j = 0;j < (int)v1[i].size();j++)modify(1 , 1 , n , v1[i][j].l , v1[i][j].r);
		for(int j = 0;j < (int)v2[i].size();j++)modify(1 , 1 , n , v2[i][j].l , v2[i][j].r);
		int p = 0;
		if(i > 1)p += i - 1 - query(1 , 1 , n , 1 , i - 1);
		if(i < n)p += query(1 , 1 , n , i + 1 , n);
		if(p > 1)ans -= p * (p - 1) / 2; 
	}
	cout << ans;
	return 0;
}
<think>我们正在讨论CAN通信中的CAN传输层(CAN-TP),特别是关于ISO-TP协议中的四种帧类型:单帧(SF)、首帧(FF)、连续帧(CF)和流控帧(FC)。用户希望了解这些帧的示例和使用场景。根据引用[2],这四种帧是ISO-TP中定义的基本帧类型。引用[5]提供了AutoSAR架构中CAN-TP的数据流:从DCM(诊断通信管理器)通过PduR(PDU路由器)到CAN-TP,然后CAN-TP将I-PDU拆分成SF/FF/FC/CF帧,再通过CAN接口层和CAN驱动发送到总线。下面我将详细解释每种帧的结构、使用场景和示例:1.**单帧(SF)**:-**使用场景**:当传输的数据量很小,可以在一个CAN数据帧中容纳时,使用单帧。单帧不需要分段传输,因此不需要流控和连续帧。-**帧结构**:-第一个字节(PCI字节)的高4位为0,表示单帧。-低4位表示数据长度(0-7个字节的数据时,长度值等于实际数据长度;但注意,当数据长度为8时,低4位为0,而长度由后续的字节表示?实际上,ISO-TP规定单帧的第一个字节高4位为0,低4位表示数据长度(如果数据长度小于8),但如果数据长度在8-15之间,则第一个字节的低4位为0,而实际长度由第二个字节表示?但根据ISO15765-2,单帧的最大数据长度为7,因为第一个字节的低4位只能表示0-7。但实际上,单帧可以传输0-7个字节的数据,或者使用扩展的单帧(但扩展单帧不在基本ISO-TP中)。这里我们按照基本ISO-TP。-例如:要发送3个字节的数据:0x11,0x22,0x33。-则单帧为:0x03(高4位0,低4位3)+0x11,0x22,0x33(共4个字节,因为CAN数据帧最多8字节,所以剩余字节用填充,比如0xAA填充)。-**示例**:诊断命令中的短命令,例如读取当前故障码(DTC)的请求,可能只需要几个字节。2.**首帧(FF)**:-**使用场景**:当传输的数据量超过7个字节(即单帧无法容纳)时,需要分段传输。首帧用于发起多帧传输,它包含总数据长度和第一部分数据。-**帧结构**:-第一个字节的高4位为1,表示首帧。-低4位和第二个字节共同组成12位的总数据长度(因此最大长度为4095字节)。-例如:要发送20个字节的数据,那么首帧的前两个字节表示长度:0x10(高4位1,低4位0x1)和0x14(0x014,即20),所以首帧的前两个字节为0x10,0x14,然后紧接着6个字节的数据(因为首帧总共8字节,前两个字节为长度,剩余6个字节为数据)。-**示例**:在写入大量数据时(比如刷写ECU软件),首先发送首帧,告知接收方总数据长度。3.**连续帧(CF)**:-**使用场景**:在首帧之后,发送方连续发送连续帧,每个连续帧携带一部分数据。连续帧的序列号从1开始递增。-**帧结构**:-第一个字节的高4位为2,表示连续帧。-低4位表示序列号(SN),从1开始,每发送一个连续帧,序列号加1,当序列号达到0xF时,下一个重置为0。-后续7个字节为数据。-例如:首帧之后,第一个连续帧:0x21(高4位2,序列号1)后面跟着7个字节的数据。-**示例**:在发送多帧数据时,首帧之后的数据都通过连续帧发送。4.**流控帧(FC)**:-**使用场景**:接收方在收到首帧后,需要发送流控帧来控制发送方的数据流。流控帧告知发送方是否可以继续发送,以及发送的连续帧数量(BS)和连续帧之间的最小时间间隔(STmin)。-**帧结构**:-第一个字节的高4位为3,表示流控帧。-第二个字节为流状态(FS):0表示继续发送(CTS),1表示等待(WT),2表示溢出(OVFLW)。-第三个字节为块大小(BS):表示发送方在等待下一个流控帧之前可以发送的连续帧数量。0表示无限制(即可以一直发送直到结束)。-第四个字节为最小间隔时间(STmin):单位为毫秒,表示连续帧之间的最小时间间隔。也可以使用其他单位(如微秒),由具体实现决定。-**示例**:接收方收到首帧后,如果能够接收数据,则发送流控帧(FS=0,BS=10,STmin=5),表示发送方可以连续发送10个连续帧,每个连续帧之间至少间隔5毫秒。下面我们通过一个多帧传输的示例来说明这些帧的交互:假设发送方需要发送20个字节的数据(数据为0x01,0x02,...,0x14):1.发送方发送首帧(FF):-数据:0x10(FF类型)0x14(长度20)0x010x020x030x040x050x06(注意:首帧携带了前6个字节的数据,因为总长度占2个字节,所以首帧剩余6个字节用于数据)2.接收方收到首帧后,检查自己的接收缓冲区,如果空间足够,则发送流控帧(FC):-数据:0x30(FC类型)0x00(FS=0,允许继续发送)0x0A(BS=10)0x01(STmin=1ms)3.发送方收到流控帧(允许发送,且BS=10,STmin=1ms)后,开始发送连续帧(CF):-第一个连续帧:0x21(序列号1)0x070x080x090x0A0x0B0x0C0x0D(注意:首帧已经发送了0x01-0x06,所以这里从0x07开始,连续帧有7个字节数据)-第二个连续帧:0x22(序列号2)0x0E0x0F0x100x110x120x130x14(最后两个字节0x13,0x14是第19和20个字节,剩余5个字节用填充,如0x00)(注意:因为总长度20,首帧发送了6个字节,所以剩余14个字节。每个连续帧7个字节,所以需要两个连续帧。第二个连续帧只用了2个字节有效数据,其余5个填充。但实际中,填充值可以协商,通常用0xAA或0x55)4.由于BS=10,而发送方只需要发送2个连续帧,因此不需要等待下一个流控帧。发送完成后,接收方收到所有数据,组装并传递给上层。另外,如果数据量很大,比如需要发送1000个字节,那么发送方在发送首帧后,接收方回复流控帧(BS=10,STmin=1ms),发送方发送10个连续帧(序列号1到10)后,需要等待接收方再次发送流控帧(再次允许发送),然后继续发送下一个块的连续帧(序列号从1开始重新计数,注意序列号是每块重新从1开始?还是全局递增?根据标准,序列号是全局递增的,每发送一个连续帧就递增1,达到0xF后下一个为0,然后再递增。而块大小(BS)只是限制一次可以发送的连续帧数量,并不重置序列号)。关于序列号:序列号在连续帧中是循环递增的,从1开始,发送一个连续帧后序列号加1(模16,即0xF之后是0,但注意0是保留的,所以实际上序列号是1,2,...,0xE,0xF,1,2...)。这样接收方可以通过序列号来检测是否丢失了帧。以上是四种帧的基本使用场景和示例。在实际诊断通信(如UDS)中,这些帧由CAN-TP层处理,上层(如DCM)只需要处理完整的报文(PDU)。根据引用[3]和[4],流控帧中的BS和STmin参数可以由接收方动态调整,以适应其处理能力。例如,如果接收方缓冲区不足,它可以发送FS=1(等待),要求发送方暂停发送。总结:-SF:用于短数据,一次传输完成。-FF:用于发起长数据传输,包含总长度和部分数据。-CF:用于长数据的后续分段。-FC:接收方控制发送方的流控参数(BS和STmin)。希望这些示例和使用场景能够帮助理解。下面生成相关问题:</think>### CAN-TP层中SF、FF、CF、FC帧详解及示例 #### 一、帧类型与使用场景 根据ISO 15765-2标准(引用[2]),CAN-TP层定义了四种核心帧类型: | 帧类型 | 标识符 | 使用场景 | 数据负载 | |--------|--------|----------|----------| | **单帧(SF)** | 高4位=0 | 传输≤7字节的小数据 | 数据长度(低4位)+数据 | | **首帧(FF)** | 高4位=1 | 发起多帧传输 | 总长度(12位)+首段数据 | | **连续帧(CF)** | 高4位=2 | 传输多帧数据块 | 序列号(低4位)+数据 | | **流控帧(FC)** | 高4位=3 | 流量控制 | FS状态+BS块大小+STmin间隔 | --- #### 二、典型通信示例 ##### 场景:读取ECU软件版本号(12字节数据) ```mermaid sequenceDiagram participant Tester participant ECU Tester->>ECU: FF帧 [0x10 0x0C][数据1-6] ECU->>Tester: FC帧 [0x30 0x00 0x0A 0x05] (允许发送10帧/5ms间隔) Tester->>ECU: CF帧 [0x21][数据7-13] (序列号1) Tester->>ECU: CF帧 [0x22][数据14-20] (序列号2) ``` ##### 1. 单帧(SF)场景示例 **场景**:读取DTC故障码数量(响应数据3字节) **帧结构**: ``` [02][43][01][AA][AA][AA][AA][AA] ``` - `02`:数据长度=2字节(低4位) - `43 01`:实际数据(UDS正响应+故障码数量) - 剩余5字节填充0xAA(引用[4]) ##### 2. 首帧(FF)+连续帧(CF)示例 **场景**:写入配置数据(256字节) **通信流程**: 1. 发送方发FF帧: ``` [1F][01][00][D1][D2][D3][D4][D5] ``` - `1F`:FF标识(高4位=1) + 长度高4位(0x0F) - `0100`:总长度256字节(0x100) - `D1-D5`:前5字节数据 2. 接收方回FC帧: ``` [30][00][20][0A] ``` - `00`:FS=0(继续发送) - `20`:BS=32(最大连续帧数) - `0A`:STmin=10ms(引用[3]) 3. 发送方发CF帧序列: ``` [21][D6][D7]...[DC] // 序列号1 [22][DD][DE]...[E3] // 序列号2 ... ``` ##### 3. 流控帧(FC)特殊场景 **场景**:接收方缓冲区满 **FC帧响应**: ``` [30][01][00][00] ``` - `01`:FS=1(等待状态) - 此时发送方暂停传输(引用[3]) --- #### 三、AutoSAR架构中的实现 在AutoSAR中(引用[5]): ```c /* 接收处理函数示例 */ void CanTp_RxSubDeal(PduIdType RxPduId, const PduInfoType* PduInfoPtr) { FrameType frame = CanTp_GetFrameType(PduInfoPtr->SduDataPtr); switch(frame) { case CANTP_FTYPE_SF: HandleSF(PduInfoPtr); // 单帧直接提交给DCM break; case CANTP_FTYPE_FF: HandleFF(PduInfoPtr); // 初始化多帧接收缓冲区 SendFC(CANTP_FS_CTS); // 回复流控帧[^1] break; case CANTP_FTYPE_CF: if (CheckSN(PduInfoPtr)) // 验证序列号 StoreCFData(PduInfoPtr); break; } } ``` > 数据流向:`DCM → PduR → CanTP → CanIf → CanDrv → 总线`(引用[5]) --- #### 四、关键设计要点 1. **序列号管理** - CF帧序列号循环计数(1→15→1) - 接收方检测序列号连续性:$$ \Delta SN \equiv 1 \pmod{16} $$ 2. **流控参数动态调整** ```c /* 根据缓冲区状态设置BS */ if (FreeBuffer < 64) { SetFlowControl(BS=8, STmin=20); } else { SetFlowControl(BS=0, STmin=5); // BS=0表示无限制[^4] } ``` 3. **超时保护机制** - FF帧等待FC超时:$ T_{waitFC} \leq 1000ms $ - CF帧间隔超时:$ T_{CF} \leq STmin + 50\% $ ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值