BZOJ5222[Lydsy2017省队十连测] 怪题

13 篇文章 0 订阅
4 篇文章 0 订阅

原题链接:https://www.lydsy.com/JudgeOnline/problem.php?id=5222

怪题

Description

给出一个长度为 n n n的整数序列 h i h_i hi,现在要通过一些操作将这个序列修改为单调不降序列,即 h i ≤ h i + 1 h_i ≤ h_i+1 hihi+1

可以用的操作有 m m m种,第 i i i种操作可以通过支付 c i c_i ci的代价将一段长度恰为 l i l_i li的连续子序列 + 1 +1 +1 − 1 −1 1(由对应的操作符确定是 + 1 +1 +1还是 − 1 −1 1,具体参考输入格式)。

不限制每种操作的使用次数,序列中的 h i h_i hi可以被改为任意整数(可以是负数),求最小代价,无解输出 − 1 −1 1

Input

第一行,两个整数 n , m n,m n,m

第二行, n n n个整数 h i h_i hi

接下来 m m m行,每行格式为 o p i , l i , c i op_i,l_i,c_i opi,li,ci空格隔开,其中 o p i op_i opi为一个字符,表示这种操作是 + 1 +1 +1还是 − 1 −1 1

Output

输出一行一个整数表示最小代价,若无解输出 − 1 −1 1

Sample Input
10 10
23 1 8 14 2 3 15 50 53 53
+ 4 6
- 1 10
+ 2 4
+ 4 2
- 3 5
+ 1 2
+ 3 2
+ 5 7
- 1 6
+ 4 5
Sample Output

96

HINT

对于 20 % 20\% 20%的数据, n , m ≤ 5 , h i ≤ 10 , c i ≤ 3 n,m≤5,h_i≤10,c_i≤3 n,m5hi10ci3

对于另 20 % 20\% 20%的数据, l i = 1 , h i ≤ 500 l_i=1,h_i≤500 li=1hi500

对于 100 % 100\% 100%的数据, n , m ≤ 200 , l i ≤ n , 1 ≤ h i , c i ≤ 1 0 6 n,m≤200,l_i≤n,1≤h_i,c_i≤10^6 n,m200lin1hi,ci106

题解

我们的目标是让序列单增,所以我们并不关心具体的数值,只需让差分数组全大于 0 0 0即可。

果断差分,发现一次区间操作会把一个数 − 1 -1 1,一个数 + 1 +1 +1,可以看做一单位流量的转移,那么我们就可以愉快的建图了:

源点先向一头一尾连条 i n f inf inf边,然后再遍历中间的点,源点向差分为正的点连边,差分为负的点向汇点连边;对于每个操作连 O ( n ) O(n) O(n)条边,单位费用为 c i c_i ci

接下来就可以欢乐费用流了,不满流输出 − 1 -1 1

代码
#include<bits/stdc++.h>
using namespace std;
const int M=5e3+5,inf=0x3f3f3f3f;
struct sd{int to,fl,val;}ed[M*20];
int n,m,start,end,id,dis[M],minf[M],pre[M],que[M];
long long ans1,ans2,full;
bool vis[M];
char ch[2];
vector<int>mmp[M];
queue<int>dui;
void add(int u,int v,int w,int f)
{
	mmp[u].push_back(id);ed[id++]=(sd){v,w,f};
	mmp[v].push_back(id);ed[id++]=(sd){u,0,-f};
}
bool spfa(int s,int t)
{
	fill(dis,dis+3+n,inf);
	memset(vis,0,sizeof(vis));
	dui.push(s);dis[s]=0;vis[s]=1;minf[s]=inf;
	int f,to,fl,hh,val;
	while(!dui.empty())
	{
		f=dui.front();dui.pop();
		vis[f]=0;
		for(int i=mmp[f].size()-1;i>=0;--i)
		{
			hh=mmp[f][i];to=ed[hh].to;fl=ed[hh].fl;val=ed[hh].val;
			if(fl>0&&dis[to]>dis[f]+val)
			{
				dis[to]=dis[f]+val;
				minf[to]=min(minf[f],fl);
				pre[to]=hh;
				if(!vis[to])dui.push(to),vis[to]=1;
			}
		}
	}
	return dis[t]!=inf;
}
void up(int s,int t)
{
	int v=t,hh;
	while(v!=s)
	{
		hh=pre[v];
		ed[hh].fl-=minf[t];
		ed[hh^1].fl+=minf[t];
		v=ed[hh^1].to;
	}
	ans1+=minf[t];
	ans2+=1ll*minf[t]*dis[t];
}
void in()
{
	scanf("%d%d",&n,&m);
	start=n+1,end=n+2;
	for(int i=1;i<=n;++i)scanf("%d",&que[i]);
	for(int i=0;i<=n;++i)que[i]=que[i+1]-que[i];
	add(start,n,inf,0),add(start,0,inf,0);
	for(int i=1;i<n;++i)que[i]>0?add(start,i,que[i],0):(full-=que[i],add(i,end,-que[i],0));
	for(int i=1,a,b,l,c,j;i<=m;++i)
	{
		scanf("%s%d%d",ch,&l,&c);
		for(a=0,b=l;b<=n;++a,++b)ch[0]=='+'?add(b,a,inf,c):add(a,b,inf,c);
	}
}
void ac()
{
	while(spfa(start,end))up(start,end);
	printf("%lld\n",ans1==full?ans2:-1);
}
main(){in();ac();}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ShadyPi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值