思路来源
https://www.cnblogs.com/carcar/p/11618099.html
https://blog.csdn.net/BeNoble_/article/details/101985236
https://codeforces.com/blog/entry/70233
E. Special Permutations(暴力/差分)
对于长度为n(n<=2e5)的近有序排列,n个数在第i个近有序排列中是这样的
给定m(m<=2e5)个数,第j个数为aj(1<=aj<=n),
对于一个给定的排列,将m个数的贡献和函数f计算如下:
取两个相邻位置的数aj和aj+1,其对答案贡献为abs(pos[aj]-pos[aj+1]),
即在原近有序排列中距离的差的绝对值,
依序输出对于i从1到n,在第i个近有序排列中,f函数的值
题解
两种做法,
①考虑第i个排列向第i+1个排列转移的时候,有哪些位置的贡献会发生变化
预处理第一个排列,然后转移的时候,先分别减去受u/v影响的数的贡献,交换位置后,再加上对应贡献
注意到,如果u和v正好在此轮互换,则(u,v)的贡献只能被统计一次
②差分,考虑相邻数在哪些时间段时,距离不变,贡献固定
不妨记相邻一对数值为l和r,且l<r,则
(1)i<l时,l和r相对距离不变,为r-l
(2)i==l时,l在首,且r在r处,距离为r-1
(3)l<i<r时,(l,r)间有一个数在首,距离为r-l-1
(4)i==r时,r在首,且r右侧有数[1,l-1],距离为l
(5)i>r时,l和r相对距离不变,为r-l
代码1(暴力)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int a[N],now[N],to[N];
int n,m,x,y;
ll ans[N];
vector<int>pos[N];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
{
scanf("%d",&a[i]);
pos[a[i]].push_back(i);
}
for(int i=1;i<=n;++i)
{
now[i]=i;
to[i]=i;
}
for(int i=1;i<m;++i)
ans[1]+=abs(now[a[i]]-now[a[i+1]]);
for(int i=2;i<=n;++i)
{
ans[i]=ans[i-1];
x=to[1],y=to[i];
for(int v:pos[x])
{
if(v!=m&&a[v+1]!=y)ans[i]-=abs(now[a[v]]-now[a[v+1]]);
if(v!=1&&a[v-1]!=y)ans[i]-=abs(now[a[v]]-now[a[v-1]]);
}
for(int v:pos[y])
{
if(v!=m)ans[i]-=abs(now[a[v]]-now[a[v+1]]);
if(v!=1)ans[i]-=abs(now[a[v]]-now[a[v-1]]);
}
now[x]=i;now[y]=1;
to[i]=x;to[1]=y;
for(int v:pos[x])
{
if(v!=m&&a[v+1]!=y)ans[i]+=abs(now[a[v]]-now[a[v+1]]);
if(v!=1&&a[v-1]!=y)ans[i]+=abs(now[a[v]]-now[a[v-1]]);
}
for(int v:pos[y])
{
if(v!=m)ans[i]+=abs(now[a[v]]-now[a[v+1]]);
if(v!=1)ans[i]+=abs(now[a[v]]-now[a[v-1]]);
}
}
for(int i=1;i<=n;++i)
printf("%I64d%c",ans[i]," \n"[i==n]);
return 0;
}
代码2(差分)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
int n,m,l,r;
int a[N];
ll f[N];//f[]:差分数组
//考虑每一对值的贡献
void add(int l,int r,int v)//[l,r]+=v
{
f[l]+=v;
f[r+1]-=v;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;++i)
scanf("%d",&a[i]);
for(int i=1;i<m;++i)
{
l=a[i],r=a[i+1];
if(l==r)continue;
if(l>r)swap(l,r);
add(1,l-1,r-l);
add(l,l,r-1);
add(l+1,r-1,r-l-1);
add(r,r,l);
add(r+1,n,r-l);
}
for(int i=1;i<=n;++i)
{
f[i]+=f[i-1];
printf("%I64d%c",f[i]," \n"[i==n]);
}
return 0;
}
F. Yet Another Substring Reverse(状压dp)
给你一个只由字母表前20个字母组成的字符串s,|s|<=1e6
要求你最多翻转一个区间[l,r],使得存在一个子串[L,R]
[L,R]内没有相同字母,且长度最大,输出这个长度
题解
肯定是一个区间不变,另一个区间翻过去接在一旁
问题等价于求两个串上不相交且字符集不相交的区间,使区间长度和最大
状压,一位代表一种字符是否出现,
若令dp[mask]为mask中字符的个数,
则问题等价于求两个不相交mask的和最大,
那么从子集转移而来时,取子集个数最多的那个子集转移即可
最终答案是最优的两个不相交集合的长度之和,mask和mask的补集
代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
char s[N];
int dp[1<<20],ans;
int main()
{
scanf("%s",s);
int len=strlen(s);
for(int i=0;i<len;++i)
{
int mask=0;
for(int j=i;j<len;++j)//从i起的不重复字母连续子串集
{
int v=s[j]-'a';
if(mask&(1<<v))break;
mask|=(1<<v);
dp[mask]=j-i+1;//mask中1的数量
}
}
for(int v=0;v<20;++v)
{
for(int j=0;j<(1<<20);++j)
{
if(j&(1<<v))dp[j]=max(dp[j],dp[j^(1<<v)]);
}
}
int all=(1<<20)-1;
for(int j=0;j<(1<<20);++j)
ans=max(ans,dp[j]+dp[all^j]);
printf("%d\n",ans);
return 0;
}