ABC287 贪心 字典树 树上背包

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*sz1szsz1这双重循环。

这样一个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';
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值