25-暑期-来追梦noip-卷3 总结

开题顺序:B-A-C(D 没看)

分配时间:B 1h A 30min C 30min

A

显然有:

\(dis_{i,j}=w_i+w_j-2 \times w_{lca_{i,j}}\)

\(dis_{i,k}=w_i+w_k-2 \times w_{lca_{i,k}}\)

\(dis_{j,k}=w_j+w_k-2 \times w_{lca_{j,k}}\)

注意到,后面那部分都是 \(2\) 的倍数,所以要让这三个相等,仅需 \(dp_i,dp_j,dp_k\)\(2\) 后相等即可。用乘法原理简单统计即可。

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=5e5+5,M=32;
int _,n;
int dis[N];
struct NODE{
	int v,w;
};
vector<NODE> G[N];

void dfs(int cur,int fa){
	for(auto i:G[cur]){
		if(i.v==fa)
			continue;
		dis[i.v]=(dis[cur]+i.w)%2;
		dfs(i.v,cur);
	}
}
void solve(){
	for(int i=1;i<=n;i++)
		G[i].clear();
	cin>>n;
	for(int i=1,u,v,w;i<n;i++){
		cin>>u>>v>>w;
		G[u].push_back({v,w});
		G[v].push_back({u,w});
	}
	dfs(1,0);
	int cnt1=0,cnt2=0;
	for(int i=1;i<=n;i++){
		if(!dis[i]) cnt1++;
		if(dis[i]==1) cnt2++;
	}
	cout<<cnt1*cnt1*cnt1+cnt2*cnt2*cnt2<<'\n';
}

signed main(){
	//freopen("T1.in","r",stdin);
	//freopen("T1.out","w",stdout);
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>_;
	while(_--)
		solve();
	return 0;
} 

总结:

  • 计数题:推式子思想(写下来)。

B

考虑分类讨论。

黑点位于端点时,直接加上子树内外的红点联通块内的红点个数即可。

黑点位于中间时,直接选两个红点联通块然后将红点个数用乘法原理统计即可,注意去重。

实现
#include<bits/stdc++.h>
#define int long long
using namespace std;

const int N=1e6+5,M=32;
int n,ans;
int a[N],num[N],fa[N];
vector<int> G[N];

int fnd(int x){
	return x==fa[x]?x:fa[x]=fnd(fa[x]);
}
void dfs(int cur){
	int sum=0;
	vector<int> tmp;
	for(int i:G[cur]){
		if(!a[i]){
			int cur=fnd(i);
			tmp.push_back(num[cur]);
			sum+=num[cur],ans+=num[cur];
		}
	}
	int cnt=0;
	for(int i:tmp)
		cnt+=i*(sum-i);
	ans+=cnt/2;
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	//freopen("T2.in","r",stdin);
	//freopen("T2.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++){
		char c; cin>>c;
		a[i]=(c=='B'?1:0);
		num[i]=1,fa[i]=i;
	}
	for(int i=1,u,v;i<n;i++){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
		if(!a[u]&&!a[v]){
			int uu=fnd(u),vv=fnd(v);
			if(uu!=vv){
				fa[uu]=vv;
				num[vv]+=num[uu];
			}
		}
	}
	for(int i=1;i<=n;i++)
		if(a[i])
			dfs(i);
	cout<<ans<<'\n';
	return 0;
}

总结:

  • 计数题:分类讨论。

C

并查集倒着做即可,注意一下最后可能图也不连通,需要单独加一下贡献。

这题我赛时想出来了,只是没时间写。

实现
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;

const int N=1e6+5,M=32;
int n,m,q;
string s[N];
int x[N],a[N],sum[N],fa[N];
bool vis[N];
vector<int> G[N];
pair<int,int> e[N];

int fnd(int x){
	return x==fa[x]?x:fa[x]=fnd(fa[x]);
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>n>>m>>q;
	for(int i=1,u,v;i<=m;i++){
		cin>>u>>v;
		e[i]={u,v};
	}
	for(int i=1;i<=q;i++){
		cin>>s[i];
		if(s[i][0]=='D')
			cin>>x[i],vis[x[i]]=1;
	}
	for(int i=1;i<=n;i++)
		cin>>a[i],sum[i]=a[i],fa[i]=i;
	for(int i=1;i<=m;i++){
		if(!vis[i]){
			int u=fnd(e[i].first),v=fnd(e[i].second);
			if(u!=v){
				if(v>u) swap(u,v);
				fa[u]=v,sum[v]+=sum[u];
			}
		}
	}
	int tme=q+1,ans=tme*sum[1];
	for(int i=q;i>=1;i--){
		if(s[i][0]=='D'){
			int u=fnd(e[x[i]].first),v=fnd(e[x[i]].second);
			if(u!=v){
				if(v>u)
					swap(u,v);
				fa[u]=v,sum[v]+=sum[u];
				if(v==1)
					ans+=sum[u]*tme;
			}
		}
		else
			tme=i;
	}
	for(int i=1;i<=n;i++)
		if(fnd(i)!=1)
			ans+=a[i]*tme;
	cout<<ans;
	return 0;
}

D

"使得所有集合中包含元素最多的集合元素个数最少" \(\to\) SCC 数量最多。

考虑什么样的连边能增加 SCC 数量。画个图可知,只有交叉且不同方向的连边才能增加 SCC 数量。

对着这个图思考,显然我们应该先考虑第二条河向第一条河的连边,因为它可以约束第一条河向第二条河的连边。

于是对于所有边按照 \(x_i\) 升序排序,\(x_i\) 相同时 \(y_i\) 降序排序。从贪心的角度考虑,每次第二条河向第一条河的连边就让 \(\max\{y_i\}\)\(x_i\) 连边,然后另一种自然连边即可。

最后跑一边 tarjan 即可,注意第二条河上的坐标要 \(+a\)

实现
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;

const int N=1e6+5,M=32;
int a,b,m,cnt,tot;
string s[N];
int dfn[N],low[N],ans[N];
bool vis[N],instk[N];
stack<int> stk;
vector<int> G[N];
struct NODE{
	int x,y,id;
}e[N];

bool cmp(NODE &u,NODE &v){
	if(u.x==v.x)
		return u.y>v.y;
	return u.x<v.x;
}
void tarjan(int cur){
    stk.push(cur);
    instk[cur]=1;
    dfn[cur]=low[cur]=++cnt;
    for (int i : G[cur]) {
        if (!dfn[i]) {
            tarjan(i);
            low[cur]=min(low[cur],low[i]);
        } else if (instk[i]) {
            low[cur]=min(low[cur],dfn[i]);
        }
    }
    if (dfn[cur]==low[cur]) {
        ++tot;
        while (stk.top()!=cur) {
            instk[stk.top()]=0;
            stk.pop();
        }
        instk[cur]=0;
        stk.pop();
    }
}

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cin>>a>>b>>m;
	for(int i=1;i<a;i++)
		G[i].push_back(i+1);
	for(int i=1;i<b;i++)
		G[i+a].push_back(i+a+1);
	for(int i=1;i<=m;i++){
		cin>>e[i].x>>e[i].y;
		e[i].id=i;
	}
	sort(e+1,e+m+1,cmp);
	int rmx=-1e9;
	for(int i=1;i<=m;i++){
		if(rmx>=e[i].y){
			ans[e[i].id]=0;
			G[e[i].x].push_back(e[i].y+a);
		}
		else{
			ans[e[i].id]=1;
			rmx=e[i].y;
			G[rmx+a].push_back(e[i].x);
		}
	}
	for(int i=1;i<=a+b;i++)
		if(!dfn[i])
			tarjan(i);
	cout<<tot<<'\n';
	for(int i=1;i<=m;i++)
		cout<<ans[i]<<' ';
	return 0;
}

总结:

  • 画图。

结语

成绩:10+0+0+0=10,累计挂分 50 分。

问题:计数题不够熟练、没有养成写草稿和画图的习惯。

方案:勤动笔,多画图。

以上。

posted @ 2025-08-16 22:48  _KidA  阅读(14)  评论(0)    收藏  举报