基础训练(3)

基础训练(3)

1.Problem - 1692G - Codeforces

写了一道水题。对柿子化简之后就是后面的两倍大于前面即可。如果后面某个数不满足条件了。再从那个点开始找起即可。因为前面肯定是都无法和这个数形成一个满足条件的序列。

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.Problem - C - Codeforces

一开始很明显可以想到贪心就是先把大的子树给保存下来。但是发现如果两个子树一样大就并不对了。这时候就要比较儿子了。比较复杂不好解决。

考虑切掉一边,另外一边也就相当于根被感染的情况。所以就是一个子问题。所以考虑用dp。那我应该dp记录什么呢?很明显dp[i]记录i被感染儿子可以活下来多少。最后解就变成了

$$
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.Problem - 1677A - Codeforces

开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.Problem - 1671D - Codeforces

这题其实就一个性质也确实发现但是为什么没写出来呢?要尽量减少取看题解的次数。

很明显可以发现[ 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.Problem - 1670C - Codeforces

初看就是发现唯一需要确定的点就是如何确定我选了这个点后面就可能没有点选了。后来看到一个dsu的标志发现这可以用并查集维护。就是两个二选一的点放到一个集合里面。发现这个集合里面只有一个点可以两个都选。于是最后统计集合个数即可。先排除掉必须选一个点的点。

和正接差不多。

image-20221006203024118

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)
if(find(a[i]) == a[i] ) ans=(ans*2)%mod;
}
cout<<ans<<endl;
}

6.Problem - D - Codeforces

发现看错题了,sb。

发现image-20221007152637445

a只能是奇数,并且只要这个也在范围内即可。

void slove(){
cin>>n;
int ans=0;
for(int i=3;i<=1e9;i+=2){
int t=i*i;
if(t/2+1>n) break;
ans++;
}
cout<<ans<<endl;
}

7.Problem - C1 - Codeforces

这两个题算是稍微感受了一下二分板子,如果else那个-1了就需要对

l + r + 1处理。这也是那个区间划分的依据吧。

还有就是r-l>1的板子时不太一样的。

简单版本可以每次二分的时候询问两次。ask返回的还是次大值则最大值在这个区间。注意r-l>1的二分版本时不同的。

int ask(int l,int r){
cout<<"? "<<l<<" "<<r<<endl;
int ans;
cin>>ans;
return ans;
}
void slove(){
cin>>n;
int l=1,r=n;
while(r-l>1){
int mid=l+r+1>>1;
int maxx=ask(l,r);
if(maxx<mid){
if(ask(l,mid)==maxx){
r=mid;
}
else{
l=mid;
}
}
else{
if(ask(mid,r)==maxx){
l=mid;
}
else{
r=mid;
}
}
}
cout<<"? "<<l<<" "<<r<<endl;
int mid;
cin>>mid;
cout<<"! "<<(mid==l?r:l)<<endl;
}

8.Problem - C2 - Codeforces

首先我们可以确定的是只有一个数是大于整个数组的次大值。那我可以先确定次大值得位置,再确定最大值所在区间就可以避免每次二分查找需要两次ask了。那我考虑现在知道了最大值所在区间和次大值得位置。那我只需二分这个区间就可以知道最大值在哪里了。

int ask(int l,int r){
cout<<"? "<<l<<" "<<r<<endl;
fflush(stdout);
int ans;
cin>>ans;
return ans;
}
void slove(){
cin>>n;
int p=ask(1,n);
int l=1,r=n,t;
if(p==1){
l=2;
}
else{
t=ask(1,p);
if(t==p){
r=p-1;
}
else{
l=p+1;
}
}
if(l==p+1){
while(l<r){
int mid=l+r>>1;
t=ask(p,mid);
if(t==p){
r=mid;
}
else{
l=mid+1;//这个就不需要
}
}
}
else{
while(l<r) {
int mid=(l+r+1)>>1;
t=ask(mid,p);
if(t==p){
l=mid;
}
else{
r=mid-1;//下面是-1时需要l+r+1>>1
}
}
}
cout<<"! "<<l<<endl;
fflush(stdout);
}

9.Problem - C - Codeforces

初次诞生的想法肯定是尽量使一个字母靠前一个字母靠后这样才可以使最大.所以考虑两次遍历一次从前往后一次从后往前分别求每个字母可以靠前可以最靠后的位置。

int main(){
cin>>n>>m;
cin>>s;
cin>>t;
int step=0;
for(int i=0;i<n&&step<m;i++){
if(s[i]==t[step]) f[step++]=i;
}
step=m-1;
for(int i=n-1;i>=0&&step>=0;i--){
if(s[i]==t[step]) se[step--]=i;
}
int ans=0;
for(int i=0;i<m-1;i++){
ans=max(ans,se[i+1]-f[i]);
}
cout<<ans<<endl;
}

10.Problem - D - Codeforces

发现类似与这种1()0 - 0()1会产生的结果是产生0(全是1)1这种情况下最多可以产a+b-2个一,但是前提是没有前导0。这种情况也是产生1最多的。去掉不行的情况,其他情况就是找到一个1和0的坐标相差k。输出交换之前和交换之后的字符串即可。

void slove(){
int a,b,k;
cin>>a>>b>>k;
if(b==1&&k||a==0&&k){
cout<<"NO"<<endl;
return;
}
if(k>a+b-2&&k!=0){
cout<<"NO"<<endl;
return;
}
string s;
for(int i=0;i<b;i++){
s+='1';
}
for(int j=0;j<a;j++){
s+='0';
}
cout<<"YES"<<endl;
cout<<s<<endl;
for(int i=1;i<a+b;i++){
if(s[i]=='1'&&s[i+k]=='0'){
swap(s[i],s[i+k]);
break;
}
}
cout<<s<<endl;
}
 
posted @ 2022-10-10 00:06  silky__player  阅读(164)  评论(0)    收藏  举报