A:Marketing Scheme
满足 r ≥ 2 ∗ l r\ge2*l r≥2∗l无解。
B:Reverse Binary Strings
这题应该注重结果而不是交换的过程。我们可以发现,对于一个串来说,一定发生了下面这种情况:
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][j−1],f[i−1][j−1]+abs(j−a[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]−1≥A[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 n−len个数,原序列就是严格上升的。
注意这里替换的时候要使用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...pos−1]的元素可以不用被修改, f [ p o s . . . l e n ] f[pos...len] f[pos...len]需要被修改,所以该区间需要修改 ( r − l + 1 ) − p o s (r-l+1)-pos (r−l+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=1i−1apjapi∈/(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 2∗aj≤ai中最大的 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 ai≥aj.第一步,我们先将 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 n−pos[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=0∑i−1dp[j]∗An−pos[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]=n−1.
现在考虑一下构造的合理性,这样的构造方式会不会造成重复计数的问题。例如我们令 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] n−pos[0]−2=n−1,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;
}