Educational Codeforces Round 97 (Rated for Div. 2)

A:Marketing Scheme

  满足 r ≥ 2 ∗ l r\ge2*l r2l无解。

B:Reverse Binary Strings

  这题应该注重结果而不是交换的过程。我们可以发现,对于一个串来说,一定发生了下面这种情况:

1 1xxxxxx 0 0
  这时候我们可以发现,交换中间这个字符串的时候,仅仅改变了 中间这个字符串,造成的影响 仅仅针对于左右两侧s[i]≠s[i+1],而对这个中间字符相邻的关系没有任何改变!因此,我们可以统计11串和00串的个数,最终的答案是max(num0,num1)。(PS:取二者之中的较大者的原因是因为两个不一定相等,例如串110可能会造成11的数量比00的数量多一个)。

C:Chef Monocarp

  先考虑贪心,肯定尽量会往它本身的那个时刻放,剩下的往本身时刻的两边放所造成的贡献更小。但是这样会出现一个矛盾,比如时刻7有5个,时刻9有5个,这样就会造成中间位置的竞争。
  然后我们考虑到这样的竞争结果是不好预测的,无论是让给左边还是让给右边都会取决于后面其他位置的影响。前面的选择的结果会影响后面,这便可以考虑DP的解题策略了。
  我们用 f [ i ] [ j ] f[i][j] f[i][j]表示在时刻 j j j放置前 i i i个物品所需要的消耗,很容易得出转移方程是 f [ i ] [ j ] = m a x ( f [ i ] [ j − 1 ] , f [ i − 1 ] [ j − 1 ] + a b s ( j − a [ i ] ) ) f[i][j]=max(f[i][j-1],f[i-1][j-1]+abs(j-a[i])) f[i][j]=max(f[i][j1],f[i1][j1]+abs(ja[i])).即 j j j时刻放物品 i i i,或者 j j j时刻不放物品 i i i.注意,在这个表达式下,物品 i i i一定放在物品 i + 1 i+1 i+1前面,因此,我们一定要通过排序,保证 t i < t i + 1 t_i<t_{i+1} ti<ti+1!
  最后就是DP初始化的问题:一开始的合法状态必然是 f [ 0... n ] [ 0 ] = 0 f[0...n][0]=0 f[0...n][0]=0.

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
int n,m,e,ans=0;
int a[210],f[410][410];
const int INF=0x3f3f3f3f;
int main()
{
    close;
    int T;cin>>T;
    while(T--)
    {
        int n;cin>>n;
        for(int i=1;i<=n;++i) cin>>a[i];
        memset(f,INF,sizeof(f));
        for(int j=0;j<=400;++j) f[0][j]=0;
        sort(a+1,a+n+1);
        for(int i=1;i<=n;++i)
            for(int j=1;j<=400;++j)
                f[i][j]=min(f[i][j-1],f[i-1][j-1]+abs(j-a[i]));
        int maxnum=INT_MAX;
        for(int j=n;j<=400;++j) maxnum=min(maxnum,f[n][j]);
        cout<<maxnum<<endl;
    }
}

D:Minimal Height Tree

  题目的限制条件是一个根结点的儿子的结点编号是呈递增的,因此我们只要贪心地将数组 a a a当中尽可能长的递增序列安排到一个结点下方做他的子结点即可。

E:Make It Increasing

【前置准备:HDU5256 序列变换】
  题目大意是,给定一个数列A,让你修改数量最少的元素,使得这个数列严格递增。我们可以这么考虑,如果有 ∀ i , A [ i + 1 ] > A [ i ] \forall{i},A[i+1]>A[i] i,A[i+1]>A[i],那么则有 A [ i + 1 ] − 1 ≥ A [ i ] A[i+1]-1\ge A[i] A[i+1]1A[i],进而有 A [ i + 1 ] − ( i + 1 ) ≥ A [ i ] − i A[i+1]-(i+1)\ge A[i]-i A[i+1](i+1)A[i]i.我们只要得到修改后序列的最长非下降子序列的长度 l e n len len,那么只要修改 n − l e n n-len nlen个数,原序列就是严格上升的。
  注意这里替换的时候要使用upper_bound()函数,替换掉第一个比他大的数字!使用lower_bound()函数是错误的,例如序列A=[1,3,4,6,4,4,5],使用lower_bound()得到的最终结果是1,3,4,5;而使用upper_bound()得到的最终结果是1,3,4,4,4,5.

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=5e5+100;
int a[maxn],b[maxn];
int solve(int n)
{
    int len=1;
    b[0]=a[0];
    for(int i=1;i<n;++i)
    {
        if(a[i]>=b[len-1]) b[len++]=a[i];
        else{
            int pos=upper_bound(b,b+len,a[i])-b;
            b[pos]=a[i];
        }
    }
    return len;
}
int main()
{
    close;
    int T;cin>>T;
    for(int casenum=1;casenum<=T;++casenum)
    {
        int n;cin>>n;
        for(int i=0;i<n;++i) cin>>a[i],a[i]-=i;
        printf("Case #%d:\n%d\n",casenum,n-solve(n));
    }
}

  CF上这道题唯一的区别就是多了几个固定位的限制。可以把问题分情况讨论一下:
①当 k = 0 k=0 k=0时,问题退化成上面的形式,直接变换后求最长非下降子序列;
②当 k > 0 k>0 k>0时,先检查是否满足 ( a [ b [ i + 1 ] ] − a [ b [ i ] ] − 1 ) ≥ ( b [ i + 1 ] − b [ i ] − 1 ) (a[b[i+1]]-a[b[i]]-1)\ge(b[i+1]-b[i]-1) (a[b[i+1]]a[b[i]]1)(b[i+1]b[i]1),不满足直接输出-1;
③否则,我们将上述序列按照b划分成一个个区间,分别在区间上求最长非下降子序列。可以设置数组a和数组b的哨兵结点,然后在区间 [ l , r ] [l,r] [l,r]上求解最长非下降子序列即可。注意:每个区间的第一个数值不能被修改!得到一个最长非下降子序列后,我们找到右端点在序列中第一个比他大的元素的位置pos,可以得知在最长非下降子序列 f [ 1... p o s − 1 ] f[1...pos-1] f[1...pos1]的元素可以不用被修改, f [ p o s . . . l e n ] f[pos...len] f[pos...len]需要被修改,所以该区间需要修改 ( r − l + 1 ) − p o s (r-l+1)-pos (rl+1)pos个。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int maxn=5e5+100;
int a[maxn],b[maxn],f[maxn];
bool check(int k)
{
    for(int i=2;i<=k;++i)
        if((a[b[i]]-a[b[i-1]])<=(b[i]-b[i-1]-1)) return false;
    return  true;
}
int solve(int l,int r)
{
    int len=1;
    f[1]=a[l];
    for(int i=l+1;i<=r;++i)
    {
        if(a[i]>=f[len]) f[++len]=a[i];
        else{
            int pos=upper_bound(f+1,f+len+1,a[i])-f;
            if(pos!=1) f[pos]=a[i];
        }
    }
    int pos=upper_bound(f+1,f+len+1,a[r])-(f+1);
    return (r-l+1)-pos;
}
int main()
{
    close;
    int n,k;cin>>n>>k;
    for(int i=1;i<=n;++i) cin>>a[i];
    for(int i=1;i<=k;++i) cin>>b[i];
    if(k==0)
    {
        for(int i=1;i<=n;++i) a[i]-=i;
        int len=1;
        f[1]=a[1];
        for(int i=2;i<=n;++i)
        {
            if(a[i]>=f[len]) f[++len]=a[i];
            else{
                int pos=upper_bound(f+1,f+len+1,a[i])-f;
                f[pos]=a[i];
            }
        }
        cout<<n-len<<endl;
        return 0;
    }
    if(!check(k)) {cout<<-1<<endl;return 0;}
    for(int i=1;i<=n;++i) a[i]-=i;
    a[0]=INT_MIN;a[n+1]=INT_MAX;//设立哨兵结点
    b[0]=0;b[k+1]=n+1;
    int ans=0;
    for(int i=1;i<=k+1;++i)
        ans+=solve(b[i-1],b[i]);
    cout<<ans<<endl;
}

F:Emotional Fishermen

  题目大意:给定一个有 n n n个正整数的序列 a a a,目标是能够使排列后的数组满足: a p i m a x j = 1 i − 1 a p j ∉ ( 1 2 , 2 ) \frac{a_{p_i}}{max_{j=1}^{i-1}a_{p_j}} \notin (\frac {1}{2},2 ) maxj=1i1apjapi/(21,2)问有多少种排列方案?
  先将序列 a a a按照从小到大的顺序进行排列,我们用 d p [ i ] dp[i] dp[i]表示最大值为 a i a_i ai时的方案个数,用 p o s [ i ] pos[i] pos[i]表示从 [ 1... i ] [1...i] [1...i]中满足 2 ∗ a j ≤ a i 2*a_j\le a_i 2ajai中最大的 j j j.
  先考虑如何求解 p o s [ i ] pos[i] pos[i]的问题。这里我们可以采用单调栈的方法(通过模拟实现)。我们令 c u r cur cur指针指向的是对于当前的 a i a_i ai来说满足条件的 a j a_j aj,当 a i a_i ai增大的时候, c u r cur cur同样右移。
  接着我们考虑如何构造的问题。现在我们拿到了一个最大值为 a j a_j aj的序列,然后我们试图朝这个序列添加一个最大值为 a i a_i ai,且满足 a i ≥ a j a_i\ge a_j aiaj.第一步,我们先将 a i a_i ai放在当前序列从左向右看第一个空位上!这是我们构造的基础,也是我们后面正确性证明的必要条件。这时候我们就发现,这时候有 p o s [ i ] − p o s [ j ] − 1 pos[i]-pos[j]-1 pos[i]pos[j]1个数,可以放在 n − p o s [ j ] − 2 n-pos[j]-2 npos[j]2个空位上。(原来的序列有 p o s [ j ] + 1 pos[j]+1 pos[j]+1个, a i a_i ai在第一个空位上,这一位也不可选;这时候会有 p o s [ i ] − p o s [ j ] − 1 pos[i]-pos[j]-1 pos[i]pos[j]1可以随意放在 a i a_i ai后的任意空位上,一定满足条件。)因此,我们可以得出,递推方程是: d p [ i ] = ∑ j = 0 i − 1 d p [ j ] ∗ A n − p o s [ j ] − 2 p o s [ i ] − p o s [ j ] − 1 dp[i]=\sum_{j=0}^{i-1} dp[j]*A_{n-pos[j]-2}^{pos[i]-pos[j]-1} dp[i]=j=0i1dp[j]Anpos[j]2pos[i]pos[j]1很明显我们最终的答案是 d p [ n ] dp[n] dp[n],当且仅当 p o s [ n ] = n − 1 pos[n]=n-1 pos[n]=n1.
  现在考虑一下构造的合理性,这样的构造方式会不会造成重复计数的问题。例如我们令 a k < a j < a i a_k<a_j<a_i ak<aj<ai,计算 d p [ j ] dp[j] dp[j]使用了 d p [ k ] dp[k] dp[k],计算 d p [ i ] dp[i] dp[i]同时使用了 d p [ j ] , d p [ k ] dp[j],dp[k] dp[j],dp[k].但是根据我们的构造方式,能够保证不同的转移,最大值的出现次序一定是不同的,因为我们的最大值一定放在当前序列从左到右的第一个空位上,因此肯定不同。
  最后一定要令 p o s [ i ] = − 1 pos[i]=-1 pos[i]=1,这样能够使得 n − p o s [ 0 ] − 2 = n − 1 , p o s [ i ] − p o s [ j ] − 1 = p o s [ i ] n-pos[0]-2=n-1,pos[i]-pos[j]-1=pos[i] npos[0]2=n1,pos[i]pos[j]1=pos[i],满足实际情况。

#include<bits/stdc++.h>
#define close ios::sync_with_stdio(false)
using namespace std;
const int mod=998244353;
const int maxn=5e3+100;
typedef long long ll;
ll qpow(ll base,ll x)
{
    ll ans=1;
    while(x) {if(x&1) ans=ans*base%mod;base=base*base%mod;x>>=1;}
    return ans;
}
ll f[maxn],inv[maxn],a[maxn],pos[maxn],dp[maxn];
void Init(int n)
{
    f[0]=1;inv[0]=1;
    for(int i=1;i<=n;++i) f[i]=f[i-1]*i%mod;
    inv[n]=qpow(f[n],mod-2);
    for(int i=n-1;i>0;--i) inv[i]=inv[i+1]*(i+1)%mod;
}
ll A(ll n,ll m) {return f[n]*inv[n-m]%mod;}
int main()
{
    close;int n;cin>>n;
    for(int i=1;i<=n;++i) cin>>a[i];
    sort(a+1,a+n+1);
    Init(n);
    int cur=0;
    for(int i=1;i<=n;++i)
    {
        while(cur<n && a[i]>=2*a[cur+1]) cur++;
        pos[i]=cur;
    }
    if(pos[n]!=n-1) {cout<<0<<endl;return 0;} 
    dp[0]=1;pos[0]=-1;
    for(int i=1;i<=n;++i)
        for(int j=0;j<=pos[i];++j)
            dp[i]=(dp[i]+dp[j]*A(n-2-pos[j],pos[i]-pos[j]-1))%mod;
    cout<<dp[n]<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值