大意:给定一个长度为 n 的字符串 S, 以及长度为 m 的模板串 T。字符串有 ‘0’-'9’以及通配符‘*’组成,通配符可以变成任意的字符,现规定两个字符串不一样的位置不超过 k 个就算匹配,求 k = 0 , 1 , 2 , . . . . . . ∣ T ∣ k=0,1,2,......|T| k=0,1,2,......∣T∣ 字符串 S 中和 T 匹配的字符串个数。
思路:通过这道题算是长见识了。FFT/NTT 是可以用来求解字符串匹配问题的,尤其是在处理含有通配符或者不完全匹配的问题上,FFT/NTT 和KMP 相比更有优势。
首先我们先不考虑通配符的问题。 F(x) 表示 S 中以 下标 x 结尾 的 子字符串和 T 匹配的字符个数,当 S i = T j S_i=T_j Si=Tj 则 F ( i − j + m − 1 ) F(i-j+m-1) F(i−j+m−1)++(模拟一下容易看出)。显然直接暴力枚举是不行的。我们仔细想想要求的是什么,假设我们确定以某个子字符串结尾下标为 x, F(x)实际通过的i,j, 满足 i-j+m-1=x,来求的 x。这不就是求卷积嘛。把 i,j 搞成次幂,最终系数就是我们要求的F(x)。当然对于 -j 的处理我们可以通过加个偏移量,除此之外,我们可以考虑把字符串 T 翻转,这样 j 就变成了 m-1-j。i-j+m-1 就变成了 i+j。接下来就是具体怎么求出 F 的点值表示法进而求出系数表示法。因为字符种类不多,我们可以去枚举字符,当等于当前枚举的字符时设置为系数设置为1,否则设置为0,只有全都为1,即字符相等时才有贡献。然后就是考虑通配符的问题。总的匹配的字符的个数=不考虑通配符匹配的个数+S中通配符的个数+T中通配符的个数-S和T通配符匹配的个数。就是个简单容斥。
代码如下:
#include
using namespace std;
const int N=200010,M=998244353;
typedef long long LL;
int n,m,limit,l,R[N<<2],sum[N],ans[N];
LL A[N<<2],B[N<<2],f[N<<2];
char s[N<<2],t[N<<2];
LL qmi(LL a,LL b)
{
LL res=1;
while(b)
{
if(b&1)res=res*a%M;
a=a*a%M;
b>>=1;
}
return res%M;
}
void init()
{
memset(s,0,sizeof(s));//注意s,t一定要初始化,因为我们遍历的时候用的 limit
memset(t,0,sizeof(t));
memset(ans,0,sizeof(ans));
memset(sum,0,sizeof(sum));
memset(f,0,sizeof(f));
limit=1,l=-1;
while(limit<=n+m) limit<<=1,l++;
for(int i=0;i>1]>>1)|((i&1)<>_;
while(_--)
{
cin>>n>>m;
init();
cin>>s>>t;
reverse(t,t+m);
for(int k=0;k<=10;k++)
{
char ch;
if(k<=9)ch='0'+k;
else ch='*';
for(int i=0;i=m)f[i]=(f[i]-sum[i-m]+M)%M;
}
for(int i=m-1;i
总结:FFT/NTT 求字符串匹配问题关键在于通过卷积求 F数组,进而处理相关问题。
思路:用优先队列从后往前贪心就好了。
思路:简单的前缀和问题,如果某段区间内存在类型1,相当于从类型1的位置往后计算颜色,所以维护每个位置最左边的1在哪就行了。最后和255取个min
代码如下:
#include
using namespace std;
const int N=100010;
int sum[N][4],lt[N];
int n,q;
int main()
{
int _;
scanf("%d",&_);
while(_--)
{
scanf("%d%d",&n,&q);
for(int i=1,op,x;i<=n;i++)
{
scanf("%d%X",&op,&x);
lt[i]=(op==1) ? i : lt[i-1];
for(int j=3;j>=1;j--)
{
sum[i][j]=x%256;
x/=256;
sum[i][j]+=sum[i-1][j];
}
}
for(int i=1,l,r;i<=q;i++)
{
scanf("%d%d",&l,&r);
if(l<=lt[r])l=lt[r];
for(int j=1;j<=3;j++)printf("%02X",min(255,sum[r][j]-sum[l-1][j]));
puts("");
}
}
return 0;
}
大意:给定一个 n*n 的地图,从起点为(1,1),终点为(n,n)。每个位置都有两个数字 a i , j a_{i,j} ai,j和 b i , j b_{i,j} bi,j 每次只能向下或者向右走, S a S_a Sa表示走到终点处a的数字之和, S b S_b Sb表示走到终点b的数字之和。求 S a ∗ S b S_a * S_b Sa∗Sb的最大值。n<=100, a i , j , b i , j a_{i,j},b_{i,j} ai,j,bi,j均为正数。
思路:这题大致扫一眼,这不是一道很简单的dp吗?然后迅速的敲完代码,然后果断wa。其实仔细想想这题是没有最优子结构的。很容易举出反例,假设 2*2d的地图,在(1,2)时 a为 1000,b为 1,但是在 (2,1)时候a=500,b=3。那么我们会从 (2,1)的时候转移过来,但是如果(2,2)处a=1,b=50。那么显然从(1,2)处转移过来是最优的。所以我们是不能用简单的dp写的。
方法1:时间复杂度并不是很有,但是思路很新奇,觉得有必要写一下。因为n<=100,数据还是很小的,所以就放心大胆的搞。我们在动态规划转移的时候虽然不能确定哪个是最有的,但是我们可以很容易知道哪个一定不是最优的,我们可以把一定不是最优的给去掉,然后把所有可能的结果都存下来。那么什么时候一定不是最优的呢?显然对于两对 ( a 1 , b 1 ) , ( a 2 , b 2 ) (a_1,b_1),(a_2,b_2) (a1,b1),(a2,b2) a 1 < a 2 , b 1 < b 2 a_1
代码如下:
#include
using namespace std;
const int N=110;
typedef long long LL;
typedef pair PII;
PII stk[N*N*N];
int top;
vector dp[N][N];
int a[N][N],b[N][N],n;
void check(PII x)
{
while(top&&stk[top].second<=x.second)top--;
if (!top || stk[top].first < x.first) stk[++top] = x;
}
void cale(vector a,vector b,vector &ans)
{
top=0;
int sa=a.size(),sb=b.size();
int i=0,j=0;
while(i> _;
while(_ --)
{
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j],dp[i][j].clear();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>b[i][j];
dp[1][1].push_back({a[1][1],b[1][1]});
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
{
if(i==1&&j==1)continue;
cale(dp[i][j-1],dp[i-1][j],dp[i][j]);
for(auto &x:dp[i][j])x.first+=a[i][j],x.second+=b[i][j];
}
LL ans=0;
for(auto x:dp[n][n])ans=max(ans,(LL)x.first*x.second);
cout<
方法2:既然没有最优子结构,那我们就爆搜呗,直接搜肯定不行,得剪枝,想来想去也就也没有什么好的剪枝策略,没办法了,只能设计预估函数了,还以为永远遇不到 IDA*了,这下子终于给遇到了。接下来就是想想怎么设计预估函数。虽然我们没办法求权值之和的乘积,但是要求a,b的权值之和的最大值还是很好求的。我们先预先求出来,即已知S=A+B。求A * B 的最大值,我们当前位置的权值之和为
a,b。则最理想的情况下最终结果为 (A+a)*(B+b)=(A+a) *(S-A+b) 化简得: − A 2 + ( S + b − a ) A + x y -A^2+(S+b-a)A+xy −A2+(S+b−a)A+xy 二次函数求最值。当2A=S+b-a取最值。
代码如下:
#include
using namespace std;
typedef long long LL;
const int N=110;
int n,a[N][N],b[N][N],dp[N][N];
LL ans;
LL cale(int aa,int bb,int S)
{
int A=(S+bb-aa)/2;
int B=S-A;
return (LL)(A+aa)*(B+bb);
}
void dfs(int x,int y,int aa,int bb)
{
if(x==n&&y==n)
return (void)(ans=max(ans,1LL*aa*bb));
if(xans)dfs(x+1,y,aa+a[x+1][y],bb+b[x+1][y]);
if(yans)dfs(x,y+1,aa+a[x][y+1],bb+b[x][y+1]);
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int _;
cin>>_;
while(_--)
{
cin>>n;
ans=0;
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>a[i][j];
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)cin>>b[i][j];
for(int i=0;i<=n+1;i++)for(int j=0;j<=n+1;j++)dp[i][j]=0;
for(int i=n;i>=1;i--)
for(int j=n;j>=1;j--)
{
int sum=a[i][j]+b[i][j];
if(i==n&&j==n)dp[i][j]=sum;
else dp[i][j]=max(dp[i+1][j],dp[i][j+1])+sum;
}
dfs(1,1,a[1][1],b[1][1]);
cout<
总结:逆向思维的动态规划很奇妙,IDA*真的牛,预估函数设计的好快到飞起。
大意:本来是构建线段树时当l=r即区间长度为 1 时return ,现在改成(r - l + 1 <= k) return ; 问总共需要多少个节点。
思路:比赛时想的是模拟构建往下推,写的很麻烦,实际上直接写个记忆化搜索就好了,但是l,r<=1e18,数组存不下,所以用unorder_map存。
代码如下:
#include
using namespace std;
typedef long long LL;
LL n,k;
unordered_map dp;
LL dfs(LL l,LL r)
{
if(r-l+1<=k)return 1;
if(dp.count(r-l+1))return dp[r-l+1];
LL v=1;
LL mid=l+r >> 1;
v+=dfs(l,mid);
v+=dfs(mid+1,r);
dp[r-l+1]=v;
return v;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
int _;
cin>>_;
while(_--)
{
cin>>n>>k;
cout<
总结:多次用到同一内容可以考虑同dp,dp+搜索=记忆化搜索。