D
贪心?
两个串匹配,要求可以随意替换其中的?,使得完全相等。
从s里选一个前缀+一个后缀,与t长度相同,问能否与t匹配?对s所有长度的前缀回答这个问题
前缀+后缀拼起来和t匹配,那么只要s前缀和t前缀匹配,s后缀和t后缀匹配就行了。预处理前后缀匹配,然后枚举前缀长度,前后缀分解即可
注意到前缀匹配是单调性的,维护最长匹配前缀长度即可。
void solve(void){
string s,t;
cin>>s>>t;
int n=t.size(),m=s.size();
int pre=0,suf=0;
rep(i,0,n-1){
if(s[i]==t[i]||s[i]=='?'||t[i]=='?'){
pre++;
}
else{
break;
}
}
reverse(s.begin(),s.end());
reverse(t.begin(),t.end());
rep(i,0,n-1){
if(s[i]==t[i]||s[i]=='?'||t[i]=='?'){
suf++;
}
else{
break;
}
}
rep(i,0,n){
if(pre>=i&&suf>=n-i){
cout<<"Yes\n";
}
else{
cout<<"No\n";
}
}
}
E
trie板子
一个字符串序列,问sis_isi和其它所有串的lcp,对于每个i执行这个询问
维护一个包含所有串的trie,询问哪个时暂时删除哪个即可
class TrieNode {
public:
TrieNode* children[26]; // 假设只支持小写字母
bool isEndOfWord;
int prefixCount; // 以该节点为前缀的字符串数量
TrieNode() : isEndOfWord(false), prefixCount(0) {
fill(begin(children), end(children), nullptr);
}
};
class Trie {
private:
TrieNode* root;
public:
Trie() {
root = new TrieNode();
}
// 插入字符串
void insert(const string& word) {
TrieNode* node = root;
for (char c : word) {
int index = c - 'a';
if (!node->children[index]) {
node->children[index] = new TrieNode();
}
node = node->children[index];
node->prefixCount++; // 更新前缀计数
}
node->isEndOfWord = true;
}
// 查询字符串是否存在
bool search(const string& word) {
TrieNode* node = root;
for (char c : word) {
int index = c - 'a';
if (!node->children[index]||node->children[index]->prefixCount==0) {
return false;
}
node = node->children[index];
}
return node->isEndOfWord;
}
// 获取给定字符串与字典树中字符串的最长公共前缀长度
int lcp(const string& word) {
TrieNode* node = root;
int length = 0;
for (char c : word) {
int index = c - 'a';
if (node->children[index] && node->children[index]->prefixCount > 0) {
length++;
node = node->children[index];
} else {
break;
}
}
return length;
}
// 删除字符串
void remove(const string& word) {
TrieNode* node = root;
for (char c : word) {
int index = c - 'a';
if (!node->children[index]) {
node->children[index] = new TrieNode();
}
node = node->children[index];
node->prefixCount--; // 更新前缀计数
}
node->isEndOfWord = false;
}
int ask(const string& word) {
int res = 0;
TrieNode* node = root;
for (char c : word) {
int index = c - 'a';
if (!node->children[index]) {
node->children[index] = new TrieNode();
}
node = node->children[index];
res+=node->isEndOfWord;
}
return res + node->prefixCount;
}
}tr;
void solve(void){
int n;
cin>>n;
vector<string>s(n+1);
rep(i,1,n){
cin>>s[i];
tr.insert(s[i]);
}
rep(i,1,n){
tr.remove(s[i]);
cout<<tr.lcp(s[i])<<'\n';
tr.insert(s[i]);
}
}
F
树上背包
导出子图是选一些点,并保留所有两端都在这个点集中的边。问一棵树的所有导出子图中,连通块个数分别为[1,k][1,k][1,k]的方案数
dp部分比较好想,dp(i,j,0/1)dp(i,j,0/1)dp(i,j,0/1)表示iii子树,jjj个连通块,iii点选不选的方案数
转移时类似一个背包统计方案数
关键是复杂度分析,这个转移过程,实际上就是每层dfs内,每个子树,都和前面枚举过的子树组成贡献,即sz∗sz1sz*sz1sz∗sz1这双重循环。
这样一个dfs(u)dfs(u)dfs(u)的总计算量,实际上等价于,子树uuu内所有点构成的点对。总的计算量,等价于树中,每个子树的点对数之和,这个有点反直觉,实际上就等价于双重循环枚举n个点构成的所有点对,只有O(n2)O(n^2)O(n2)
int dp[5050][5050][2];
void solve(void){
int n;
cin>>n;
vvi g(n+1);
rep(i,1,n-1){
int u,v;
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
auto &&dfs=[&](auto &&dfs,int u,int f)->int{
int sz=1;
dp[u][0][0]=dp[u][1][1]=1;
for(int v:g[u]){
if(v==f)continue;
int sz1=dfs(dfs,v,u);
rep1(i,sz,0){
rep1(j,sz1,1){
dp[u][j+i][0]+=dp[u][i][0]*(dp[v][j][0]+dp[v][j][1])%M2;
dp[u][j+i][0]%=M2;
}
}
rep1(i,sz,1){
rep1(j,sz1,1){
dp[u][j+i][1]+=dp[u][i][1]*dp[v][j][0]%M2;
dp[u][j+i][1]%=M2;
dp[u][j+i-1][1]+=dp[u][i][1]*dp[v][j][1]%M2;
dp[u][j+i-1][1]%=M2;
}
}
sz+=sz1;
}
return sz;
};
dfs(dfs,1,-1);
rep(i,1,n){
cout<<(dp[1][i][0]+dp[1][i][1])%M2<<'\n';
}
}