Codeforces Round #672 (Div. 2)

其实是一场相对比较傻逼的场,但因为自己更傻逼只出了3题,还得继续努力呀

A.Cubes Sorting

思路:冒泡排序的最坏次数就是n*(n-1)/2, 只要当单增或者单减才能取到,所以就是刚好完全单减的时候是不行的

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=5e4+5;
int a[maxn];
int main(){ 
    int t;
    scanf("%d",&t);
    while(t--){ 
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)scanf("%d",&a[i]);
        int i;   
        for(i=2;i<=n;++i)
           if (a[i]>=a[i-1])
              break; 
        if (i<=n)
           puts("YES");
        else puts("NO");
    } 
    return 0;
}

B.Rock and Lever
思路:
容易发现只有当最高位相同的两个数才能产生贡献,所以就用桶存下来每一位cnti 处理这个偏序
答案就是
∑ C c n t i 2 \sum C_{cnti}^{2} Ccnti2
复杂度 O(32n)

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=1e5+5;
int a[maxn],cnt[34];
void ins(int x){
    int ans=0;
    while(x){
        ans++;
        x>>=1;
    }
    cnt[ans]++;
}
int main(){ 
    int t;
    scanf("%d",&t);
    while(t--){ 
        memset(cnt,0,sizeof(cnt));
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;++i)scanf("%d",&a[i]);
        for(int i=1;i<=n;++i)ins(a[i]);
        ll ans=0;
        for(int i=1;i<=32;++i){
            ans+=1ll*cnt[i]*(cnt[i]-1)/2;
        }
        cout<<ans<<endl;
    }
    return 0;
}

C.Pokémon Army
本场最傻逼的题来了,但我更傻逼
一眼知道是极值点产生贡献,但是开头结尾边界没考虑好,其实只要将a[0],a[n+1]都赋值为负无穷就好了

交换操作其实是个更傻逼的问题,当时已经想到了该贡献明显只和极值点以及相邻的2个数有关,但是没写,只要删除之前的贡献,再加上去就好了,其实每次操作只有6个点可能被修改,题目相当于需要我们撤销+重新贡献而已
时间复杂度大概是O(q log)

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=3e5+5;
int a[maxn],n,q;
bool v[maxn];
ll ans=0;
void del(int x){
    if(!v[x])return;
    v[x]=0;
    if(a[x]<a[x-1]&&a[x]<a[x+1])ans+=a[x];
    else if(a[x]>a[x-1]&&a[x]>a[x+1])ans-=a[x];
}   
void add(int x){
    if(v[x])return;
    v[x]=1;
    if(a[x]<a[x-1]&&a[x]<a[x+1])ans-=a[x];
    else if(a[x]>a[x-1]&&a[x]>a[x+1])ans+=a[x];
}
int main(){ 
    int t;
    scanf("%d",&t);
    while(t--){
        int n;
        ans=0;
        scanf("%d%d",&n,&q);
        for(int i=1;i<=n;++i)scanf("%d",&a[i]),v[i]=0;
        a[n+1]=a[0]=0;
        for(int i=1;i<=n;++i){
            if(a[i]>a[i-1]&&a[i]>a[i+1])ans+=a[i],v[i]=1;
            else if(a[i]<a[i-1]&&a[i]<a[i+1])ans-=a[i],v[i]=1;
        }
        cout<<ans<<endl;
        for(int i=1;i<=q;++i){
            int l,r;
            scanf("%d%d",&l,&r);
            if(l==r){cout<<ans<<endl;continue;}
            del(l-1);del(l);del(l+1);
            del(r-1);del(r);del(r+1);
            swap(a[l],a[r]);
            add(l-1);add(l);add(l+1);
            add(r-1);add(r);add(r+1);
            cout<<ans<<endl;
        }
    }
    return 0;
}

D.Rescue Nibel!
思路:组合数+优先队列
比赛的时候一直在想线段树怎么写,因为图论和数据结构被队友接手后基本没写过了,实际上这是一个DP的思想,计数不重不漏的关键还是在于找一个基准点,经常都是通过枚举来划分的,我有想过将当前区间作为第一个区间,这样必当不重,但是却很难维护,其实将当前区间作为最后一个区间即可,对于当前区间的左端点,前面所有区间在其右边的左端点都会产生贡献,用优先队列维护右端点即可

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
const int maxn=3e5+5;
const ll mod=998244353;
int l[maxn],r[maxn],mx[maxn<<1],L,R;
ll fac[maxn],facinv[maxn],ans=0;
vector<pair<int,int>>a;
priority_queue<int,vector<int>,greater<int>>q;
ll mypow(ll a,ll b){
    ll ans=1;
    while(b){
        if(b&1)ans=ans*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return ans;
}
void init(){
    fac[1]=fac[0]=1;
    for(int i=2;i<maxn;++i)fac[i]=fac[i-1]*i%mod;
    facinv[maxn-1]=mypow(fac[maxn-1],mod-2);
    for(int i=maxn-2;i>=0;--i)
        facinv[i]=facinv[i+1]*(i+1)%mod;
}
ll C(int n,int m){
    return fac[n]*facinv[n-m]%mod*facinv[m]%mod;
}
int main(){ 
    init();
    int n,k,cnt=0;
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;++i){
        scanf("%d%d",&L,&R);
        a.push_back({L,R});
    } 
    sort(a.begin(),a.end());
    for(int i=0;i<a.size();++i){
        while(!q.empty()&&q.top()<a[i].first)q.pop();
        if(q.size()>=k-1)
            ans=(ans+C(q.size(),k-1))%mod;
        q.push(a[i].second);
    }
    cout<<ans<<endl;
    return 0;
}

E.Battle Lemmings

题意:
给你一个01串,你可以进行k次操作,每次将一个1和相邻的0互换,问最大的对数,对数就是一个1左右两边0的对数之和

容斥思想+DP
思路:一道非常不错的DP,其实并没有那么难,状态我都已经猜到了,但是由于转化不够好,导致最后没法定义下来

我们可以发现,其实1划分了n个数,出现了很多段0区间,不管怎么操作,1的个数都是不变的,所以枚举1的位置是必须的,关键容斥转化来了
s u m = n ( n − 1 ) 2 − ( c n t − 1 ) c n t 2 − c n t ∗ ( n − c n t ) − ∑ C i 2 sum= \cfrac{n(n-1)}{2}-\cfrac{(cnt-1)cnt}{2}-cnt*(n-cnt)-\sum C_{i}^{2} sum=2n(n1)2(cnt1)cntcnt(ncnt)Ci2
i表示每段连续0的个数,最后一项越小总和越大
所以dp[i][j][k]表示枚举到第i个1,放在第j个位置,做了k次操作的最后一项的最小值

d p [ i ] [ j ] [ k + ∣ p o s i − j ∣ ] = 0 < = t < j m i n ( d p [ i ] [ j ] [ k + ∣ p o s i − j ∣ ] , d p [ i − 1 ] [ t ] [ k ] + C j − t + 1 2 dp[i][j][k+|pos_i-j|]=_{0<=t<j}min(dp[i][j][k+|pos_i-j|],dp[i-1][t][k]+C_{j-t+1}^{2} dp[i][j][k+posij]=0<=t<jmin(dp[i][j][k+posij],dp[i1][t][k]+Cjt+12

虽然看起来是O(n^5)的,似乎是8e8左右,题解说能跑到O(n ^5/27),所以冲一波

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
int a[85],pos[85],cnt;
int dp[82][82][82*82];
int main(){ 
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i){ 
        scanf("%d",&a[i]);
        if(a[i])pos[++cnt]=i;
    }
    int ans=0;
    int N=n*(n-1)/2;
    ans=n*(n-1)/2-cnt*(cnt-1)/2-cnt*(n-cnt);
    for(int i=0;i<=cnt;++i)
        for(int j=0;j<=n;++j)
            for(int k=0;k<=N;++k)dp[i][j][k]=0x3f3f3f3f;
    int sum=0;
    for(int i=0;i<=cnt;++i){
        if(i>=1) 
             sum+=max(0,(pos[i]-pos[i-1]-1)*(pos[i]-pos[i-1]-2)/2);
        dp[i][pos[i]][0]=sum;
    }
    for(int i=1;i<=cnt;++i)
        for(int j=1;j<=n;++j)
            for(int t=0;t<j;++t)
                for(int k=0;k<=N;++k){ 
                    if(dp[i-1][t][k]==0x3f3f3f3f)continue;
                    dp[i][j][k+abs(pos[i]-j)]=min(dp[i][j][k+abs(pos[i]-j)],dp[i-1][t][k]+max(0,(j-t-1)*(j-t-2)/2));
                }
    int mx=0x3f3f3f3f;
    for(int i=0;i<=N;++i){ 
        for(int j=1;j<=n;++j){ 
            mx=min(mx,dp[cnt][j][i]+max(0,(n-j-1)*(n-j)/2));
        }
        if(!cnt){printf("0 ");continue;}
        printf("%d ",ans-mx); 
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值