AB题没必要会,但CD两题一定要会, EF我没看题,不做评论;
cf rate 1700+的出题就是不一样,差了200分感觉距离差的挺远的。
我只负责前三题题解,后面的不是我
A题(求逆序对):
暴力肯定会T掉,所以要借助树状数组这个利器;
题意:给一段序列,找出该序列中的逆序对个数。
考察范围:(树状数组,离散化)
思路:依次遍历数组中的每个元素,将元素的值为其插入的下标并在逐渐插入的过程中不断计算逆序对的数量即可;
注意:由于本题中a[i]<=1e9数据过大,需要离散化;因此
对于样例:
2,4,3,1;
先用树状数组在2的位置上插入1,再计算树状数组中3到n的所有数的和(未插入的为0),计算得和为零,再在4的位置上插入1在计算5到n的所有数的和,仍为0,继续在3的位置上插入1,再计算4到n的每一位数的和为1(因为在4处有一个已经插入的1),最后在1的位置上插入1,计算2~n的和为3; 则逆序对个数为1+3=4;
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e4+7;
unordered_map<int,int>mp;//用来离散化
int idx=0;//离散化后的坐标
int n;
int tr[N];//树状数组
struct node {
int ai;//第ai位
int x;//离散前的值
int hash;//离散后的值
} a[N];
bool cmp1(node a,node b) {
return a.x<b.x;
}
bool cmp2(node a,node b) {
return a.ai<b.ai;
}
int get_hash(int x) {
if(mp.count(x)==0) mp[x]=++idx;
return mp[x];
}
int lowbit(int x) {
return x&(-x);
}
void add(int x,int k) {
for(int i=x; i<=n; i+=lowbit(i))tr[i]+=k;
}
int ask(int x) {
ll sum=0;
for(int i=x; i; i-=lowbit(i))sum+=tr[i];
return sum;
}
int main() {
cin>>n;
int res=0;//用来储存逆序对数
for(int i=1; i<=n; ++i) {
cin>>a[i].x;
a[i].ai=i;
}
sort(a+1,a+n+1,cmp1);
for(int i=1;i<=n;++i) {
int t=get_hash(a[i].x);
a[i].hash=t;//t位哈希后的值
}
sort(a+1,a+1+n,cmp2);
for(int i=1;i<=n;++i) {
int t=a[i].hash;
add(t,1);
res+=(ask(n)-ask(t));
}
cout<<res;
return 0;
}
第二题:
这一题比较难:一开始想的是用树状数组维护,但是有点麻烦,老是迷,但本题a[]的范围给的是<=100的,索性用前缀和和后缀和处理
对于每一个不等于-1的元素只需要计算出前面有多少比他大的然后加一块,
对于每一个等于-1的元素找到最优解后,找到前面有多少比它大的,加一块就行;
这是你可能有一个疑问:对于每一个a[ i ]== -1的值,他的值得大小对于前后逆序对都会产生贡献,则只需要找出产生贡献最小的那个值就行,就 tt 记录该值,
####每次遍历每一个值的时候,别忘了更新前后缀就行
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
const int N=1e4+4,M=110;
int q[M],h[M];
int n,k,res=0;//res为全局记录逆序对的变量
int a[N];
int main() {
scanf("%d%d",&n,&k);
for(int i=1; i<=n; ++i) {
scanf("%d",&a[i]);
if(a[i]!=-1)++q[a[i]];
}
for(int i=1; i<=k; ++i)q[i]=q[i-1]+q[i]; //求前缀和
for(int i=1; i<=n; ++i) {
if(a[i]==-1) {
int mx=0x7f7f7f7f,tt=0;
for(int j=1;j<=k;++j){
if(q[j-1]+h[j+1]<mx){
mx=q[j-1]+h[j+1];
tt=j;
}
}
a[i]=tt;
res+=h[a[i]+1];
} else {
for(int j=a[i];j<=k;++j)--q[j];//去除该点对后续节点的影响
res+=h[a[i]+1];
}
for(int j=1;j<=a[i];++j)++h[j];//维护后缀和
}
cout<<res;
return 0;
}
第三题:(典型的01背包问题)//典型的01背包问题,不会的建议去bilibili找讲解:
点这里,我以前写过的01背包的笔记
通常状态转移方程分为拿和不拿两个状态;
如果j<v[i];表示不能够拿,则不能拿,则当前的dp值是不拿该物品所对应的dp值
如果j>=v[i]; 则取拿和不拿两个状态所对应的最大值;
二维的:(并不是本题的代码)
#include<iostream>
#include<cmath>
using namespace std;
int V,N;//N是物品的个数,V 是所容纳最大的体积
int v[1005],c[1005];
int dp[1005][1005];
int main() {
cin>>N>>V;
for(int i=1; i<=N; i++)cin>>v[i]>>c[i];
for(int i=1; i<=N; ++i) {
for(int j=1; j<=V; ++j) {
if(j<v[i])dp[i][j]=dp[i-1][j];
else dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+c[i]);
}
}
for(int i=0; i<=N; ++i) {
for(int j=0; j<=V; ++j) {
cout<<dp[i][j]<<" ";
}
cout<<"\n";
}
return 0;
}
一维的:
AC代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5e6+7;
int n,w;
int dp[N];
int v1,w1;
int main() {
cin>>n>>w;
for(int i=1; i<=n; ++i) {
cin>>v1>>w1;
for(int i=w; i>=v1; --i) {
dp[i]=max(dp[i],dp[i-v1]+w1);
}
}
cout<<dp[w];
return 0;
}