CF1926题解
初二提高1926 题解
1092F Tree with Maximum Cost
很明显的换根。
首先假定以 \(1\) 为根,求出 \(f_i\) 表示以 \(i\) 为根的子树的权值和,\(s_i\) 表示以 \(i\) 为根的子树的答案。
\(f_i\) 的求法不多赘述,看\(s_i\)。
对于每个 \(s_i\),相当于所有子节点到他的距离都是到他儿子的距离 \(+1\),也就是 \(s_u=\sum_{v\in son(u)}s_v+f_v\)。
接着维护 \(d_i\),表示以 \(i\) 为根的树(注意并非子树)的答案。
如图:

对于每一个 \(v\),他以 \(1\) 为根的子树为 \(s_v\),而他缺少的答案就是左上放 \(u\) 的答案,更具体的,就是 \(d_u-(s_v+f_v)\).
而由于左上放的所有节点到 \(v\) 的距离相比于到 \(u\) 的距离增加了一,所以相应的也需要加上所有点的权值,也就是 \(f_1-f_v\)(不是 \(f_u\) 是因为 \(f\) 并未进行换根)。
综上,\(d_v=d_u-(s_v+f_v)+s_v+f_1-f_v\).(懒得化简了)
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Maxn=2e5+10;
struct Edge
{
int v,nxt;
}edge[2*Maxn];
int head[Maxn],cnt;
int n,ans;
int f[Maxn];
int s[Maxn];
int d[Maxn];
int a[Maxn];
void add(int u,int v)
{
cnt++;
edge[cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs1(int u,int fa)
{
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa) continue;
dfs1(v,u);
f[u]+=f[v];
}
f[u]+=a[u];
}
void dfs2(int u,int fa)
{
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa) continue;
dfs2(v,u);
s[u]+=s[v]+f[v];
}
}
void dfs3(int u,int fa)
{
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(v==fa) continue;
d[v]=s[v]+d[u]-(s[v]+f[v])+(f[1]-f[v]);
dfs3(v,u);
}
}
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<n;i++)
{
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
dfs1(1,1);
dfs2(1,1);
d[1]=s[1];
dfs3(1,1);
for(int i=1;i<=n;i++) ans=max(ans,d[i]);
cout<<ans<<endl;
return 0;
}
1095E Almost Regular Bracket Sequence
首先分析一下,整个序列可以通过一次修改变为合法的括号序列当且仅当左右括号数量之差等于二(显然的)。
而对于每一个位置,当改完这个位置之后,前后须分别合法。
检查括号序列是否合法的方法有两种(第二种这道题看题解知道的),分别是利用栈或维护一个对应的序列。
考虑维护 \(sum_i\) ,其中左括号为 \(1\),右括号为 \(-1\),加和。
不考虑修改操作,那么一个括号序列合法当且仅当 \(sum_n=0\) 且 \(\forall i \in [1,n] ,sum_i\geq 0\),这是因为首先最后所有括号都需要被匹配,其次序列中不能出现某个位置前右括号数量多余左括号。
接着考虑修改,假设要修改位置 \(i\) 为左括号,那么对于所有 \(j>i,sum_j\) 的贡献就是 \(-2\),若希望满足 \(\forall i \in [1,n] ,sum_i\geq 0\),需要令 \(j>i,sum_j\geq 2\),同时 \(j<i,sum_j\geq 0\)。
\(i\) 为右括号是类似。
#include<bits/stdc++.h>
using namespace std;
const int Maxn=1e6+10;
int n,cnt_l,cnt_r;
char c[Maxn];
int sum[Maxn];
bool f[Maxn];
int ans;
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>c[i];
if(c[i]=='(') sum[i]=sum[i-1]+1,cnt_l++;
else sum[i]=sum[i-1]-1,cnt_r++;
}
if(abs(cnt_l-cnt_r)!=2)
{
cout<<0<<endl;
return 0;
}
if(cnt_l>cnt_r)
{
f[n+1]=1;
for(int i=n;i>=1;i--) f[i]=f[i+1]&&(sum[i]>=2);
for(int i=1;i<=n && sum[i-1]>=0;i++) if(f[i] && c[i]=='(') ans++;
}
else
{
f[n+1]=1;
for(int i=n;i>=1;i--) f[i]=f[i+1]&&(sum[i]>=-2);
for(int i=1;i<=n && sum[i-1]>=0;i++) if(f[i] && c[i]==')') ans++;
}
cout<<ans<<endl;
return 0;
}
1095F Make It Connected
先不考虑额外的 \(m\) 种方法,只考虑无中生有添加边。
很明显(明显到我没想到),这种情况下只需要将所有点和点权最小的点连边即可。
证明:
- 因为边权为正,所以最终的图一定形如一棵树;
- 假设有更有的边代替原有的连边,即 \(a_u+a_v > a_v+a_w\),得到 \(a_u>a_w\),明显与题设不符,所以不可能有边代替已有连边。
综上,将点权最小的点和其余所有点连边一定是最优的。
那么只需要建 \(n+m\) 条边,接着跑最小生成树即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Maxn=2e5+10;
struct Edge
{
int u,v,w;
Edge(int uu,int vv,int ww) :u(uu),v(vv),w(ww) {}
};
vector<Edge>edge;
int n,m;
int ans;
struct numb
{
int id,x;
}a[Maxn];
int f[Maxn];
bool cmp1(numb x,numb y)
{
return x.x<y.x;
}
bool cmp2(Edge x,Edge y)
{
return x.w<y.w;
}
int find(int x)
{
if(f[x]==x) return x;
else return f[x]=find(f[x]);
}
void merge(int x,int y)
{
f[find(x)]=find(y);
}
bool check(int x,int y)
{
return find(x)==find(y);
}
signed main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
{
f[i]=i;
cin>>a[i].x;
a[i].id=i;
}
sort(a+1,a+n+1,cmp1);
for(int i=2;i<=n;i++)
{
edge.push_back(Edge(a[1].id,a[i].id,a[1].x+a[i].x));
}
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
edge.push_back(Edge(u,v,w));
}
sort(edge.begin(),edge.end(),cmp2);
for(int i=0;i<edge.size();i++)
{
int u=edge[i].u,v=edge[i].v,w=edge[i].w;
if(check(u,v)) continue;
merge(u,v);
ans+=w;
}
cout<<ans<<endl;
return 0;
}
1105D Kilani and the Game
按题意模拟即可。为每一个玩家维护一个队列,每次进行 bfs 直到队列为空或队首距离格子为 \(i\) 的距离超过 \(a_i\)。
#include<bits/stdc++.h>
using namespace std;
const int dx[]={-1,0,1,0};
const int dy[]={0,-1,0,1};
struct state
{
int x,y;
int step;
state(int xx,int yy,int sstep) :x(xx),y(yy),step(sstep) {}
};
int n,m,p;
queue<state>q[10];
char c[1010][1010];
int a[10],b[10];
int ans[10];
bool check()
{
for(int i=1;i<=p;i++) if(q[i].size()) return 1;
return 0;
}
int main()
{
cin>>n>>m>>p;
for(int i=1;i<=p;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>c[i][j];
if(c[i][j]>='1' && c[i][j]<='9')
{
for(int k=0;k<4;k++)
{
q[c[i][j]-'0'].push(state(i+dx[k],j+dy[k],1));
}
}
}
}
while(check())
{
for(int i=1;i<=p;i++)
{
b[i]+=a[i];
while(!q[i].empty() && q[i].front().step<=b[i])
{
int x=q[i].front().x,y=q[i].front().y,step=q[i].front().step;
q[i].pop();
if(x<=0 || x>n || y<=0 || y>m || c[x][y]!='.') continue;
c[x][y]=i+'0';
for(int j=0;j<4;j++)
{
q[i].push(state(x+dx[j],y+dy[j],step+1));
}
}
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if(c[i][j]>='1' && c[i][j]<='9') ans[c[i][j]-'0']++;
for(int i=1;i<=p;i++) cout<<ans[i]<<" ";
cout<<endl;
return 0;
}
1114D Flood Fill
区间 dp,设 \(dp_{i,j}\) 表示将 \([i,j]\) 区间变为颜色相同的最小代价。
那么很明显,如果 \(c_i=c_j\) ,\(dp_{i,j}=dp_{i-1,j+1}\).
否则,\(dp_{i,j}=\min (dp_{i,j-1}+[c_j=c_{j-1}],dp_{i+1,j}+[c_i=c_{i+1}])\).
#include<bits/stdc++.h>
using namespace std;
const int Maxn=5010;
int n;
int c[Maxn];
int f[Maxn][Maxn];
int main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>c[i];
for(int len=2;len<=n;len++)
{
for(int i=1;i+len-1<=n;i++)
{
int j=i+len-1;
f[i][j]=1e9;
if(c[i]==c[j])
{
if(len==2) f[i][j]=min(f[i][j],0);
else f[i][j]=min(f[i+1][j-1]+1,f[i][j]);
}
if(c[j]==c[j-1]) f[i][j]=min(f[i][j],f[i][j-1]);
else f[i][j]=min(f[i][j],f[i][j-1]+1);
if(c[i]==c[i+1]) f[i][j]=min(f[i][j],f[i+1][j]);
else f[i][j]=min(f[i][j],f[i+1][j]+1);
}
}
cout<<f[1][n]<<endl;
return 0;
}
1117C Magic Ship
很明显,如果 \(k\) 是最终答案,那么所需时间少于 \(k\) 一定到不了,而所需时间超过 \(k\) 一定能到。
所以是一个 01 二分,考虑怎么 check。
把被风吹和自行移动两个操作分开考虑,当时间经过了 \(k\),如果我不做任何操作,只由风吹小船,那么小船的坐标可以用前缀和预处理得到,此时只要小船的坐标到终点的距离小于等于 \(k\),说明 \(k\) 是合法的。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int Maxn=1e5+10;
int f[5];
int sx,sy;
int tx,ty;
int nx,ny;
int n;
int num;
int x[Maxn],y[Maxn];
int dis(int xx1,int yy1,int xx2,int yy2)
{
return abs(xx1-xx2)+abs(yy1-yy2);
}
bool check(int t)
{
int q,r; q=t/n; r=t-n*q;
return dis(sx+x[n]*q+x[r],sy+y[n]*q+y[r],tx,ty)<=t;
}
signed main()
{
cin>>sx>>sy>>tx>>ty;
cin>>n;
for(int i=1;i<=n;i++)
{
char c;
cin>>c;
if(c=='U') x[i]=x[i-1],y[i]=y[i-1]+1;
else if(c=='D') x[i]=x[i-1],y[i]=y[i-1]-1;
else if(c=='L') x[i]=x[i-1]-1,y[i]=y[i-1];
else x[i]=x[i-1]+1,y[i]=y[i-1];
}
int l,r,mid,ans=-1;
l=0,r=1e18;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid)) r=mid-1,ans=mid;
else l=mid+1;
}
cout<<ans<<endl;
return 0;
}
1119E Pavel and Triangles
根据一个显然的性质:\(2^i+2^j< 2^k (i< j<k)\),则最终组成的三角形中最长边至少有两个,话句话说,拼出来的三角形必定是等腰三角形(等边三角形也等腰)。
贪心的考虑,对于每个 \(2^i\),可以和边长小于等于 \(2^i\) 的木棍组成三角形。设 \(num\) 表示长度小于 \(2^i\) 的木棍数量。如果 \(a_i\geq 2\times num\) ,说明匹配完等腰三角形后还能匹配等边。这时再假设有 \(x\) 个等边,那么会有 \(a_i-x=2\times (num+x)\),解得 \(x=\lfloor \dfrac{a_i-2\times num}{3} \rfloor\).
记得维护 \(sum\).
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int Maxn=3e5+10;
int n,num,ans;
int a[Maxn];
signed main()
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=n;i++)
{
if(a[i]>=num*2)
{
int x=(a[i]-2*num)/3;
ans+=num+x;
a[i]-=2*(num+x)+x;
num=0;
}
else
{
ans+=a[i]>>1;
num-=a[i]>>1;
a[i]&=1;
}
num+=a[i];
}
cout<<ans<<endl;
return 0;
}
1120A Diana and Liana
很明显的双指针,对于每个 \(l\) 找到最近的 \(r\).
难点在于如何输出答案。假设找到了长度最小的 \([l,r]\),首先通过删除其中冗余项使得其长度小于等于 \(k\),随后再从 \([1,l)\) 中删除若干项使得 \([l,r]\) 在最终序列中位于同一个块内。
可以用反证法证明如果长度最小的 \([l,r]\) 无解,那么长度大于其的也一定无解。
#include<bits/stdc++.h>
using namespace std;
const int Maxn=5e5+10;
int m,k,n,s;
int M;
int a[Maxn];
int b[Maxn];
int f[Maxn];
int del[Maxn];
int last;
int ans;
pair<int,int>range;
void get_ans(int l,int r)
{
if(r-l+1-k+(l-1)%k<=ans)
{
ans=r-l+1-k+(l-1)%k;
range=make_pair(l,r);
}
}
bool check()
{
for(int i=last;i<=Maxn-10;i++) if(f[i]<b[i])
{
last=i;
return 0;
}
last=1;
return 1;
}
int main()
{
ans=1e9;
last=1;
cin>>m>>k>>n>>s;
M=m;
for(int i=1;i<=m;i++) cin>>a[i];
for(int i=1;i<=s;i++)
{
int x;
cin>>x;
b[x]++;
}
int l,r;
l=1; r=0;
for(r=0;r<=m && !check();r) f[a[++r]]++;
if(check()) get_ans(l,r);
while(r<=m)
{
int temp=a[l];
f[a[l++]]--;
if(f[temp]>=b[temp])
{
get_ans(l,r);
continue;
}
bool flag=0;
while(r<=m && !flag)
{
f[a[++r]]++;
if(a[r]==temp) flag=1;
}
if(flag) get_ans(l,r);
}
int num=range.second-range.first+1;
int now=range.second;
for(int i=range.first;i<=range.second && num>k;i++)
{
if(b[a[i]])
{
b[a[i]]--;
continue;
}
del[i]=1;
num--;
m--;
now--;
}
for(int i=1;i<=range.first && ((now-1)%k+1<num);i++)
{
if(del[i]) continue;
del[i]=1;
now--;
m--;
}
if(num>k || (now-1)%k+1<num || ans==1e9 || m<n*k)
{
cout<<-1<<endl;
return 0;
}
num=0;
for(int i=1;i<=M;i++) num+=del[i];
cout<<num<<endl;
for(int i=1;i<=M;i++) if(del[i]) cout<<i<<" ";
cout<<endl;
return 0;
}
1133F2 Spanning Tree with One Fixed Degree
首先删除所有和 \(1\) 的连边,整个图会变为若干个联通块。\(1\) 到每一个联通块都应有连边。
其余步骤和最小生成树类似。
#include<bits/stdc++.h>
using namespace std;
const int Maxn=2e5+10;
struct Edge
{
int v,nxt;
bool flag;
Edge() :flag(0) {}
}edge[2*Maxn];
int head[Maxn],cnt;
int vis[Maxn];
int col[Maxn];
int f[Maxn];
int n,m,d;
int num;
vector<int>ans[Maxn];
int find(int x)
{
if(f[x]==x) return x;
else return f[x]=find(f[x]);
}
void merge(int x,int y)
{
f[find(x)]=find(y);
}
bool check(int x,int y)
{
return find(x)==find(y);
}
void add(int u,int v)
{
cnt++;
edge[cnt].v=v;
edge[cnt].nxt=head[u];
head[u]=cnt;
}
void dfs(int u,int n_col)
{
col[u]=n_col;
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(vis[v]) continue;
vis[v]=1;
dfs(v,n_col);
}
}
int main()
{
cin>>n>>m>>d;
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++)
{
int u,v;
cin>>u>>v;
add(u,v);
add(v,u);
}
int now=0;
vis[1]=1;
for(int i=head[1];i;i=edge[i].nxt) if(!vis[edge[i].v]) dfs(edge[i].v,++now);
for(int i=2;i<=n;i++) if(!col[i])
{
cout<<"NO"<<endl;
return 0;
}
memset(vis,0,sizeof(vis));
for(int i=head[1];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(!vis[col[v]])
{
ans[1].push_back(v);
d--;
vis[col[v]]=1;
merge(1,v);
num++;
}
}
if(num!=now)
{
cout<<"NO"<<endl;
return 0;
}
for(int i=head[1];i && d;i=edge[i].nxt)
{
int v=edge[i].v;
if(check(1,v)) continue;
merge(1,v);
ans[1].push_back(v);
d--;
}
if(d)
{
cout<<"NO"<<endl;
return 0;
}
for(int u=2;u<=n;u++)
{
for(int i=head[u];i;i=edge[i].nxt)
{
int v=edge[i].v;
if(check(u,v) || v==1) continue;
merge(u,v);
ans[u].push_back(v);
}
}
cout<<"YES"<<endl;
for(int i=1;i<=n;i++)
for(int j=0;j<ans[i].size();j++)
cout<<i<<" "<<ans[i][j]<<endl;
return 0;
}

浙公网安备 33010602011771号