https://ac.nowcoder.com/acm/contest/11211#question
a题
思路:
两种方法
① 找最长上升子序列,那剩下的那些就是要修改的,O(nlogn)
② f[ i ][ j ] 代表 修改到第i位,最后一位<=j的最小代价。
转移方程:f[ i ][ j ] = min(f[ i ][ j-1 ],f[ i - 1][ j ] + (a[i] != (j - 1 + ‘A’))); O(26 * n)
int t,n,m;
char s[N],a[N];
int main(){
scanf("%d",&n);
getchar();
scanf("%s",s+1);
a[1] = s[1];
int cnt = 1;
for(int i=2;i<=n;i++){
if(s[i] >= a[cnt]) a[++cnt] = s[i];
else{
int pos = upper_bound(a+1,a+1+cnt,s[i]) - a;
a[pos] = s[i];
}
}
printf("%d\n", n - cnt);
return 0;
}
int t,n,m;
char s[N],a[N];
int f[N][30];
int main(){
cin>>n;
cin>>a + 1;
memset(f,0x3f,sizeof f);
for(int i=1;i<=26;i++) f[0][i] = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=26;j++){
f[i][j] = min(f[i][j-1],f[i-1][j] + (a[i] != (j - 1 + 'A')));
}
}
cout<<f[n][26]<<endl;
return 0;
}
b题
思路:
状压dp
每个点只有经过和没经过两个状态
f [ i ] [ j ] [ k ] ,i 表示二进制所有的状态,j表示当前停在哪个点, k取值0,1,2,3
k = 0 代表 没有经过重力和反重力
k = 1 代表 经过重力加速
k = 2 代表 经过反重力加速
k = 3 代表 两个加速都用过了
转移方程:
到状态0,原值 与 这次跳 取min
到状态1,分为这次用了重力加速 和 之前用的重力加速 ,与 原值取min
到状态2,分为这次用了反重力加速 和 之前用的反重力加速 , 与原值取min
到状态3,分为 状态1用了反重力加速 ;状态2用了重力加速;状态3正常跳;与原值取min
const int N = 70000,M = 20;
int f[N][M][5];
int n;
int p[M][M];
int a[M],b[M],c[M];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>p[i][j];
memset(f,0x3f,sizeof f);
for(int i=0;i<n;i++) f[1<<i][i+1][0] = 0;
for(int op=1;op<(1<<n);op++){
int cnt1=0,cnt2=0;
for(int i=1;i<=n;i++){
if(op >> (i-1) & 1) a[++cnt1] = i;
else b[++cnt2] = i;
}
for(int i=1;i<=cnt1;i++){
for(int j=1;j<=cnt2;j++){
f[op + (1 << (b[j] - 1))][b[j]][0] = min(f[op + (1 << (b[j] - 1))][b[j]][0],f[op][a[i]][0] + p[a[i]][b[j]]);
f[op + (1 << (b[j] - 1))][b[j]][1] = min(f[op + (1 << (b[j] - 1))][b[j]][1],min(f[op][a[i]][0],f[op][a[i]][1] + p[a[i]][b[j]]));
f[op + (1 << (b[j] - 1))][b[j]][2] = min(f[op + (1 << (b[j] - 1))][b[j]][2],min(f[op][a[i]][0] + 2 * p[a[i]][b[j]],f[op][a[i]][2] + p[a[i]][b[j]]));
f[op + (1 << (b[j] - 1))][b[j]][3] = min(min(f[op + (1 << (b[j] - 1))][b[j]][3],f[op][a[i]][3] + p[a[i]][b[j]]),min(f[op][a[i]][2],f[op][a[i]][1] + 2 * p[a[i]][b[j]]));
}
}
}
int res = 0x3f3f3f3f;
for(int i=1;i<=n;i++)
res = min(res,f[(1<<n) - 1][i][3]);
cout<<res<<endl;
return 0;
}
c题
思路:
与a题的做法二类似,
改一下转移方程就好了,加上偏移量
f [ i ] [ j ] = min(f [ i ] [ j - 1] , f [ i - 1] [ j ] + abs(a[ i ] - (j + 1 - ‘A’));
const int N = 1000010, M = 30;
char a[N];
int n;
ll f[N][M];
int main(){
cin>>n;
cin>>a + 1;
memset(f,0x3f,sizeof f);
for(int i=1;i<=26;i++) f[0][i] = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=26;j++){
f[i][j] = min(f[i][j-1],f[i-1][j] + abs(a[i] - (j - 1 + 'A')));
}
}
cout<<f[n][26]<<endl;
return 0;
}
d题
思路:
这题虽然过的人比g,h多,但我看题解的时候,理解花的时间比g和更久。。
考虑只有两行的时候,分别对两行进行降序排列,
那比如当前取了a[2] + b[3] ,那就往优先队列里放a[3]+b[2],a[2]+b[4],
思考一下这样的过程,会出现很多重复的。
如果是一开始将一维里所有的放进去,之后只要插入另一维就好了
多行也同理,每次将当前行 和 已经搞好的n个最大的数的合成行 进行合并
#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n;
int a[N],b[N];
struct node{
int va,id;
bool operator<(const node &t)const{
return va < t.va;
}
};
priority_queue<node> q;
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>b[i];
sort(b+1,b+1+n,greater<int>());
for(int i=2;i<=n;i++){
for(int j=1;j<=n;j++) cin>>a[j];
sort(a+1,a+1+n,greater<int>());
for(int j=1;j<=n;j++){
q.push({a[1] + b[j],1});
}
for(int j=1;j<=n;j++){
node t = q.top();q.pop();
b[j] = t.va;
q.push({t.va - a[t.id] + a[t.id+1],t.id+1});
}
while(q.size()) q.pop();
}
for(int j=1;j<=n;j++) cout<<b[j]<<" ";
return 0;
}
g题
思路:
按照题意思考,
任意大于2的子串都不是回文串,
如果子串的长度为偶数,则相邻两位不同
如果子串的长度为奇数,则当前位前后两位不同即可满足
如果每次枚举三位,则是 O(n * 26 ^ 3) 复杂度,太大了
考虑鸽巢原理,每一位只受前2位和后2位的影响,5种情况必有一种可以满足
所以枚举改变量即可。
f [ i ] [ y ] [ z ] 代表当前枚举到第i位,第i-1位的改变量是y,第i位的改变量是z的最小代价
f [ i ] [ y ] [ z ] = min(f [ i ] [ y ] [ z ] , f [ i - 1] [ x ] [ y ] + abs(z));
//g题
const int N = 1000010,M = 6;
char s[N];
int f[N][M][M];
int a[N],n;
int main(){
cin>>n;
cin>>s + 1;
for(int i=1;i<=n;i++) a[i] = s[i] - 'a';
memset(f,-1,sizeof f);
for(int i=-2;i<=2;i++)
for(int j=-2;j<=2;j++)
if((a[1] + i + 26) % 26 != (a[2] + j + 26) % 26)
f[2][i + 2][j + 2] = abs(i) + abs(j);
for(int i=3;i<=n;i++)
for(int x = -2;x <= 2;x ++)
for(int y = -2; y <= 2; y ++)
if(f[i-1][x + 2][y + 2] != -1)
for(int z = -2;z <= 2;z ++)
if((a[i] + z + 26) % 26 != (a[i-2] + x + 26) % 26 && (a[i] + z + 26) % 26 != (a[i-1] + y + 26) % 26)
if(f[i][y + 2][z + 2] == -1)
f[i][y + 2][z + 2] = f[i-1][x + 2][y + 2] + abs(z);
else
f[i][y + 2][z + 2] = min(f[i][y + 2][z + 2],f[i-1][x + 2][y + 2] + abs(z));
int res = 2e9;
for(int i=0;i<=4;i++)
for(int j=0;j<=4;j++)
if(f[n][i][j] != -1) res = min(res,f[n][i][j]);
cout<<res<<endl;
return 0;
}
h题
思路:
无论如何,位置 i %(k + 1) 余数相同的这些位上的这些数都应该一样
枚举a,取min
%(k+1) 余数一样的这些位,a = 这些位里的最小值,b = 全局最小值(因为a >= b)(因为只能减)
res = min(res , 是a的这些位之和 - 是a 的这些位中的最小值 * 是a的这些位的个数 + 是b的这些位之和 - 全局最小值 * 是b 的这些位的个数);
const int N = 1e6+10;
int n,k;
ll a[N],cnt[N],va[N],nowmin[N];
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]);
k++;
ll sum = 0,mins = 0x3f3f3f3f;
memset(nowmin,0x3f,sizeof nowmin);
for(int i=1;i<=n;i++) {
va[i % k] += a[i];
cnt[i % k]++;
nowmin[i % k] = min(nowmin[i % k],a[i]);
sum += a[i];
mins = min(mins,a[i]);
}
ll res = 2e18;
for(int i = 0 ;i < k;i++){
res = min(res,va[i] - nowmin[i] * cnt[i] + sum - va[i] - mins * (n - cnt[i]));
}
printf("%lld\n",res);
return 0;
}