Codeforces Round #672 (Div. 2)

A - Cubes Sorting

冒泡排序交换次数等于逆序对数,严格降序需要交换 n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n1)次才能升序排列,由此只需要判断原数组是否严格降序即可。

#define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0)
#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
using namespace std;
const int N=500010;
int a[N];
int n;
int main()
{
    IO;
    int T=1;
    cin>>T;
    while(T--)
    {
        bool ok=1;
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            if(i>1&&a[i]>=a[i-1]) ok=0;
        }
        if(ok) cout<<"NO\n";
        else cout<<"YES\n";
        
    }
    return 0;
}

B - Rock and Lever

按位考虑,随便搞搞

#define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0)
#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N=500010;
int a[N];
int cnt[40];
int n;
int main()
{
    IO;
    int T=1;
    cin>>T;
    while(T--)
    {
        bool ok=1;
        cin>>n;
        memset(cnt,0,sizeof cnt);
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            bool ok=1;
            for(int j=31;j>=0;j--)
                if(ok&&(a[i]>>j&1))
                {
                    cnt[j]++;
                    ok=0;
                    break;
                }
        }
        ll res=0;
        for(int i=31;i>=0;i--)
            if(cnt[i])
            res+=1ll*cnt[i]*(cnt[i]-1)/2;
        cout<<res<<'\n';
    }
    return 0;
}

C1 - Pokémon Army (easy version)

不带修改,直接dp扫一遍。

状态表示: f ( i , 0 ) f_{(i,0)} f(i,0)考虑前 i i i个数,并且选择第 i i i个数状态是+ f ( i , 1 ) f_{(i,1)} f(i,1)考虑前 i i i个数,并且选择第 i i i个数状态是-
状态转移: f ( i , 0 ) = m a x ( f ( i , 0 ) , f ( k , 1 ) + a i ) f_{(i,0)}=max(f_{(i,0)},f_{(k,1)}+a_i) f(i,0)=max(f(i,0),f(k,1)+ai) f ( i , 1 ) = m a x ( f ( i , 1 ) , f ( k , 0 ) − a i ) f_{(i,1)}=max(f_{(i,1)},f_{(k,0)}-a_i) f(i,1)=max(f(i,1),f(k,0)ai),( 1 < k < i 1<k<i 1<k<i)只需要在维护2个前缀最大值即可 O ( n ) O(n) O(n)得到答案。

#define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0)
#pragma GCC optimize(2)
#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N=300010;
const int INF=1e17;
ll a[N],f[N][2];
int n,q;
int main()
{
    IO;
    int T=1;
    cin>>T;
    while(T--)
    {
        cin>>n>>q;
        for(int i=1;i<=n;i++) cin>>a[i];
        for(int i=1;i<=n;i++) f[i][0]=f[i][1]=-INF;
        f[0][0]=-INF;
        ll mmax0=-INF,mmax1=0;
        ll res=-INF;
        for(int i=1;i<=n;i++)
        {
            f[i][0]=mmax1+a[i];
            f[i][1]=mmax0-a[i];
            mmax0=max(mmax0,f[i][0]);
            mmax1=max(mmax1,f[i][1]);
            res=max(res,max(f[i][0],f[i][1]));
        }
        cout<<res<<'\n';
    }
    return 0;
}

Pokémon Army (hard version)

用线段树维护 f f f数组即可,对于线段树每一个区间维护数组 f [ 2 ] [ 2 ] f[2][2] f[2][2]
f [ 0 ] [ 0 ] f[0][0] f[0][0]表示序列开头+,结尾+
f [ 0 ] [ 1 ] f[0][1] f[0][1]表示序列开头+,结尾-
f [ 1 ] [ 0 ] f[1][0] f[1][0]表示序列开头-,结尾+
f [ 1 ] [ 1 ] f[1][1] f[1][1]表示序列开头-,结尾-

#define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0)
#pragma GCC optimize(2)
#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<string>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N=300010;
const ll INF=1e17;
ll a[N];
int n,q;
// f[0][0] ++
// f[0][1] +-
// f[1][0] -+
// f[1][1] --
struct node
{
    int l,r;
    ll val;
    ll f[2][2];
}tree[N*4];
void pushup(int u)
{
    tree[u].val=-INF;
    for(int i=0;i<2;i++)
        for(int j=0;j<2;j++)
        {
            tree[u].f[i][j]=max(tree[u<<1].f[i][j],tree[u<<1|1].f[i][j]);
            tree[u].f[i][j]=max(tree[u].f[i][j],max(tree[u<<1].f[i][0]+tree[u<<1|1].f[1][j],tree[u<<1].f[i][1]+tree[u<<1|1].f[0][j]));
            tree[u].val=max(tree[u].val,tree[u].f[i][j]);
        }
}
void build(int u,int l,int r)
{
    tree[u]={l,r};
    if(l==r)
    {
        tree[u].val=a[l];
        tree[u].f[0][0]=a[l];
        tree[u].f[1][1]=-a[l];
        tree[u].f[0][1]=tree[u].f[1][0]=-INF;
        return;
    }
    int mid=l+r>>1;
    build(u<<1,l,mid);
    build(u<<1|1,mid+1,r);
    pushup(u);
}
void modify(int u,int p,int x)
{
    if(tree[u].l==tree[u].r)
    {
        tree[u].val=x;
        tree[u].f[0][0]=x;
        tree[u].f[1][1]=-x;
        tree[u].f[0][1]=tree[u].f[1][0]=-INF;
        return;
    }
    int mid=tree[u].l+tree[u].r>>1;
    if(p<=mid) modify(u<<1,p,x);
    else modify(u<<1|1,p,x);
    pushup(u);
}
int main()
{
    IO;
    int T=1;
    cin>>T;
    while(T--)
    {
        cin>>n>>q;
        for(int i=1;i<=n;i++) cin>>a[i];
        build(1,1,n);
        cout<<tree[1].val<<'\n';
        while(q--)
        {
            int l,r;
            cin>>l>>r;
            modify(1,l,a[r]);
            modify(1,r,a[l]);
            cout<<tree[1].val<<'\n';
            swap(a[l],a[r]);
        }
    }
    return 0;
}

D - Rescue Nibel!

先上课去了,晚上回来写。第一次数组开小了还re了

先把每个区间的左端点和右端点拆开记录下来,然后排个序(注意:先按照位置排序,按照左端点优先原则排序)借用扫描线的思想扫描整个序列。
扫描的过程中,如果到一个左端点说明目前有一盏灯在此时刻点亮那么对答案的贡献就是在需要从前面点亮的灯中选择 k − 1 k-1 k1盏灯,维护前面点亮灯的数量只需要维护一个cnt即可,那么对答案的贡献即 C c n t k − 1 C_{cnt}^{k-1} Ccntk1,然后更新cnt即可,如果扫描到一个右端点不难发现对答案没有贡献,只需要维护cnt即可。
计算 C c n t k − 1 C_{cnt}^{k-1} Ccntk1的过程中由于既要取模又有除法需要提前预处理阶乘和逆元。

#define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0)
#pragma GCC optimize(2)
#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<string>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N=300010;
const ll mod=998244353;
pii a[2*N];
int n,k;
ll qmi(ll a,ll b,ll p)
{
    ll res=1;
    while(b)
    {
        if(b&1) res=res*a%p;
        b>>=1;
        a=a*a%p;
    }
    return res;
}

ll fact[N],infact[N];
void init(int n)
{
    fact[0]=infact[0]=1;
    for(int i=1;i<=n;i++)
    {
        fact[i]=fact[i-1]*i%mod;
        infact[i]=qmi(fact[i],mod-2,mod);
    }
}
int main()
{
    IO;
    int T=1;
    //cin>>T;
    while(T--)
    {
        cin>>n>>k;
        init(n);
        for(int i=1;i<=n;i++)
        {
            int l,r;
            cin>>l>>r;
            a[i].first=l,a[i].second=-1;//左端点
            a[i+n].first=r,a[i+n].second=1;//右端点
        }
        sort(a+1,a+1+2*n);
        ll res=0;
        ll cnt=0;
        for(int i=1;i<=2*n;i++)
        {
            int p=a[i].first,id=a[i].second;
            if(id==-1)
            {
                if(cnt>=k-1)
                    res=(res+fact[cnt]*infact[k-1]%mod*infact[cnt-k+1]%mod)%mod;
                cnt++;
            }
            else cnt--;
        }
        cout<<res<<'\n';
    }
    return 0;
}

E - Battle Lemmings

我还是不会难的dp
whd大佬视频题解

为什么能用dp?
首先有一点非常重要:无论如何移动1,所有1的相对位置并不会改变。因此考虑答案的集合肯定是将这些1不改变相对位置的情况下排到某些位置,而代价即移动的步数可以通过原先位置和当前位置算出来,那么如果我们逐一考虑每一个1最终的位置不难发现这个问题是无后效性的。

如何算出答案?
最终要求算一个很奇怪的东西,就是两个0之间只要存在1的对数,发现非常难以计算,我们转化问题考虑反面:哪些0不会对答案有贡献?不难发现只要连续的0两两配对都不会对答案有贡献,我们只需要用总数目减去这些不满足的即可。

状态表示: f ( i , j , k ) f_{(i,j,k)} f(i,j,k)考虑前 i i i个位置,目前考虑了前 j j j 1 1 1的位置,代价即交换次数是 k k k的集合。
状态转移:考虑第 j + 1 j+1 j+1 1 1 1的位置,枚举 i + 1 → n i+1\to n i+1n,进行答案更新,这里是向后更新未知状态。

时间复杂度: O ( n 5 ) O(n^5) O(n5),但是常数非常小,再加上cf强大的评测鸡,跑的并不慢155ms

#define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0)
#pragma GCC optimize(2)
#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<string>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef pair<int,int> pii;
typedef long long ll;
const int N=83,INF=0x3f3f3f3f;
int f[N][N][N*(N-1)/2];
int n,a[N],pos[N],cnt1;
int main()
{
    IO;
    int T=1;
    //cin>>T;
    while(T--)
    {
        cin>>n;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            if(a[i]) pos[++cnt1]=i;
        }
        int m=n*(n-1)/2;
        memset(f,0x3f,sizeof f);
        f[0][0][0]=0;
        for(int i=0;i<=n;i++)
            for(int j=0;j<cnt1;j++)
                for(int k=0;k<=m;k++)
                {
                    if(f[i][j][k]>=INF) continue;
                    for(int p=i+1;p<=n;p++)
                    {
                        int now=abs(p-pos[j+1]);
                        if(now+k>m) continue;
                        int cost=max(p-i-1,0)*max(p-i-2,0)/2;
                        if(j+1==cnt1) cost+=(n-p)*(n-p-1)/2;
                        f[p][j+1][k+now]=min(f[p][j+1][k+now],f[i][j][k]+cost);
                    }
                }
        int cnt0=n-cnt1;
        for(int i=0;i<=m;i++)
        {
            int res=INF;
            for(int j=1;j<=n;j++)
            {
                if(i) f[j][cnt1][i]=min(f[j][cnt1][i],f[j][cnt1][i-1]);
                res=min(res,f[j][cnt1][i]);
            }
            if(!cnt1) cout<<0<<' ';
            else cout<<cnt0*(cnt0-1)/2-res<<' ';
            
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值