7/29训练赛 AC率:【9:12】
★★★★☆
题意:定义c为相邻序列中两个数差的绝对值的最大值,其中最多能够改变k个数,求能够满足上述可以改动条件的c值。当序列长度为0,1时c=0.
思路:想到了二分,但是死活没有想到dp,以为是贪心!!是对我的差值最后的结果c进行的二分。但是理解了半天才明白了贪心的含义,希望我也能够讲的清楚==
考虑到你需要改变某些值,但是你肯定不能确切的知道这些值的,也就是无法进行计算的。这个时候就需要能够固定一些值并且让某区间内的一些值发生变化从而能够确定最终改变的次数和差值的结果。因此确定区间某段是需要改变的时候的情况比如j~i;因此假设dp[i]表示从1~i-1我需要改变的数目是多少,对二分的距离x进行检验,那么现在需要对1~i的部分考虑那些区间进行改变和如何变化的转移了。
在(j,i)的区间内(不包括端点)的时候,每相邻的部分的差值为x,因此在这个范围内的差最大为x*(i-j),那么在两个端点的部分的差值肯定需要也是小于这个范围值的才是必要条件,因此让差值保证能够从小到大的进行传递,如果能够保证满足条件,那么dp[i]=dp[j]+i-j-1,表示在这个范围内我的值都会发生改变,当1~i的个数在k之内的时候就可以直接取得i-1了,因为是开区间的范围,最后还需要考虑包含的i的情况,所以就是看剩余的个数+dp[i]是否满足条件即可。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN=2010;
const ll inf=1<<30;
ll a[MAXN];
int dp[MAXN];
int n,k;
bool check(ll x) //检查x是否满足性质
{
dp[1]=0;
for(int i=2;i<=n;i++)
{
dp[i]=MAXN; //初始条件
for(int j=i-1;j>=1;j--) //倒着来,距离从小到大
{
if(abs(a[i]-a[j])<=x*(i-j)) //当在j~i的距离满足小于x*(i-j)的最大条件时就可以进行状态转移了
dp[i]=min(dp[i],dp[j]+i-j-1); //在这个之间的改变值的个数
}
if(i-1<=k) dp[i]=min(dp[i],i-1); //当i的值过小时,需要设置的初始条件
}
for(int i=1;i<=n;i++)
if(dp[i]+n-i<=k) return true; //需要考虑到当i也被选中的时候
return false;
}
// **此题最最最关键的部分在于dp的理解!!**
int main()
{
ll l,r,mid;
while(~scanf("%d %d",&n,&k))
{
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
l=0;r=2e9;
while(l<=r) //二分的过程
{
mid=(l+r)/2;
if(check(mid))
r=mid-1;
else l=mid+1;
}
printf("%lld\n",l);
}
return 0;
}
E Insertion Sort 362C
★★★☆
题意:给定一个序列,当交换一对数后求对应的逆序对的个数和能够满足最小逆序对个数时可以组合的交换的对数。
思路:说真的比赛的时候一点点思路都没有,不会这种可以动态改变然后问你后续的是怎样的题目,然而这次大部分都是这样的题。。。。
看到一个比较好理解的题解,mi[i][j]表示0~j中比a[i]小的个数,ma[i][j]表示0~j中比a[i]大的个数。对于一个数列如5 1 4 7 3,那么可以知道在互换5 3的位置时其中只有在3~5的之间的值才能够影响最后的值,这时可以把这些中间的值的影响叠加到两端上。再加上互换后的影响1,用原来的逆序数减去这些值。就需要这些能够求在i,j下标内的在a[j]~a[i]间的值了。
如图中,需要找到2*(a[j]-a[i])的部分,那么就是两个相互交叉的部分了。即用比a[j]大的减去比a[i]大的和比a[i]小的减去比a[j]小的部分就是重叠的部分个数的2倍了
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
using namespace std;
const int MAXN=5005;
int mi[MAXN][MAXN],ma[MAXN][MAXN];
int a[MAXN];
int main()
{
int n;
scanf("%d",&n);
for(int i=0;i<n;i++) scanf("%d",&a[i]);
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
{
if(a[j]>a[i]) ma[i][j]=1; //初始条件,考虑到可能为0的时候
if(a[j]<a[i]) mi[i][j]=1;
if(j>0) ma[i][j]+=ma[i][j-1],mi[i][j]+=mi[i][j-1]; //进行传递的部分
}
int now=0;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
if(i<j&&a[i]>a[j]) now++; //计算最开始的逆序数
int res=0x3f3f3f3f,cnt=0;
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
{
if(a[i]<a[j]) continue;
int ans=now;
// mi[j][j-1]-mi[j][i] 为在[i+1~j-1]区间内小于a[j]的个数
// ma[i][j-1]-ma[i][i] 为在[i+1~j-1]区间内大于a[i]的个数
ans+=(mi[j][j-1]-mi[j][i]+ma[i][j-1]-ma[i][i]);
// ma[j][j-1]-ma[j][i] 为在[i+1~j-1]区间内大于a[j]的个数
// mi[i][j-1]-mi[i][i] 为在[i+1~j-1]区间内小于a[i]的个数
ans-=(ma[j][j-1]-ma[j][i]+mi[i][j-1]-mi[i][i]);
ans--;
if(ans==res) cnt++; //记录可能的个数
else if(res>ans) res=ans,cnt=1;
}
printf("%d %d\n",res,cnt);
return 0;
}
J Fixing Typos 363C
★☆
题意:只能说我开始的时候智障的读错了题,“You are allowed to delete letters from both ends and from the middle of the word.” 我以为是只能从首尾和正中间开始删,然而应该理解为可以从任意部分删,那为何题意不写成any呢???
思路:很简单,就是当出现xxx时候直接删除其中一个就可以了,为xxyy的时候就考虑删除x或者y,因为是一个个接进来的,考虑后续的影响,那就直接删掉y就可以了。可以通过构造来解决掉上面所提到的问题。
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
typedef long long ll;
const int MAXN=2000005;
char s1[MAXN],s2[MAXN];
int main()
{
scanf("%s",s1);
int len=strlen(s1);
if(len<2) {printf("%s\n",s1);return 0;}
s2[0]=s1[0]; s2[1]=s1[1];
int now=2;
for(int i=2;i<len;i++)
{
if(s1[i]==s2[now-1]&&s1[i]==s2[now-2]) continue;
else if(s1[i]==s2[now-1]&&s2[now-2]==s2[now-3]) continue;
else s2[now++]=s1[i];
}
for(int i=0;i<now;i++)
printf("%c",s2[i]);
puts("");
return 0;
}
感悟:还是需要学会STL容器里的熟练使用,才能够更快的解决点一些问题!! 对于字符的处理不仅仅可以通过删除等来处理,也可以通过构造新的字符串来进行,对于dp的理解上还是不够有所欠缺的!这个需要不断地去加强.