Codeforces Round #590 (Div. 3) E(暴力/差分)+F(子集dp)

思路来源

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;
} 
发布了467 篇原创文章 · 获赞 53 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/Code92007/article/details/102092190