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 分。
问题:计数题不够熟练、没有养成写草稿和画图的习惯。
方案:勤动笔,多画图。
以上。

浙公网安备 33010602011771号