2019.08.12【NOIP提高組】模擬 B 組 模擬+DP+差分約束、spfa

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]=min(f[j]+b[j+1i])l[i]&lt;j&lt;=if[i]=min(f[j]+b[j+1 ——i]),l[i]&lt;j&lt;=i,這樣的轉移是O(n2)O(n^2)

可以用單調隊列維護合法的點組成的隊列,每次更新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;=ka[i]-a[j]&lt;=k 的約束條件,求a[i]a[i]狀態

首先我們盪開筆墨,先假設我們有一張有向圖,已經求出了最短路,a[i]a[i]表示從1到i的最短路
現在有一條i到j的邊,表示爲dis[i][j]dis[i][j]
當然有a[j]a[i]&lt;=dis[i][j]a[j]-a[i]&lt;=dis[i][j]

那麼我們反過來,回到給出的約束條件a[i]a[j]&lt;=ka[i]-a[j]&lt;=k,就相當於從j到i連一條權值爲k的有向邊
求出a[i]a[i]的狀態當然也就是用最短路,快樂跑spfa

當然有很多題目不會直接給出約束條件,要自己從題目描述中找


再回到這道題
條件:[u, v]區間的所有中轉點中,至少有t個收費站
s[i]s[i]表示11ii的收費站總數,則條件轉化爲s[v]s[u1]&gt;=ts[v]-s[u-1]&gt;=t
再移下項,就是s[u1]s[v]&lt;=ts[u-1]-s[v]&lt;=t
這就相當於差分約束系統的條件了,求出s[i]s[i]狀態後s[i]s[i1]s[i]-s[i-1]即爲ii是否有收費站

然後就是用最短路跑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();
}

我很喜歡你,我會一直喜歡你到你不再需要我喜歡你爲止。——顧飛

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章