【LOJ6043】「雅礼集训 2017 Day7」蛐蛐国的修墙方案(搜索技巧题)

点此看题面

大致题意: 给你一个长度为\(n\)的排列\(p\),要求构造一个合法的括号序列,使得如果第\(i\)个位置是左括号,则第\(p_i\)个位置一定是右括号。

暴搜

很容易想出一个暴搜。

即对于每一个没有确定的位置\(x\),无非有两种情况:

  • 选左括号。前提是\(p_x\)没被选过或者\(p_x\)为右括号,然后标记第\(p_x\)位为右括号。
  • 选右括号。我们可以开一个变量\(v\)来记录左括号个数\(-\)有括号个数,则选右括号的前提是\(v>0\)。(因为要是一个合法的括号序列)

最后判断\(v\)是否恰好等于\(0\)即可。

代码如下:

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
using namespace std;
int n,a[N+5];char s[N+5];
I void dfs(CI x,CI v)//x记录当前位置,v记录左括号个数减右括号个数
{
    if(x>n) return (void)(!v&&(puts(s+1),exit(0),0));//如果x大于n且v恰好为0,则输出答案,退出程序
    if(s[x])//如果已经确定 
    {
        if(s[x]^')') return dfs(x+1,v+1);//如果是左括号,搜索下一位
        if(v) return dfs(x+1,v-1);return;//如果是右括号且v大于0,搜索下一位
    }
    if(a[x]>x||s[a[x]]^'(') s[x]='(',s[a[x]]=')',dfs(x+1,v+1),s[x]=s[a[x]]='\0';//选左括号
    if(v) s[x]=')',dfs(x+1,v-1),s[x]='\0';//选右括号
}
int main()
{
    RI i;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);//读入
    return dfs(1,0),0;
}

所以这样就有\(81\)分了。

如果你是常数之神,说不定能直接过。

观察性质

接下来,我们要观察一下这个题目中的一个性质。

注意到它保证有解,再结合题意,如果我们把\(i->p_i\)看成一条边,则原序列可以看成若干个环,而这些环一定都是偶环(不然就无解了)。

而对于一个偶环,我们对它的选择必然是左括号与右括号相交替的。

也就是说,对于一个偶环,只有两种选择方法,似乎暴枚即可。

然而,要特判环长为\(2\)的情况(不然会像我第一次那样只有\(86\)分——也就比暴搜多\(5\)分),贪心一下即可发现肯定是左边那位选左括号,右边那位选右括号。

这样就能过了。

关于时间复杂度有严谨的证明:

考虑我们特判了环长为\(2\)的情况,也就是说环长至少为\(4\)

则最多\(100÷4=25\)个环,而\(2^{25}\)稳过。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
using namespace std;
int n,tot,a[N+5],v[N+5];char s[N+5];vector<int> f[N+5];
I void Check()//验证是否为合法括号序列
{
    for(RI i=1,t=0;i<=n;++i) if((t+=(s[i]^')'?1:-1))<0) return;//若出现不合法,直接退出函数
    puts(s+1),exit(0);//合法则输出答案,退出程序
}
I void dfs(CI x)//搜索,判断第i个环的填法
{
    if(x>tot) return Check();RI i,sz=f[x].size();
    if(!(sz^2)) return s[f[x][0]]='(',s[f[x][1]]=')',dfs(x+1);//特判环长为2的情况
    for(i=0;i^sz;++i) s[f[x][i]]=(i&1?'(':')');dfs(x+1);//环中必然是左右括号交替
    for(i=0;i^sz;++i) s[f[x][i]]=(i&1?')':'(');dfs(x+1);//枚举另一种情况
}
int main()
{
    RI i,x;for(scanf("%d",&n),i=1;i<=n;++i) scanf("%d",a+i);//读入
    for(i=1;i<=n;++i) if(!v[x=i])//如果没访问过
        {++tot;W(!v[x]) v[x]=1,f[tot].push_back(x),x=a[x];}//找环
    return dfs(1),0;//搜索
}

转载于:https://www.cnblogs.com/chenxiaoran666/p/LOJ6043.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
PN532是一种非接触式射频识别(RFID)模块,可用于读取和写入NFC(近场通信)芯片、标签和卡片。以下是蛐蛐的PN532使用方法。 首先,确保PN532模块已正确连接到Arduino或其他控制器上。连接包括将模块的GND引脚连接到控制器的地线,VCC引脚连接到控制器的电源引脚,SDA引脚连接到控制器的数据引脚,以及SCK和MOSI引脚分别连接到控制器的时钟和数据线。 在Arduino IDE中,导入PN532库,该库为PN532模块提供了一些便捷的函数来进行通信和操作。 在程序中,首先通过`Wire.begin()`函数初始化I2C总线通信,然后通过`pn532.begin()`函数初始化PN532模块。 接下来,可以调用`getFirmwareVersion()`函数获取PN532模块的固件版本信息。此函数将返回一个16位的版本号。 要读取卡片或标签上的数据,可以调用`readPassiveTargetID()`函数。此函数将返回一个卡片或标签的UID(唯一识别号码)。可以根据需要使用UID来进行后续的操作和验证。 另外,可以使用`mifareclassic_AuthenticateBlock()`函数进行Mifare Classic卡片的验证。该函数需要传递卡片的扇区和块号,以及卡片的KEY A或KEY B。验证成功后,可以使用`mifareclassic_ReadDataBlock()`函数读取卡片上特定块的数据。 除了读取数据,还可以使用`mifareclassic_WriteDataBlock()`函数向卡片写入数据。同样,需要提供卡片的扇区和块号,以及要写入的数据。 最后,使用`SAMConfig()`函数可以配置PN532模块的安全访问模块(SAM)。 总体而言,蛐蛐的PN532使用方法包括初始化PN532模块、获取固件版本、读取卡片或标签的UID、验证和读写Mifare Classic卡片的数据,以及配置安全访问模块等。具体的操作可以根据实际需求来使用PN532库提供的函数进行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值