二分+状压dp 这个dp设计的太妙了
学到了 f[i][j] 处理完第i个字符 从第j个位置开始的 位置
dp[i] 处理完当前 i状态的字符的 需要的最远的位置 最后判断小于等于 n+1即可
#include <bits/stdc++.h>
using namespace std;
#define int long long
//typedef long long ll;
typedef pair<int,int> pii;
#define x first
#define y second
#define pb push_back
#define inf 1e18
#define IOS std::ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#define fer(i,a,b) for(int i=a;i<=b;i++)
#define der(i,a,b) for(int i=a;i>=b;i--)
const int maxn=1e5+10;
const int mod=1e9+7;
int qmi(int a,int b) {
int res=1;
while(b) {
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
const int N=2e5+10;
int dr[4][2]= {{-1,0},{1,0},{0,-1},{0,1}};
int n,k;
string s;
int dp[1<<17];
int f[20][N];
bool check(int mid) {
memset(dp,0x3f3f3f,sizeof(dp));
memset(f,0x3f3f3f,sizeof(f));
for(int i=0; i<k; i++) {
int t=0;
for(int j=n; j>=1; j--) {
if(s[j]=='?'||s[j]==i+'a')t++;
else t=0;
if(t>=mid)f[i][j]=mid+j;
else f[i][j]=f[i][j+1];
}
}
dp[0]=1;
for(int i=0; i<1<<k; i++) {
for(int j=0; j<k; j++) {
if(i>>j&1)continue;
if(dp[i]<=n+1)dp[i|(1<<j)]=min(dp[i|(1<<j)],f[j][dp[i]]);
}
}
if(dp[(1<<k)-1]>n+1)return false;
return true;
}
void solve() {
cin>>n>>k;
cin>>s;
s=" "+s;
int l=0,r=n;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid))l=mid+1;
else r=mid-1;
}
cout<<r<<endl;
}
signed main() {
IOS;
int _=1;
//cin>>_;
while(_--) solve();
return 0;
}