P3264 题解

Description

LOJ 传送门

Solution

算法一

首先考虑只有一种频道的情况。

注意到最终形成的是一棵树,并且 k k k 较小,这些启发我们做状压 dp \text{dp} dp

f i , s f_{i,s} fi,s 表示,目前树的 i i i,且树内包含了情报站集合 s s s

转移的时候,我们需要分类讨论 i i i 的儿子数为 1 1 1 以及不小于 2 2 2 的情况。对于前者,转移形如 f i , s → f j , s f_{i,s} \to f_{j,s} fi,sfj,s;对于后者,转移形如 f i , t + f i , s − t → f i , s ( t ∈ s ) f_{i,t}+f_{i,s-t} \to f_{i,s}(t \in s) fi,t+fi,stfi,s(ts)

于是,我们从小到大枚举 s s s,先通过枚举子集处理第二种情况,然后通过一遍 Dijkstra 对 f 1 , s , f 2 , s , ⋯   , f n , s f_{1,s},f_{2,s},\cdots,f_{n,s} f1,s,f2,s,,fn,s 进行松弛。

时间复杂度 O ( 3 p n + m log ⁡ m 2 p ) O(3^pn+m \log m 2^p) O(3pn+mlogm2p),其中 3 p 3^p 3p 为枚举子集的复杂度。

PS: 其实这就是最小斯坦纳树的板子。

算法二

对于多个频道的情况,我们该怎么办呢?

g S g_S gS 表示,所有频道属于集合 S S S 的情报站对应的最小斯坦纳树的大小。注意到,答案即为,挑选出若干个 S S S,在它们能够覆盖到所有的情报站的前提下,让它们的 g g g 之和最小。

预处理出每个 g S g_S gS,然后进行状压 dp \text{dp} dp 即可。

时间复杂度是什么呢?似乎是 O ( 2 p ( 3 p n + m log ⁡ m 2 p ) ) O(2^p(3^pn+m \log m 2^p)) O(2p(3pn+mlogm2p)) 的,无法通过。但是,我们感觉严重跑不满,于是我们写了一个程序:

#include <bits/stdc++.h>
using namespace std;
const int maxl=15;

int n=1000,m=3000,ans;
int f[maxl],pow3[maxl],pow2[maxl];

signed main(){
	pow3[0]=pow2[0]=1;
	for (int i=1;i<=10;i++)  pow3[i]=pow3[i-1]*3,pow2[i]=pow2[i-1]*2;
	for (int i=1;i<=10;i++)  f[i]=pow3[i]*n+pow2[i]*n*10;
	for (int i=0;i<(1<<10);i++){
		int sumv=0,cnt=0;
		for (int j=1;j<=n;j++){
			if (i&(1<<(j-1)))  cnt+=j,sumv+=f[j];
		}
		ans=max(ans,sumv);
	}
	cout<<ans<<endl;
	return 0;
}

发现这个值只有 1 0 8 10^8 108 左右,所以可以通过。

Code

#include <bits/stdc++.h>
#define int long long
#pragma GCC optimize("Ofast")
#define rg register
#define inf 1000000000000007
using namespace std;
const int maxn=1005,maxm=3005,maxk=15,maxs=1030;

int read(){
	int s=0,w=1;char ch=getchar();
	while (ch<'0'||ch>'9'){if (ch=='-')  w=-w;ch=getchar();}
	while (ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+(ch^'0');ch=getchar();}
	return s*w;
}
int n,m,k,cnt,len;
int head[maxn],dis[maxn],vis[maxn],p[maxk],q[maxk],tmp[maxk];
int f[maxn][maxs],g[maxs];

struct edge{int nxt,to,dis;}e[maxm<<1];
struct node{
	int dis,pos;
	bool operator < (const node &x) const{return x.dis<dis;}
};
priority_queue<node> que;
vector<int> Bin;
void chkmin(int x,int &y){y=min(x,y);}
void add_edge(int u,int v,int w){e[++cnt].to=v,e[cnt].dis=w,e[cnt].nxt=head[u],head[u]=cnt;}

namespace Dream_Tree{
	void dijkstra(){
		while (!que.empty()){
			int now=que.top().pos;que.pop();
			if (vis[now])  continue;
			vis[now]=1;
			
			for (int i=head[now];i;i=e[i].nxt){
				int y=e[i].to;
				if (dis[y]>dis[now]+e[i].dis){
					dis[y]=dis[now]+e[i].dis;
					if (!vis[y])  que.push((node){dis[y],y});
				}
			}
		}
	}
	int solve(){
		int k=Bin.size(),fi=0;
		for (int i=1;i<=n;i++){
			for (int j=0;j<(1<<k);j++)  f[i][j]=inf;
		}
		for (int i=0;i<k;i++){
			int now=Bin[i];
			if (!fi)  fi=Bin[i];
			f[now][(1<<i)]=0;
		}
		for (int s=0;s<(1<<k);++s){
			for (rg int i=1;i<=n;++i){
				for (rg int t=(s&(s-1));t;t=(t-1)&s)
				  f[i][s]=min(f[i][s],f[i][t]+f[i][s^t]); 
			}
			for (int i=1;i<=n;i++)  vis[i]=0;
			for (rg int i=1;i<=n;i++){
				dis[i]=f[i][s];
				que.push((node){f[i][s],i});
			}
			dijkstra();
			for (int i=1;i<=n;i++)  f[i][s]=dis[i];
		}
		return f[fi][(1<<k)-1];
	}
}

signed main(){
	n=read(),m=read(),k=read();
	for (int i=1;i<=m;i++){
		int u=read(),v=read(),w=read();
		add_edge(u,v,w),add_edge(v,u,w);
	}
	for (int i=1;i<=k;i++){
		q[i]=read(),p[i]=read();
		tmp[++len]=q[i];
	}
	sort(tmp+1,tmp+len+1);
	len=unique(tmp+1,tmp+len+1)-tmp-1;
	for (int i=1;i<=k;i++)  q[i]=lower_bound(tmp+1,tmp+len+1,q[i])-tmp;
	for (int i=1;i<(1<<len);i++){
		Bin.clear();
		for (int j=1;j<=k;j++){
			if (i&(1<<(q[j]-1)))  Bin.push_back(p[j]);
		}
		g[i]=Dream_Tree::solve();
	}
	for (int i=0;i<(1<<len);i++){
		for (int j=i;j;j=(j-1)&i)  chkmin(g[j]+g[i^j],g[i]);
	}
	cout<<g[(1<<len)-1]<<endl;
	return 0;
}
### 问题解析 P8306 是一道编程竞赛题目,通常涉及字符串处理和数据结构的应用。题目要求解决的是多个查询,每个查询给出一个字符串,要求输出该字符串在给定的一组字符串中的出现次数。这种问题通常可以通过高效的字符串匹配算法或数据结构来解决。 ### 解题方法 1. **Trie 树(前缀树)** - Trie 是一种树形数据结构,用于高效存储和检索字符串集合。通过构建 Trie 树,可以快速查询某个字符串是否存在于集合中,以及统计其出现次数。 - 在构建 Trie 树时,每个节点可以记录经过该节点的次数,这样在查询时可以直接返回该值。 2. **哈希表(Hash Map)** - 哈希表是一种非常直接且高效的解决方案。将输入的字符串集合存储在一个哈希表中,键为字符串,值为该字符串的出现次数。 - 查询时,只需查找哈希表中对应的键,即可得到出现次数。 3. **AC 自动机** - 如果需要处理多模式匹配问题,AC 自动机是一个更高级的选择。它结合了 Trie 树和 KMP 算法的思想,能够在多个模式字符串中快速查找匹配。 ### 示例代码 以下是一个使用哈希表的简单实现: ```cpp #include <iostream> #include <unordered_map> #include <vector> #include <string> using namespace std; int main() { int n, q; cin >> n >> q; unordered_map<string, int> countMap; // 读取n个字符串并统计出现次数 for (int i = 0; i < n; ++i) { string s; cin >> s; countMap[s]++; } // 处理q个查询 for (int i = 0; i < q; ++i) { string t; cin >> t; cout << countMap[t] << endl; } return 0; } ``` ### 代码说明 - **输入处理**:首先读取两个整数 `n` 和 `q`,分别表示字符串集合的大小和查询的数量。 - **统计出现次数**:使用 `unordered_map` 来统计每个字符串的出现次数。 - **查询处理**:对于每个查询字符串,直接在哈希表中查找并输出其出现次数。 这种方法的时间复杂度接近线性,适用于大多数情况。 --- ### 相关问题 1. 如何使用 Trie 树实现 P8306 的解法? 2. 在处理大量字符串时,如何优化哈希表的性能? 3. 什么是 AC 自动机,它在字符串匹配问题中的应用是什么? 4. 如何处理字符串重复出现的情况? 5. 如果字符串集合非常大,如何减少内存占用?
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值