基础训练(3)
1.
写了一道水题。对柿子化简之后就是后面的两倍大于前面即可。如果后面某个数不满足条件了。再从那个点开始找起即可。因为前面肯定是都无法和这个数形成一个满足条件的序列。
void slove(){
cin>>n>>k;
fel(i,1,n) cin>>a[i];
int ans=0;
fel(i,1,n){
int j=i;
while(2*a[j+1]>a[j]&&j<n) j++;
//cout<<j<<endl;
if(j-i+1>k){
ans+=j-i+1-k;
}
i=j;
}
cout<<ans<<endl;
}
2.
一开始很明显可以想到贪心就是先把大的子树给保存下来。但是发现如果两个子树一样大就并不对了。这时候就要比较儿子了。比较复杂不好解决。
考虑切掉一边,另外一边也就相当于根被感染的情况。所以就是一个子问题。所以考虑用dp。那我应该dp记录什么呢?很明显dp[i]记录i被感染儿子可以活下来多少。最后解就变成了
$$
dp[i]=max(dp[lson]+size[rson]−1,dp[rson]+size[lson]−1)
$$
dp[i]=max(dp[lson]+size[rson]−1,dp[rson]+size[lson]−1)
$$
/*
感觉就是一个简单的dfs
发现还要求如果这个点被感染之后他的子树能剩下多少结点
也就变成了一个dp
树形dp的一般套路就是递归先解决小问题然后回来
*/
const int N=3e5+100;
vector<int>b[N];
int si[N],dp[N],n;
void dfs(int x,int fa){
si[x]=1;
dp[x]=0;//有可能是叶子结点
int sum=0;
for(auto i:b[x]){
if(i==fa) continue;
dfs(i,x);
sum+=dp[i];
si[x]+=si[i];
}
for(auto i:b[x]){
if(i==fa) continue;
dp[x]=max(dp[x],sum-dp[i]+si[i]-1);
}
}
void slove(){
cin>>n;
fel(i,1,n) b[i].clear();
fel(i,1,n-1){
int x,y;
cin>>x>>y;
b[x].pb(y);
b[y].pb(x);
}
dfs(1,0);
cout<<dp[1]<<endl;
}
3.
开longlong爆空间我靠。
可以考虑两重for循环,枚举b,c的位置。那么就只需要算出来b之前比c小的数。再算出c之后比b小的数。这个预处理就好了
这个预处理可以学学。
void slove(){
cin>>n;
fel(i,1,n) cin>>a[i];
fel(i,1,n) vis[i]=0;
for(int i=1;i<=n;i++){
int sum=0;
for(int j=1;j<=n;j++){
sum1[i][j]=sum;
sum+=vis[j];
}
vis[a[i]]=1;
}
fel(i,1,n) vis[i]=0;
for(int i=n;i>=1;i--){
int sum=0;
for(int j=1;j<=n;j++){
sum2[i][j]=sum;
sum+=vis[j];
}
vis[a[i]]=1;
}
ll ans=0;
for(int i=2;i<=n;i++){
for(int j=i+1;j<n;j++){
ans+=sum1[i][a[j]]*sum2[j][a[i]];
}
}
cout<<ans<<endl;
}
4.
这题其实就一个性质也确实发现但是为什么没写出来呢?要尽量减少取看题解的次数。
很明显可以发现[ a[i], a[r] ]区间内如果选择i 是在[ a[i], a[r] ]区间内的,那么肯定是不会产生额外贡献的。进一步发现只要是数组最大值最小值之间的数都是不会产生贡献的。所以就只需要考虑[1 - minn-1 ] 和 [maxx + 1, x ] 。很明显1,x都会填进去,所以只需要考虑1,x的贡献即可。这个时候就只需要考虑1,x的贡献即可,这就可以遍历了。
void slove(){
cin>>n>>x;
int minn=inf,maxx=0;
fel(i,1,n) {
cin>>a[i];
minn=min(a[i], minn);
maxx=max(a[i], maxx);
}
int ans=0;
fel(i,1,n-1) ans+=abs(a[i]-a[i+1]);
if(minn>1){
int res=min(a[1]-1,a[n]-1);
for(int i=1;i<n;i++){
res=min( res, a[i]+a[i+1]-2-abs(a[i]-a[i+1]) );
}
ans+=res;
}
if(maxx<x){
int res=min( x-a[1], x-a[n] );
for(int i=1;i<n;i++){
res=min( res, x*2-a[i]-a[i+1]-abs(a[i]-a[i+1]) );
}
ans+=res;
}
cout<<ans<<endl;
}
5.
初看就是发现唯一需要确定的点就是如何确定我选了这个点后面就可能没有点选了。后来看到一个dsu的标志发现这可以用并查集维护。就是两个二选一的点放到一个集合里面。发现这个集合里面只有一个点可以两个都选。于是最后统计集合个数即可。先排除掉必须选一个点的点。
和正接差不多。
int find(int x){
if(x!=fa[x]) fa[x]=find(fa[x]);
return fa[x];
}
void slove(){
cin>>n;
fel(i,1,n) fa[i]=i,vis[i]=0;
fel(i,1,n) cin>>a[i];
fel(i,1,n) cin>>b[i];
fel(i,1,n){
int x;
cin>>x;
if(x!=0){
vis[x]=1;
continue;
}
if(vis[a[i]]==1){
vis[b[i]]=1;
}
else if(vis[b[i]]==1){
vis[a[i]]=1;
}
else if(a[i]==b[i]){
vis[a[i]]=1;
}
}
//fel(i,1,n) cout<<vis[i]<<endl;
int ans=1;
fel(i,1,n){
if(vis[a[i]]==0&&vis[b[i]]==0){
fa[find(a[i])]=find(b[i]);
}
}
fel(i,1,n){
if(vis[a[i]]==0&&vis[b[i]]==0)
