2019.08.12【NOIP提高组】模拟 B 组 模拟+DP+差分约束、spfa

21 篇文章 0 订阅
17 篇文章 0 订阅

0 少女觉

在幽暗的地灵殿中,居住着一位少女,名为古明地觉。
据说,从来没有人敢踏入过那座地灵殿,因为人们恐惧于觉一族拥有的能力——读心。
掌控人心者,可控天下。

咳咳。
人的记忆可以被描述为一个黑块(B)与白块(W)的序列,其中情感值被定义为序列中黑块数量与白块数量之比。
小五口在发动读心术时,首先要解析人的记忆序列,因此,需要将序列分割为一些段,并且要求每一段记忆序列的情感值都相等。
下面给出两个例子:
BWWWBB -> BW + WWBB (Ratio=1:1)
WWWBBBWWWWWWWWWB -> WWWB + BBWWWWWW + WWWB (Ratio=3:1)
现在小五手上有一个人的记忆序列,她想要知道,如何将手中的记忆序列分成尽可能多的段呢?

对于10%的数据,n<=15
对于20%的数据,n<=500
另有30%的数据,K=1
另有30%的数据,K<=50
对于100%的数据,N<=10^5 , 序列长度不超过10^9
保证对于全部测试点,输入文件行数不超过2.5*10^6


可以发现黑块与白块的比是确定的,是所有黑块比上所有白块,因而每一个小序列的比就是这个总比值

那么就按读入顺序往下做,每次一满足比值就马上记录答案

#include <cstdio>
#include <algorithm>

using namespace std;

int t,n,ans;
long long suma,sumb,wa,wb,p;
int a[100005],b[100005];

void read(int i){
	char ch=getchar();
	a[i]=0;
	while (ch<'0'||ch>'9') ch=getchar();
	while (ch>='0'&&ch<='9'){
		a[i]=a[i]*10+ch-'0';
		ch=getchar();
	}
	while (ch!='B'&&ch!='W') ch=getchar();
	if (ch=='B') b[i]=1,sumb+=a[i]; else b[i]=0,suma+=a[i];
}

long long pk(long long s,long long a,long long b){
	if ((a*s)%b==0) return (a*s)/b; else return -1;
}

int main(){
	freopen("silly.in","r",stdin);
	freopen("silly.out","w",stdout);
	scanf("%d",&t);
	for (;t;t--){
		scanf("%d",&n);
		suma=0,sumb=0,ans=0;
		for (int i=1;i<=n;i++){
			read(i);
		}
		if (suma==0||sumb==0){
			printf("%d\n",max(suma,sumb));
			continue;
		}
		wa=0;wb=0;
		for (int i=1;i<=n;i++){
			if (b[i]==0) {
				long long p=pk(wb,suma,sumb);
				if (p>wa&&p<=wa+a[i]) ans++;
				wa+=a[i];
			}
			else 
			{
				long long p=pk(wa,sumb,suma);
				if (p>wb&&p<=wb+a[i]) ans++;
				wb+=a[i];
			}
		}
		printf("%d\n",ans); 
	}
}

1 灵知的太阳信仰

在炽热的核熔炉中,居住着一位少女,名为灵乌路空。
据说,从来没有人敢踏入过那个熔炉,因为人们畏缩于空所持有的力量——核能。
核焰,可融真金。

咳咳。
每次核融的时候,空都会选取一些原子,排成一列。然后,她会将原子序列分成一些段,并将每段进行一次核融。
一个原子有两个属性:质子数和中子数。
每一段需要满足以下条件:
1、同种元素会发生相互排斥,因此,同一段中不能存在两个质子数相同的原子。
2、核融时,空需要对一段原子加以防护,防护罩的数值等于这段中最大的中子数。换句话说,如果这段原子的中子数最大为x,那么空需要付出x的代价建立防护罩。求核融整个原子序列的最小代价和。


设 f[i] 为 i 与 i+1 之间分开,到i为止的代价和
预处理出 l[i] 表示 i 最早的相同质子数的位置
易得 f [ i ] = m i n ( f [ j ] + b [ j + 1 — — i ] ) , l [ i ] &lt; j &lt; = i f[i]=min(f[j]+b[j+1 ——i]),l[i]&lt;j&lt;=i f[i]=min(f[j]+b[j+1i])l[i]<j<=i,这样的转移是 O ( n 2 ) O(n^2) O(n2)

可以用单调队列维护合法的点组成的队列,每次更新f[i]只需循环合法的点,每次又都取出不合法的点
因为前一个点不能转移的点,后一个点也不能通过这个点转移来

(因为可能TLE所以稍微卡了个常?)
比如register , 快读 ,if 改为(xx条件xx?xx:xx)

#include <cstdio>
#include <algorithm>

using namespace std;

const int N=100005;
const int inf=2000000000;
int n;
int l[N],p[N],w[N];
int f[N],e[N];

int read(){
	char ch=getchar();
	while (ch<'0'||ch>'9') ch=getchar();
	int x=0;
	while (ch>='0'&&ch<='9'){
		x=x*10+ch-'0';
		ch=getchar();
	}
	return x;
}

int main(){
	freopen("array.in","r",stdin);
	freopen("array.out","w",stdout);
	n=read();
	int ab;
	for (register int i=1;i<=n;i++){
		ab=read();w[i]=read();
		l[i]=(l[i-1]>p[ab]?l[i-1]:p[ab]),
		p[ab]=i,f[i]=inf;
	}
	int h=1,t=1;
	f[1]=w[1],e[1]=1;
	for (register int i=2;i<=n;i++){
		 while (h<=t&&e[h]<=l[i]) ++h;
		 while (h<=t&&w[i]>w[e[t]]) --t;
		 e[++t]=i;
		 for (int j=h+1;j<=t;j++)
		 	f[i]=(f[i]>f[e[j-1]]+w[e[j]]?f[e[j-1]]+w[e[j]]:f[i]);	
		 f[i]=(f[i]>f[l[i]]+w[e[h]]?f[l[i]]+w[e[h]]:f[i]);
	} 
	printf("%d",f[n]);
}

2 多段线性函数

在这里插入图片描述


哎嗨,据dalao言此题是为阅读题

就,题目的函数公式,相当于y到每个区间的距离
因为要求的函数值尽可能小,所以当y在区间内xi肯定是取y ,这时对函数值无贡献;当y不在区间内xi也一定是取与y更近的端点
所以就只与端点有关

就把所有端点堆一起排个序,取中间两个就是答案
啊哈,中位数

#include <cstdio>
#include <algorithm>

using namespace std;

int n;
int a[200005];

int main(){
	freopen("linear.in","r",stdin);
	freopen("linear.out","w",stdout);
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d%d",&a[i],&a[i+n]);
	sort(a+1,a+1+n*2);
	printf("%d %d",a[n],a[n+1]);
}

3 DY引擎

BOSS送给小唐一辆车。小唐开着这辆车从PKU出发去ZJU上课了。

众所周知,天朝公路的收费站超多的。经过观察地图,小唐发现从PKU出发到ZJU的所有路径只会有N(2<=N<=300)个不同的中转点,其中有M(max(0, N-100) <=M<=N)个点是天朝的收费站。N个中转点标号为1…N,其中1代表PKU,N代表ZJU。中转点之间总共有E(E<=50,000)条双向边连接。

每个点还有一个附加属性,用0/1标记,0代表普通中转点,1代表收费站。当然,天朝的地图上面是不会直接告诉你第i个点是普通中转点还是收费站的。地图上有P(1<=P<=3,000)个提示,用[u, v, t]表示:[u, v]区间的所有中转点中,至少有t个收费站。数据保证由所有提示得到的每个点的属性是唯一的。

车既然是BOSS送的,自然非比寻常了。车子使用了世界上最先进的DaxiaYayamao引擎,简称DY引擎。DY引擎可以让车子从U瞬间转移到V,只要U和V的距离不超过L(1<=L<=1,000,000),并且U和V之间不能有收费站(小唐良民一枚,所以要是经过收费站就会停下来交完钱再走)。

DY引擎果然是好东西,但是可惜引擎最多只能用K(0<=K<=30)次。


这题可以拆分成两部分,找出收费站和找出最短路

其中找出收费站需要用到差分约束系统

差分约束系统

用于题目给出形如: a [ i ] − a [ j ] &lt; = k a[i]-a[j]&lt;=k a[i]a[j]<=k 的约束条件,求 a [ i ] a[i] a[i]状态

首先我们荡开笔墨,先假设我们有一张有向图,已经求出了最短路, a [ i ] a[i] a[i]表示从1到i的最短路
现在有一条i到j的边,表示为 d i s [ i ] [ j ] dis[i][j] dis[i][j]
当然有 a [ j ] − a [ i ] &lt; = d i s [ i ] [ j ] a[j]-a[i]&lt;=dis[i][j] a[j]a[i]<=dis[i][j]

那么我们反过来,回到给出的约束条件 a [ i ] − a [ j ] &lt; = k a[i]-a[j]&lt;=k a[i]a[j]<=k,就相当于从j到i连一条权值为k的有向边
求出 a [ i ] a[i] a[i]的状态当然也就是用最短路,快乐跑spfa

当然有很多题目不会直接给出约束条件,要自己从题目描述中找


再回到这道题
条件:[u, v]区间的所有中转点中,至少有t个收费站
s [ i ] s[i] s[i]表示 1 1 1 i i i的收费站总数,则条件转化为 s [ v ] − s [ u − 1 ] &gt; = t s[v]-s[u-1]&gt;=t s[v]s[u1]>=t
再移下项,就是 s [ u − 1 ] − s [ v ] &lt; = t s[u-1]-s[v]&lt;=t s[u1]s[v]<=t
这就相当于差分约束系统的条件了,求出 s [ i ] s[i] s[i]状态后 s [ i ] − s [ i − 1 ] s[i]-s[i-1] s[i]s[i1]即为 i i i是否有收费站

然后就是用最短路跑DP啦
跑这个最短路呢,要记录第几个点,跳了几次

#include <cstdio>
#include <cstring>

using namespace std;

const int N=50002;
int n,m,e,p,l,k;
int ls[305],ne[N],y[N],w[N],bz[305],cnt;
int s[305],v[N*10],g[N*10],b[N][35],d[N*10];
int f[305][35],a[305][305];

void ad(int _u,int _v,int _w){
	ne[++cnt]=ls[_u],ls[_u]=cnt,y[cnt]=_v,w[cnt]=_w;
}

void spfa1(){
	memset(s,0x3f,sizeof s);
	for (int i=1;i<=n;i++)
	ad(i,i-1,0),ad(i-1,i,1);
	int h=0,t=1;
	v[1]=n;s[n]=m;bz[n]=1;
	while (h<t){
		int u=ls[v[++h]];
		while (u!=-1){
			if (s[v[h]]+w[u]<s[y[u]]){
				s[y[u]]=s[v[h]]+w[u];
				if (bz[y[u]]==0){
					bz[y[u]]=1;
					v[++t]=y[u];
				}
			}
			u=ne[u];
		}
		bz[v[h]]=0;
	}
	for (int i=n;i>=1;i--) s[i]=s[i]-s[i-1];
}

void read(){
	scanf("%d%d%d%d%d%d",&n,&m,&e,&p,&l,&k);
	memset(a,0x3f,sizeof a);
	for (int i=1;i<=e;i++){
		int u,_v,_w;
		scanf("%d%d%d",&u,&_v,&_w);
		if (_w<a[u][_v]) a[u][_v]=a[_v][u]=_w;
	}
	for (int i=0;i<=n;i++) ls[i]=-1;
	for (int i=1;i<=p;i++){
		int _u,_v,_t;
		scanf("%d%d%d",&_u,&_v,&_t);		
		ad(_v,_u-1,-_t);		
	}
}

void floyed(){
	for (int k=1;k<=n;k++)
		for (int i=1;i<=n;i++)
		if (i!=k)
			for (int j=1;j<=n;j++)			
			if (i!=j&&j!=k&&s[k]==0&&a[i][k]!=a[0][0]&&a[k][j]!=a[0][0])
			a[i][j]=(a[i][j]>a[i][k]+a[k][j]?a[i][k]+a[k][j]:a[i][j]);
}

void spfa2(){
	cnt=0;
	int h=0,t=1;
	memset(b,0,sizeof b);	
	memset(f,0x3f,sizeof f);
	b[1][0]=1,f[1][0]=0;
	v[1]=1,d[1]=0;
	while (h<t){
		int x=v[++h],y=d[h];
		for (int i=2;i<=n;i++)
		if (a[x][i]!=a[0][0]){
			if (f[x][y]+a[x][i]<f[i][y]){
				f[i][y]=f[x][y]+a[x][i];
				if (b[i][y]==0){
					b[i][y]=1;
					v[++t]=i;
					d[t]=y;
				}
			}
			if (y+1<=k&&a[x][i]<=l&&f[x][y]<f[i][y+1]){
				f[i][y+1]=f[x][y];
				if (b[i][y+1]==0){
					b[i][y+1]=1;
					v[++t]=i;
					d[t]=y+1;
				}
			}
		}
		b[x][y]=0;
	}
}

void print(){
	int ans=1000000000;
	for (int i=0;i<=k;i++)
		ans=(ans>f[n][i]?f[n][i]:ans);
	printf("%d",ans);
}

int main(){
	read();
	spfa1();	
	floyed();
	spfa2();	
	print();
}

我很喜欢你,我会一直喜欢你到你不再需要我喜欢你为止。——顾飞

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值