【NOTE】状态压缩动态规划
状压
本质上可能全是指数级暴力。(
分这么多类可能是为了方便我记住自己写过什么题,参考性不大,请大家别看。(((
技巧1:
把阶乘级别问题转为指数级别问题。
研究 个元素的全排列时,可以考虑 个元素的全排列,再讨论最后一个元素放啥。
把 个元素的选择情况状压为 ,那么 由 减去一个元素的子集递推而来(也就是其中 个元素全排列,减去的元素即为最后的元素)
子技巧:
实现方式是用 ,得到 的最低 位对应值。
Code
P=S;
for (int I=lowbit(P);P;I=lowbit(P))
{
P-=I; T=S^I;
//...
}
例题:[SCOI2007] 排列
求数字串 的所有排列中能被 整除的个数。
朴素阶乘级做法即 。跑不过去得(
考虑阶乘级转指数级。
把 个元素的选择情况状压。该状态则由其减少一个元素的状态递推而来,而这单个元素加在最高位(最低位也可以)。根据转移的需要,再加一维表示 的余数。
由于会有重复的数字,还需要将最终的答案除以
状态转移方程:
Code - [SCOI2007] 排列
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 12
#define MAXP 1007
int n,d;
string ss;
inline int b(int A) { return 1<<(A-1); }
inline int lowbit(int A) { return A&(-A); }
int f[1<<MAXN][MAXP];
int a[1<<MAXN];
int cnt[1<<MAXN];
long long powt[MAXN],frac[MAXN];
int CNT[MAXN];
inline void R()
{//INIT!
cin>>ss; scanf("%d",&d);
n=ss.size();
for (int i=0;i<=9;i++) CNT[i]=0;
for (int i=1;i<=n;i++) a[b(i)]=(int)(ss[i-1]-'0'),CNT[(int)(ss[i-1]-'0')]++;
int E=b(n+1)-1; f[0][0]=1; cnt[0]=0;
int P,T;
for (int S=1;S<=E;S++)
{
for (int i=0;i<d;i++) f[S][i]=0;
P=S; cnt[S]=cnt[S^lowbit(S)]+1;
for (int I=lowbit(P);P;I=lowbit(P))
{
P-=I; T=S^I;
for (int i=0;i<d;i++)
f[S][(1ll*i+1ll*a[I]*powt[cnt[T]]%d)%d]+=f[T][i];
}
}
long long mu=1;
for (int i=0;i<=9;i++) mu*=frac[CNT[i]];
printf("%lld\n",f[E][0]/mu);
return;
}
int main()
{
powt[0]=frac[0]=1;
for (int i=1;i<=10;i++) powt[i]=powt[i-1]*10,frac[i]=frac[i-1]*i;
int T;
scanf("%d",&T);
while (T--) R();
return 0;
}
类似题目:yyy loves Maths VII
- 注意:lowbit 优化下,该算法的复杂度为
子技巧:
某种一一匹配问题,转移和匹配位置/匹配对象有关之类的。也是采用状压排列,排列内的数对应匹配对象的前缀。
Atcoder好像做到过一道很典的,等会我找找。之前写这个的时候陷入过一个误区,不需要一次性把影响的位置答案算出,只需要算当前集合直接相关的答案即可。
例题:邦邦的大合唱站队
设 表示集合 内的乐队全部放到最前, 内乐队需要出队的人数。
Code - 邦邦的大合唱站队
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN (int)(1e5+233)
#define MAXM 22
int a[MAXN];
int f[1<<MAXM],sum[1<<MAXM],as[MAXM];
int n,m;
int lsum[MAXN][MAXM];
inline int b(int A) { return (1<<(A-1)); }
inline int lowbit(int A) { return (A&(-A)); }
inline int cnt(int A) { int sum=0; while (A) sum++,A>>=1; return sum; }
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++) scanf("%d",&a[i]),as[a[i]]++;
for (int i=1;i<=n;i++)
{
for (int j=1;j<=m;j++)
lsum[i][j]=lsum[i-1][j];
lsum[i][a[i]]++;
}
int E=b(m+1)-1;
for (int S=1;S<=E;S++) sum[S]=sum[S^lowbit(S)]+as[cnt(lowbit(S))],f[S]=1e9;
f[0]=0;
for (int S=1,P;S<=E;S++)
{
P=S;
for (int i=lowbit(P),l,r,d;P;i=lowbit(P))
{
P^=i; d=cnt(i);
l=sum[S^i]; r=sum[S];
f[S]=min(f[S],f[S^b(d)]+lsum[l][d]+lsum[n][d]-lsum[r][d]);
}
}
printf("%d\n",f[E]);
return 0;
}
技巧2:
选择的相对限制,比如最经典的不能相邻。
用状压可以很好的枚举和判断合法的情况。
例题:[SCOI2005] 互不侵犯
选择的位置周围八个格子不能被选择。
一行一行填的话,会发现当前行的限制只与上一行有关。于是设 表示填了前 行,第 行 状态为 ,共摆了 个的方案数
很暴力的题目。
Code - [SCOI2005] 互不侵犯
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 10
#define MAXK 85
//f[S][i]
long long f[MAXN][1<<MAXN][27];
inline int b(int A) { return 1<<(A-1); }
int pcnt[1<<MAXN];
inline int lowbit(int A) { return A&(-A); }
inline void printbit(int A) { for (int i=1;i<=3;i++) printf("%d",A&1),A>>=1; return; }
int main()
{
int n,k;
scanf("%d%d",&n,&k);
if (k*4>((n&1)?n+1:n)*((n&1)?n+1:n)) { puts("0"); return 0; }
f[0][0][0]=1;
int E=b(n+1)-1;
for (int S=1;S<=E;S++) pcnt[S]=pcnt[S^lowbit(S)]+1;
for (int i=1;i<=n;i++)
for (int S=0;S<=E;S++)
{
if ((S&(S<<1))||(S&(S>>1))) continue;
for (int T=0;T<=E;T++)
{
if ((T&(T<<1))||(T&(T>>1))) continue;
if ((S&T)||((S<<1)&T)||((S>>1)&T)) continue;
for (int c=0;c<=k;c++)
if (c+pcnt[T]>k) break;
else f[i][T][c+pcnt[T]]+=f[i-1][S][c];
}
}
long long ans=0;
for (int S=0;S<=E;S++) ans+=f[n][S][k];//,printf("f[%d][",n),printbit(S),printf("][%d]=%lld\n",k,f[n][S][k]);
printf("%lld\n",ans);
return 0;
}
子技巧:
比如上面这个题就是判 ,, 来判断相邻行状态 与 是否冲突。
来判断行状态为 自己是否冲突。
子技巧:
例题:[NOI2001] 炮兵阵地
同样是有限制,当前行选择限制与上两行有关。所以状态里面需要压两行,而单行转移复杂度就高达了
但是单行也是有限制的。符合限制的状态个数,打表后得到,只有 个。我把它们打表薄纱了(
设 表示前 行,第 行为第 种状态,第 行为第 种状态,最大放置数。转移方程即
降到
Code - [NOI2001] 炮兵阵地
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 107//!
#define MAXM 12
inline int b(int A) { if (A<0) return 0; return 1<<(A-1); }
//inline int countbit(int A) { int sum=0; while (A) sum+=A&1,A>>=1; return sum; }
//inline void printbit(int A) { printf(" "); for (int i=1;i<=4;i++) printf("%d",(int)((A&b(i))!=0)); printf(" "); return; }
/*
inline bool check(int S)
{
for (int i=1;i<=10;i++)
if ((S&b(i))&&((S&b(i-1))||(S&b(i-2))||(S&b(i+1))||(S&b(i+2)))) return false;
return true;
}
*/
//2,3,4,6
int a[100]={0,0,1,2,4,8,9,16,17,18,32,33,34,36,64,65,66,68,72,73,128,129,130,132,136,137,144,145 \
,146,256,257,258,260,264,265,272,273,274,288,289,290,292,512,513,514,516,520,521 \
,528,529,530,544,545,546,548,576,577,578,580,584,585};
int tota[MAXM]={0,2,3,4,6,9,13,19,28,41,60};
int bcnt[100]={0,0,1,1,1,1,2,1,2,2,1,2,2,2,1,2,2,2,2,3,1,2,2,2,2,3,2,3,3,1,2,2,2,2,3,2,3,3,2,3,3, \
3,1,2,2,2,2,3,2,3,3,2,3,3,3,2,3,3,3,3,4};
int f[MAXN][62][62];
//int f[61][61][210];//我的评价是,不如退役(Runtime Error)
/*
(N&S)||(N&P) continue;
f[i][S][N]=max(f[i][S][N],f[i-1][P][S]+bcnt[N])
f[1][0][S]=bcnt[S]
*/
int sta[MAXN];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
// int E=b(m+1)-1;
// int sum=0;
// int Pp=1; for (int S=0;S<=E+1;S++) { if (check(S)) sum++; if (S==Pp-1) printf("%d,",sum),Pp<<=1; }
// for (int i=1;i<=tota;i++) printf("%d,",countbit(a[i]));
char C;
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
{
cin>>C;
if (C=='H') sta[i]|=b(j);
}
// for (int i=1;i<=60;i++) printbit(a[i]),puts("");
int ans=0;
for (int i=1;i<=tota[m];i++)
{
if (!(a[i]&sta[1]))
f[1][1][i]=bcnt[i],ans=max(ans,bcnt[i]);
// printf("f[1][0]["); printbit(a[i]); printf("]=%d=f[%d][%d][%d]\n",f[1][0][i],1,0,i);
}
int P,S,N;
for (int i=2;i<=n;i++)
{
for (int IP=1;IP<=tota[m];IP++)
{
P=a[IP];
if (P&sta[i-2]) continue;
for (int IS=1;IS<=tota[m];IS++)
{
S=a[IS];
if (P&S) continue;
if (S&sta[i-1]) continue;
for (int IN=1;IN<=tota[m];IN++)
{
N=a[IN];
if ((N&S)||(N&P)) continue;
if (N&sta[i]) continue;
f[i][IS][IN]=max(f[i][IS][IN],f[i-1][IP][IS]+bcnt[IN]);
// if (N==2&&S==9&&P==0) { printf("%d",i-1); printbit(P); printbit(S); printf("%d %d+%d also = f[%d][%d][%d]\n",f[i][IS][IN],f[i-1][IP][IS],bcnt[IN],i-1,IP,IS); system("pause"); }
ans=max(ans,f[i][IS][IN]);
// printf("f[%d][",i); printbit(S); printf("]["); printbit(N); printf("]=%d\n",f[i][IS][IN]);
}
}
}
}
printf("%d\n",ans);
return 0;
}
技巧3:
或许也能处理与转移与整条轮廓相关的,但我题量太少了没见过。
子技巧:
弱化版轮廓线,但是 进制(
例题:Luogu P2435 染色
这题按顺序枚举填色位置即可。只需要考虑这个点左侧,左上位置的颜色,那么我们需要的轮廓线如下图红色部分:
然后爆转移就好了。
Code - P2435 染色
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 107
#define MAXM 15
#define MMMM (int)(1e5+233)
const int mod=376544743;
int n,m,k;
int f[2][MAXM][65537];
int P[MAXM],Q[MAXM],A[MMMM],B[MMMM];
inline void printKBIT(int S)
{
int B[10];
printf("printBIT: ");
for (int i=1;i<=m;i++) B[i]=S%k,S/=k;
for (int i=m;i>=1;i--) printf("%d ",B[i]);
puts("");
return;
}
void dfs(int I,int J,int x)
{
// puts("?");
if (x==m+1)
{
int S=0;
for (int i=1;i<=m;i++) S=S*k+P[i],Q[i]=P[i];
// printf("===================================(%d,%d) ",I,J); printKBIT(S);
// puts(""); for (int i=1;i<=m;i++) printf("%d ",P[i]); puts("");
// printf("ERRRRRRRRRRRRRRRRRRRIN: \n"); printKBIT(S);
for (int i=0;i<k;i++)
if (i!=P[J]&&i!=P[J+1])
{
int S2=0;
Q[J+1]=i;
for (int j=1;j<=m;j++) S2=S2*k+Q[j];
// printf("-------\n");
// printf(">>>>>>>>>> (%d,%d)\n",I,J);
// printf("%d ",f[I][J][S]); printKBIT(S); printf("to\n"); printKBIT(S2);
// printf("-------\n");
f[I][J+1][S2]=(f[I][J+1][S2]+f[I][J][S])%mod;
}
return;
}
if (x==J+1)
{
for (int i=0;i<k;i++)
{
P[x]=i;
dfs(I,J,x+1);
}
}
else
{
for (int i=0;i<k;i++)
if (P[x-1]==i) continue;
else P[x]=i,dfs(I,J,x+1);
}
}
inline void sol(int I,int J)
{
if (J==m)
{
int RG=pow(k,m+1); //cout<<"{"<<RG<<endl; system("pause");
for (int S=0;S<RG;S++)
{
f[I^1][0][S]=f[I][J][S];
for (int j=1;j<=m;j++)
f[I^1][j][S]=0;
}
return;
}
dfs(I,J,1);
}
//f[i][j][S1]->f[i][j+1][S2]
int main()
{
scanf("%d%d%d",&n,&m,&k);
int S0=0,Sr=0;
for (int i=1;i<=m;i++) scanf("%d",&A[i]),S0=S0*k+A[i];
for (int i=1;i<=m;i++) scanf("%d",&B[i]),Sr=Sr*k+B[i];
if (n>100)
{
for (int i=1;i<=m;i++)
if (A[i]==(B[i]^(n&1)))
return puts("0"),0;
puts("1");
return 0;
}
P[0]=A[0]=P[m+1]=A[m+1]=-1;
f[2&1][0][S0]=1;
// printKBIT(S0);
for (int i=2;i<=n;i++)
{
for (int j=0;j<=m;j++)
{
sol(i&1,j);
}
}
// printf("+%d\n",f[2][2][7]);
printf("%d\n",f[n&1][m][Sr]);
return 0;
}
同样的,这个方法也可以来优化上面那道 互不侵犯。
用一个 串来表示这个已处理状态的轮廓线:从矩阵的右上角开始,轮廓线向下为 1,向左为 0,组成长度为 的一个 串。
例题:「九省联考 2018」一双木棋
一个格子可以落子当且仅当这个格子内没有棋子,且这个格子的 左侧 及 上方 的所有格子内都有棋子。
正好是满足这个限制的。
举个例子:
这个 的矩阵中,蓝色部分是已落子的话,红色就是对应的轮廓线,表示为 。
每个落子局面都对应了一条轮廓线,然后状态就有了(
初始状态形如 ,完成状态形如
剩下的部分就是博弈 了。稍微放一下做法
两个人都在自己的回合选择最优的策略,也就是:当他知道接下来的一步下在每个地方分别对应的分数,他就会选择其中分数最高的一个。
做法也就是逆推状态。
设 表示轮廓线 到结束状态的最优决策分数。这个决策分数定义为 的总和减去 的总和。
对于这个先手妹,就是要在 的扩展中选一个最大的;对于这个后手妹,就是要在 中的扩展中选一个最小的。
这样初始状态就变为了轮廓线的最终状态,答案就变成了轮廓线初始状态的答案。
另外就是,可扩展的位置是相邻的 01,记得要判一下(
Code - 「九省联考 2018」一双木棋
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 12
int n,m;
inline int bi(int S,int I) { return ((S>>(n+m-I))&1); }
int a[MAXN][MAXN],b[MAXN][MAXN];
int f[(int)(1<<22)];
bool p[(int)(1<<22)];
bool book[(int)(1<<22)];
inline int dir(int S,int I,int typ)
{
int x=n,y=1;
for (int i=1;i<=n+m-I-1;i++)
{
if (S&1) x--;
else y++;
S>>=1;
}
if (typ==0) return b[x][y];
else return a[x][y];
}
inline void printBIT(int S)
{
int B[1007];
for (int i=n+m;i;i--)
{
B[i]=S&1;
S>>=1;
}
printf("printBIT: "); for (int i=1;i<=n+m;i++) printf("%d",B[i]); puts("");
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%d",&a[i][j]);
for (int i=1;i<=n;i++)
for (int j=1;j<=m;j++)
scanf("%d",&b[i][j]);
int MAXS=(1<<(n+m))-(1<<(m));
int MINS=(1<<(n))-1; int Sy;
for (int S=MINS;S<=MAXS;S++) f[S]=-2e9;
p[MAXS]=((n*m)&1);
book[MAXS]=1;
f[MAXS]=0;
// printBIT(MINS);
for (int S=MAXS;S>MINS;S--)
{
if (!book[S]) continue;
// printBIT(S);
for (int i=1;i<n+m;i++)
if (bi(S,i)==1&&bi(S,i+1)==0)
{
// printBIT(S>>(n+m-(i+1)));
Sy=((S^(1<<(n+m-i)))^(1<<(n+m-i-1)));
/*
printf("_________________\n");
printBIT(S);
puts("to");
printBIT(Sy);
printf("~~~~~~~~~~~~~~~~~\n");
*/
book[Sy]=1;
p[Sy]=(p[S]^1);
if (p[Sy]==1)
f[Sy]=min(f[Sy]==(int)(-2e9)?(int)(2e9):f[Sy],f[S]-dir(S,i,0));
else f[Sy]=max(f[Sy],f[S]+dir(S,i,1));
}
}
printf("%d\n",f[MINS]);
return 0;
}
技巧4:
这个好像不大算状压。只是稍微提一嘴可以这么写
现在做过的只有
例题:[NOIP2021] 数列
特征是加位时可以从小到大(按照一定顺序),并且需要考虑数的
这题的加位次数很小,只有 ,所以只需要存相邻五位的进位。
技巧5:
比如说给定一系列集合,要求分成两组(多组能不能做我不知道,等会想想),要求两组并集交集为空。
设 表示并集为 的选择方案,然后枚举其补集的子集应该就可以。
子技巧:
for (int S=1;S<(1<<n);S++)
for (int j=(S-1)&S;j;j=(j-1)&S)
f[S]=max(f[S],f[j]+f[S^j]);
复杂度好像是 的。
另外这种子集的信息合并,可能有的是取 有的是求和之类的。如果求和,并且两个集合不作区分的话,这样会算重。
下文 杂题选解 - 地震后的幻想乡 中有提到解决方法:钦定某个元素在前一个集合中,就不会重了。
例题:Educational DP Contest U - Grouping
设 表示集合 的最大价值。先处理该集合分为单组的价值,然后枚举子集合并即可。
Code - Educational DP Contest U - Grouping
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;
#define MAXN 17
inline int lowbit(int a) { return a&(-a); }
long long f[1<<MAXN];
int a[MAXN][MAXN];
int n;
int main()
{
scanf("%d",&n);
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
for (int S=1;S<(1<<n);S++)
{
f[S]=f[S-lowbit(S)];
int M=lowbit(S),sm=0;
while (M) { sm++; M>>=1; }
M=(S>>sm); int sn=sm;
while (M)
{
sn++;
if (M&1) f[S]+=a[sm][sn];
M>>=1;
}
}
for (int S=1;S<(1<<n);S++)
{
for (int j=(S-1)&S;j;j=(j-1)&S)
{
f[S]=max(f[S],f[j]+f[S^j]);
}
}
printf("%lld\n",f[(1<<n)-1]);
return 0;
}
先处理出每个
原理现在反看很自然。从大到小枚举子集呢
例题:[NOI2015] 寿司晚宴
每个数分解完最多会有一个 的质因子。把这个大质因子单独提出来,并且将数按其排序
处理跟刚才不大一样。先枚举相同大质因子的家伙(
设 分别表示该质因子给了前一个集合和该质因子给了后一个集合,两集合小质因数状态为 的方案数。 则表示在该段数前的方案数。
新加数的小质因数集合状态为 :
区间遍历完后
子技巧:
在这两个集合没有区分的情况下,直接枚举子集和补,这东西会重(
所以我们钦定某个元素在前面一个子集中,就不会重了。
技巧6:
例题:Educational DP Contest O - Matching
设 表示前一个集合的前 个点与后一个集合的 匹配的匹配数。则:
- 由于这里要求的是 ,大可以先枚举 ,再让 。
杂题选解
[HNOI2012]集合选数
感觉不套路()稍微写写
以 为第一个元素,其他位置元素填上其左侧元素 的值,或者上位元素 的值。
如:
1 2 4 8 16 32 ...
3 6 12 24 48 96 ...
9 18 36 72 ...
27 ...
一个 级别的矩阵,要求不选择相邻元素(技巧2),状压 即可。
枚举 和 之外的因子组合。其实也不需要,直接从小到大枚举第一个没被选的数。继续这样构造矩阵,反复做上面那玩意。
复杂度 。不懂()QAQ(
[SCOI2008] 奖励关
期望题,倒着做就好()似乎没啥特别的。
设 表示前 行状态为 ,第 至 轮的最大期望收益。
[ZJOI2015] 地震后的幻想乡
疯狂转化的一道题()
(问题可以转化为)给定一棵树,其边权是均匀随机生成的全排列,求所有情况最小生成树最大边的总和(虽然是期望,但是每种情况等概率)。
首先认识一个问题:最小生成树最大边,其实是往图中从小到大加边的全图联通戳。
问题又转化为:设 为在图 中选择 条边使得全图恰好连通的方案数。则:
还是不好做的。使用容斥(把恰好变为可行)再转一遍:设 为在图 中选择 条边使得全图连通的方案数。则:
仍然不好做()。再根据正难则反的单步容斥:设 为在图 中选择 条边使得全图不连通的方案数。
既然不连通了就可以拆两半了(?)再考虑计数的不重不漏,我们钦定拆出的第一个集合 是连通的。大概就会得到这样一个东西:
但其实还有个问题,由于任意连接,集合 也有可能是联通的。那么钦定某个点 在集合 中,就可以解决这个问题了。
实际上这题原意来看()最后答案是
[NOIP2017 提高组] 宝藏
可能要多给自己洗脑一下 的思想。听说这题搜索+剪枝是可以过的,发现自己甚至不大会写,震。
设状态的时候,我们需要知道:
-
当前这步转移所需要的信息;
-
状态的某种扩展方法,使得该 能覆盖最优解。
我们需要的信息是:当前扩展点在树中的深度。所以可以尝试设一维与树高相关。
转移呢?
由于加点加越深该点代价越大,可以假装当前的扩展点全都加在原本树高 这一层,而该做法可以覆盖所有合法解,并且最优解不会比不合法解劣。即每次转移树中一层的点。
预处理一个点集可以扩展的点集。扩展的时候枚举该点集的子集即可。
代价需要一个 求。
到 的最小边权和需要暴力预处理。
的。感觉很卡,乌乌
[yLOI2020] 凉凉
和宝藏类似,枚举路径加在的层数。
不写了。乌乌
[NOIP2016 提高组] 愤怒的小鸟
关键在于节省转移状态。
设 表示经过点集 所需的线数, 表示经过 两点的点集。则:
枚举 两点好像需要 ,数据范围难以接受
然而我们发现 某个 到某个 的状态,可能经历了多条不同线的不同顺序的转移,但结果实际上是一样的(?)
那我们就规定每次转移的时候这条线要包含当前未经过的第一个点,这样就节省了很多重复转移。节省掉了一个 ()
AtCoder Beginner Contest 274 E - Booster
从起点开始,每经过一个宝箱速度就会 。
设 表示现在在点 ,经过了 的城镇, 的箱子需要的最少步数
大概是
[SDOI2009] Bill的挑战
求刚好 个串匹配的串 个数。
法1:
首先看看如果一个串 和 个串匹配的具体表现:
个串,某一位上每个字符都是 或者某个相同字符
每个串集合是可枚举的(?)
好像跑一下每个集合是否有对应串也跑得过来。那样就得到和钦定 个串匹配(?)的串 个数
设 表示恰好和 个串匹配的串 个数, 表示钦定了 个串匹配的串 个数。
则:
法2:
还有转移的时候取交集这种设定啊...
设 表示当前匹配到了第 个字符,匹配状态集合为 的字符串 个数, 表示 第 位为字符 匹配的串集合。则:
[SDOI2009] 学校食堂
设 表示前 个人,第 个人已经打饭的为 的时间。
由于需要计算相邻代价,还要加一维当前最后一个打饭的人是谁
设 表示前 个人,第 个人已经打饭的为 ,最后一个打饭的人为 的最小时间。
伟大的chy曾经说过:状压就是个暴搜啊
乌乌,他说的对,我不写了/dk
END.
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】