25-暑期-来追梦noip-卷4 总结
开题顺序:B-A-C(D 没看)
分配时间:B 80min A 20min C 20min
A
预估 0(因为我大样例没跑出来),实际 50。
注意到,所有的 A 都不会消失,只会对后面的 BC 产生贡献。
于是我们遇到 A 就累加贡献,遇到连续的 BC 就加到答案里,其余的情况贡献清零即可。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
string s;
bool check(){
	for(int i=1;i+1<n;i++)
		if(s[i]=='A'&&s[i+1]=='B'&&s[i+2]=='C')
			return 1;
	return 0;
}
signed main(){
	//freopen("T1.in","r",stdin);
	//freopen("T1.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>s,n=s.size(),s='#'+s;
	int ans=0,cnt=0;
	for(int i=1;i<=n;i++){
		if(s[i]=='A')e
			cnt++;
		else if(s[i]=='B'){
			if(i<n&&s[i+1]=='C')
				ans+=cnt,i++;
			else
				cnt=0; 
		}
		else
			cnt=0;
	}
	cout<<ans;
	return 0;
}
B
预估 0,实际 0。
想复杂了,实际上是最大生成树板子。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int _,n,m;
int fa[N],p[N],e[N];
struct EDGE{
	int u,v,w;
}a[N];
int fnd(int x){
	return fa[x]==x?x:fa[x]=fnd(fa[x]);
}
bool cmp(EDGE &x,EDGE &y){
	return x.w>y.w;
}
void solve(){
	cin>>n>>m;
	for(int i=1;i<=n;i++)
		fa[i]=i,p[i]=1,e[i]=0;
	for(int i=1;i<=m;i++)
		cin>>a[i].u>>a[i].v>>a[i].w;
	sort(a+1,a+m+1,cmp);
	int ans=0;
	for(int i=1;i<=m;i++){
		int u=fnd(a[i].u),v=fnd(a[i].v);
		if(u==v){
			if(e[u]<p[u])
				e[u]++,ans+=a[i].w;
		}
		else{
			if(e[u]<p[u]||e[v]<p[v])
				fa[u]=v,e[v]+=e[u],p[v]+=p[u],e[v]++,ans+=a[i].w;
		}
	}
	cout<<ans<<'\n';
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>_;
	while(_--)
		solve();
	return 0;
} 
总结:
- 最大生成树模型。
C
预估 0(原因同 A),实际 25。
容易发现后缀 \(0\) 仅与 \(2,5\) 因子有关,所以每次询问从 \(x\) 向上跳,然后统计贡献。时间复杂度 \(\mathcal{O}(qn)\),理论上可以获得 50 pts 的好成绩。
考虑询问之前预处理出来,换根 dp 即可。
考虑从 \(x\) 换到 \(son_x\) 的过程,事实上受影响的只有 \(son_x\) 那个子树,所以只需要那个子树减掉 \(x\) 的贡献,加上 \(son_x\) 的贡献即可。
时间复杂度 \(\mathcal{O}(n+q)\)。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+5;
int n,q;
vector<int> G[N];
int siz[N],ans[N],cnt2[N],cnt5[N];
void init(){
	for(int i=1;i<=n;i++){
		int cur=i;
		while(cur%2==0)
			cnt2[i]++,cur/=2;
		cur=i;
		while(cur%5==0)
			cnt5[i]++,cur/=5; 
	}
}
void DFS(int cur,int f){
	siz[cur]=1;
	for(int i:G[cur]){
		if(i==f)
			continue;
		DFS(i,cur);
		siz[cur]+=siz[i];
	}
}
void dfs(int cur,int f,int res1,int res2){
	ans[cur]=min(res1,res2);
	for(int i:G[cur]){
		if(i==f)
			continue;
		dfs(i,cur,res1+siz[i]*(cnt2[i]-cnt2[cur]),res2+siz[i]*(cnt5[i]-cnt5[cur]));
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>q;
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	init();
	DFS(1,0),dfs(1,0,0,0);
	while(q--){
		int x;
		cin>>x,cout<<ans[x]<<'\n';
	}
	return 0;
} 
总结:
- 换根 dp:考虑换根的过程、用于预处理。
D
容易发现选 0 的总是原图的最大独立集。
进一步推广,选 1 的是去掉选 0 的点之后的最大独立集,以此类推。
这样我们就有了一个 dp 顺序,结合数据范围,考虑状压 dp。
令 \(dp_i\) 表示每个点选或不选的二进制状态 \(i\) 下的方案数,答案 \(dp_{2^n-1}\),初始 \(dp_0=1\),到这里是套路的。
接着考虑转移,显然是 \(dp_i=dp_i+dp_{i^j}\),其中 \(j\) 是 \(i\) 的一个子集。但具体而言是什么样的子集呢?显然 \(j\) 应当是 \(i\) 状态下的最大独立集。
所谓"最大独立集",无非满足"最大"与"独立",我们也从这两方面着手刻画 \(j\)。
要满足"独立",令状态 \(i\) 下的邻接点集合为 \(w_i\),则 \(j\) 必定满足 \(j \And w_j=0\),即 \(w_j\) 不存在于 \(j\) 中。
要满足"最大",则 \(j\) 加上邻接点之后必须能到达 \(i\)(不一定等于),否则就能添加更多的点了,即满足 \(w_j \And (i \oplus j)=i \oplus j\)。
然后就做完了。
实现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=17;
int n,m;
int g[1<<N],dp[1<<N],w[1<<N];
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v,u--,v--;
		g[u]|=(1<<v);
		g[v]|=(1<<u);
	}
	for(int i=0;i<(1<<n);i++)
		for(int j=0;j<n;j++)
			if((i>>j)&1)
				w[i]|=g[j];
	dp[0]=1;
	for(int i=1;i<(1<<n);i++)
		for(int j=i;j;j=i&(j-1))
			if(((w[j]&j)==0)&&((w[j]&(i^j))==(i^j)))
				dp[i]+=dp[i^j];
	cout<<dp[(1<<n)-1];
	return 0;
}
总结:
- \(\operatorname{mex}\) 在图论上是最大独立集。
结语
成绩:50+0+25+0=75,累计反向挂分 75。
问题:对于最大生成树模型、换根 dp 不熟练。
方案:加强熟练度即可。
以上。
 
                    
                
 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号