Codeforces 884F Anti-Palindromize 题解

题目链接1

题目链接2

题目原文

CF884F

CF884F_cn

题目大意

将一个字符串打乱后放回,满足第一个位置上的字符和最后一个位置上的字符不相同,第二个的位置上的字符和倒数第二个位置上的字符不相同…以此类推。每个位置有一个权值,如果原串和新串的位置上的字符相同,就获得这个位置上的权值。求满足条件的最大的权值和。

思路

显然我们要尽可能地保证原字符串中尽可能多的字符在原位上,那么如果两个对应位置相同,我们可以视作将权值小的提到前面,然后优先考虑交换它们的位置和顺序,于是可以用dp等方法求出。

上面的是我最开始的思路,但是状态的设计和转移都比较难。我们可以换一种思想考虑,我们先把所有的字符全部拿出来,然后再一个个地放回去。那么我们考虑放回去的时候的约束有哪些:

1.放回去的时候每个字符的总个数要和原串中每个字符的总个数相同。

2.放回去之后前后两个对应的位置上不能出现重复的字符。(也就是说同种字符只能放在两个位置上的其中一个)

3.每一个位置都必须放一个字符。

从这里就看出了可以用网络流相关知识来求解了。那么现在来考虑如何保证权值最大。

同样我们可以只考虑哪些字符不在原来的位置上即可,所以我们可以求出不在原来位置上的权值和,用总权值-这些值就是答案。

实现

算法:最小费用最大流

首先建立源点S和汇点T

考虑约束条件1,我们先求出每个字符出现的次数cnt[i],然后从S向每个字符(设点的类型为A)连流量为cnt[i],费用为0的边,这样就保证了每个字符只能选最多cnt[i]次。

考虑约束条件2,由于同样的字符在每两个对应位置上只能放一个,那我们每个字符最多只能放n/2次,那么我们每个字符的点再新建n/2个点(设点的类型为B),第i个点分别对应位置i和位置n-i+1,那么我们从每个A类型的点向对应的B类型的点连流量为1,费用为0的点,就限制了对于每个字符,对应的两个位置只能选一个来放。

考虑约束条件3,我们对每一个位置建一个点(设点的类型为C),那么我们对每一个位置都建一条到汇点T流量为1,费用为0的点,就保证了每一个位置最多有一个字符。然后我们从B类型的点向C类型的点连边,这里是关键的一步,由上一个约束条件的考虑得出第i个点分别向i和n-i+1连流量为1的边即可。考虑费用:如果B类型的点的字符和连的位置对应的原串的字符相等,则费用为0,否则费用为对应的权值。

显然,在题意给定一定合法的情况下,这个网络一定满流。那我们就得出了修改所需要的代价,最后答案就是所有权值和-最小费用。

代码

#include < cstdio >
#include < cstring >
#include < algorithm >
#include < queue >
using namespace std;
const int maxn=1000005;
const int inf=0x7f7f7f7f;
struct node
{
	int to,next,cost,flow;
} edge[maxn<<1];
int head[maxn],dist[10005],k=1,tot_cost=0,ans=0,s,t;
bool visit[10005];
void add(int u,int v,int w,int c)
{
	edge[++k].to=v;edge[k].next=head[u];edge[k].flow=w;edge[k].cost=c;head[u]=k;
	swap(u,v);
	edge[++k].to=v;edge[k].next=head[u];edge[k].flow=0;edge[k].cost=-c;head[u]=k;
}
bool spfa()
{
	memset(dist,inf,sizeof(dist));
	memset(visit,false,sizeof(visit));
	dist[t]=0;visit[t]=true;deque<int> q;q.push_back(t);
	while(!q.empty())
	{
		int x=q.front();q.pop_front();visit[x]=false;
		for(int i=head[x];i;i=edge[i].next)
		{
			if(dist[edge[i].to]>dist[x]-edge[i].cost&&edge[i^1].flow)
			{
				dist[edge[i].to]=dist[x]-edge[i].cost;
				if(!visit[edge[i].to])
				{
					visit[edge[i].to]=true;
					if(!q.empty()&&dist[q.front()]>dist[edge[i].to]) q.push_front(edge[i].to);
					else q.push_back(edge[i].to);
				}
			}
		}
	}
	return dist[s]!=inf;
}
int dfs(int x,int f)
{
	visit[x]=true;
	if(x==t||!f) return f;
	int flow=0;
	for(int i=head[x];i;i=edge[i].next)
	{
		if(visit[edge[i].to]) continue;
		if(dist[edge[i].to]!=dist[x]-edge[i].cost||!edge[i].flow) continue;
		int temp=dfs(edge[i].to,min(f-flow,edge[i].flow));
		edge[i].flow-=temp;
		edge[i^1].flow+=temp;
		flow+=temp;
		tot_cost+=temp*edge[i].cost;
		if(f==flow) break;
	}
	return flow;
}
void zkw()
{
	while(spfa())
	{
		visit[t]=true;
		while(visit[t])
		{
			memset(visit,false,sizeof(visit));
			dfs(s,inf);
		}
	}
}
int cnt[maxn],n,b[maxn],alp[maxn];
char str[maxn];
int main()
{
	#ifdef lemon
	freopen("844f.in","r",stdin);
	#endif
	scanf("%d",&n);s=0;t=4000;
	scanf("%s",str+1);
	for(int i=1;i<=n;i++) alp[i]=str[i]-'a'+1;
	for(int i=1;i<=n;i++) scanf("%d",&b[i]),ans+=b[i];
	//s:0
	//alp:1-26
	//alp_split:100-2700(100*alp+num(n/2))
	//point:3000-3100
	//t:4000
	for(int i=1;i<=n;i++) cnt[alp[i]]++;
	for(int i=1;i<=26;i++)
	{
		add(s,i,cnt[i],0);
		for(int j=1;j<=(n>>1);j++)
		{
			add(i,100*i+j,1,0);
			if(alp[j]==i) add(100*i+j,3000+j,1,0);
			else add(100*i+j,3000+j,1,b[j]);
			if(alp[n-j+1]==i) add(100*i+j,3000+n-j+1,1,0);
			else add(100*i+j,3000+n-j+1,1,b[n-j+1]);
		}
	}
	for(int i=1;i<=n;i++) add(3000+i,t,1,0);
	zkw();
	printf("%d",ans-tot_cost);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值