修改串

本文详细介绍了AC自动机结合动态规划(DP)解决特定问题的方法。通过引入危险结点概念,优化了AC自动机的广度优先搜索(bfs),确保在遇到模式串时能够有效回溯。同时,阐述了如何利用DP求解最小修改值,以实现字符串与字典树的高效匹配。

我是传送门

AC自动机+DP

对于AC自动机的bfs,这里加了一点改进。
  • 引入了一个叫危险结点的东西,所谓危险结点,就是在字典树中以这个点结尾的字符串,是包含模式串的。

  • 如果某个点p没有了子节点,辣么我们需要将点p的子节点指向p的失败指针的子节点,这样是为了当穷途末路到达模式串的末尾时,就可以到一个可以查找的地方

  • 值得注意的就是,如果一个点x,他的儿子son,x的失败指针为j,由失败指针的定义可知,s[j]为s[x]的后缀,所以如果j也有一个和son字符一样的儿子,那么,son的失败指针就是就是j的儿子啦!这样是满足失败指针的定义的。

  • 如果某个点的失败指针是危险节点,那么这个点也要标记为危险结点

对于DP
  • f[i][j]表示原字符串第i位,字典树上的结点j,当i的后缀与j到跟组成的字符串完全匹配的最小修改值

  • 注意如果这个点是危险结点,那么就不能更新f[i][j]了,因为如果转移了,那就不符合题目要求了

  • 这个转移方程很好想,就是f[i+1][son]=f[i][j]+(a[i+1]!=k),k就是son的字符,son是j的儿子,根据定义,就很好理解啦!

  • 答案就是f[n][i](i为树上所有的节点)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char s[60],change[1100];
int slen,tot,n;
struct char_tree{int son[5],end,fail;}tr[110000];
void clean(int x)
{
	tr[x].end=0;tr[x].fail=-1;
	memset(tr[x].son,-1,sizeof(tr[x].son));
}
int id(char c)
{
	if(c=='A')return 1;
	else if(c=='C')return 2;
	else if(c=='G')return 3;
	else return 4;
}
void build_tree(int root)
{
	int x=root;
	for(int i=1;i<=slen;i++)
	{
		int j=id(s[i]);
		if(tr[x].son[j]==-1)tr[x].son[j]=++tot,clean(tot);
		x=tr[x].son[j];
	}
	tr[x].end++;
}
int head,tail,list[2100000];
void bfs()
{
	head=1;tail=1;memset(list,0,sizeof(list));
	while(head<=tail)
	{
		int x=list[head];
		for(int i=1;i<=4;i++)
		{
			int child=tr[x].son[i];
			if(child==-1)
			{
				if(x==0)tr[x].son[i]=0;
				else tr[x].son[i]=tr[tr[x].fail].son[i];
				continue;
			}
			if(x==0)tr[child].fail=0;
			else
			{
				int j=tr[x].fail;
				while(j!=-1)
				{
					if(tr[j].son[i]!=-1)
					{
						tr[child].fail=tr[j].son[i];
						int tmp=tr[j].son[i];if(tr[tmp].end!=0)tr[child].end=1;
						break;
					}
					j=tr[j].fail;
				}
				if(j==-1)tr[child].fail=0;
			}
			list[++tail]=child;
		}
		head++;
	}
}
int f[2100][2100];
int DP()
{
	for(int i=0;i<=n;i++)for(int j=0;j<=tot;j++)f[i][j]=1e9;f[0][0]=0;
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<=tot;j++)
		{
			if(f[i][j]==1e9)continue;
			for(int k=1;k<=4;k++)
			{
				int child=tr[j].son[k];
				if(tr[child].end!=0)continue;
				f[i+1][child]=min(f[i+1][child],f[i][j]+(id(change[i+1])!=k));
			}
		}
	}
	int ans=1e9;
	for(int i=0;i<=tot;i++)ans=min(ans,f[n][i]);
	if(ans==1e9)ans=-1;
	return ans;
}
int main()
{
	int t=0;
	while(scanf("%d",&n)!=EOF)
	{
		if(n==0)break;
		t++;clean(0);
		for(int i=1;i<=n;i++)
		{
			scanf("%s",s+1);slen=strlen(s+1);
			build_tree(0);
		}
		bfs();
		scanf("%s",change+1);n=strlen(change+1);
		printf("Case %d: %d\n",t,DP());
	}
	return 0;
}
在 Windows 11 系统中更改口(COM端口)设置,可以通过设备管理器或注册表编辑器来实现。以下是详细的步骤说明: ### 通过设备管理器更改口号 1. 打开 **设备管理器**: - 可以通过右键点击任务栏左下角的“开始”按钮,然后选择“设备管理器”。 2. 在设备管理器中找到需要修改口设备(通常位于“端口(COM和LPT)”下)。 3. 右键点击该设备,选择 **属性**。 4. 在打开的属性窗口中,切换到 **端口设置** 选项卡。 5. 点击 **高级** 按钮,进入高级设置界面。 6. 在这里,可以选择新的 COM 号,并点击 **确定** 完成更改。 ### 清除所有口号并重新分配 如果希望清除所有口号并重新分配,可以通过修改注册表来实现: 1. 打开 **注册表编辑器**: - 按下 `Win + R` 键,输入 `regedit`,然后按回车键。 2. 定位到以下路径: ``` HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\COM Name Arbiter ``` 3. 在右侧窗格中找到 `ComDB` 键值。其数据以十六进制表示,其中每一位代表一个 COM 号是否被占用。 4. 修改 `ComDB` 的值,将非零的数据改为 `00`,这样可以清除所有已分配的 COM 号[^1]。 5. 保存更改后,重启计算机,系统会重新分配口号。 ### 使用第三方工具简化操作 除了手动修改,还可以使用专门的工具软件,如 **ComPortManager**,它支持批量管理计算机口设备的 COM 号口。该工具不仅支持单独修改口号,还可以根据设备在系统下的物理位置进行分配,适用于多种口设备类型(如 USB 转口、PCI(e) 转口等)[^2]。 ### 示例代码 如果你希望通过编程方式更改口号,可以使用 C# 中的 `System.IO.Ports` 命名空间中的 `SerialPort` 类来实现。例如,可以通过设置 `SerialPort` 对象的 `PortName` 属性来改变口的默认端口号[^5]: ```csharp using System.IO.Ports; SerialPort mySerialPort = new SerialPort("COM1"); // 设置口参数 mySerialPort.BaudRate = 9600; mySerialPort.Parity = Parity.None; mySerialPort.StopBits = StopBits.One; mySerialPort.DataBits = 8; mySerialPort.Handshake = Handshake.None; // 修改口号 mySerialPort.PortName = "COM2"; // 打开端口 mySerialPort.Open(); ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值