基础算法1

离散化

就是把无限空间(在OI中就是很大的范围)里面的信息,映射到一个较小的空间里面

有时候需要保证仍然保留了一些信息,比如元素之间的大小关系,比如相邻两个元素的差(去重w)

一个对闭合区间离散化的小技巧

有若干个区间$[L_i,R_i] $,把他们离散化成若干个区间:

如何划分?

集合Sp表示覆盖这个点p的区间编号

将数轴上的点划分成n个区间,每个区间的点等价(区间内的点被覆盖的区间相同,也就是集合Sp相同);

举个例子:

[1,3]------①

[2,5]------②

那么划分为三个区间:

[1,1]={①};

[2,3]={①,②};

[4,5]={②}

如何实现?

1.将所有的$L_i,R_i+1 $都拿出来排序(排序时不考虑L与R的区别);

我们设排序去重后的数组为V;

那么相邻两个元素可以得到一个区间$[V_i,V_{i+1}-1] $

而得到的每一个区间,也就像我们上面↑举的例子所划分的三个区间一样的;

如何去重:

sort(V + 1, V + 1 + N);
M = unique(V + 1, V + 1 + N) - (V + 1);

如何应用?

举个栗子:

假设读入区间为$[1,107],[105+1,10^9] $

那么我们划分的区间就是:

\([1,10^5]\) ---------①

\([10^5+1,10^7]\)-----------②

\([10^7+1,10^9]\)-----------③

这样,对于读入的两个区间,就可以映射为:

$[1,10^7]=>[①,②] $

$[105+1,109]=>[②,③] $

前缀和和差分

什么是前缀和?
对于一个数组A,记录S i = A[1]+ A[2]+ ⋯ +A[i]
显然S[i]= S[i−1]+A[i],所以S数组可以线性递推出来
那么A[l]+ A[l+1]+ ⋯ +A[r]= S[r]− S[l−1]
这样就可以把,求一个数组中一段区间的和这个O(r − l)的事情变
成O(1)的啦
实际上,只要有可加可减性的信息都可以这么搞
求出前缀积、前缀异或和等等

什么是差分?
对一个数组A进行差分,形式化而言就是:D[i]= A[i]−A[i−1]
对原序列A的区间[L, R]进行+1等价于D[L]+= 1, D[R+1]−= 1
那么可以直接维护D
用D还原A也是轻松的,差分的逆运算是前缀和,直接对D做前缀和即可还原A

洛谷P3406

据说是一道差分模板题,我们可以记录每一条铁路经过的次数,如果O(n^2)的去加,显然会超时(n超大),那么我们可以考虑差分数组。

因为第i段铁路表示的是第i个城市~第i+1个城市,所以对于一段x=>y,我们只需要在两个中较小的对应的差分数组+1,较大的-1,然后对每一段铁路,贪心的比较是\(c_i+b_i*经过次数\)与$a_i*经过次数 $的大小关系,取较小一个加进ans里;

这样处理就好了√;

#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

ll n,m;
ll d[100010];
ll a,b,c;

int main(){
	n=read();
	m=read();
	ll p,nxt;
	ll z,y;
	for(int i=1;i<=m;i++) {
		p=read();
		if(i==1) {
			nxt=p;
			continue;
		}
		y=min(p,nxt);
		z=max(p,nxt);
		d[y]++;
		d[z]--;
		nxt=p;	
	}
	ll ans=0;
	for(int i=1,x=0;i<n;i++) {
		a=read();
		b=read();
		c=read();
		x+=d[i];
		if(c+b*x<a*x) ans+=c+b*x;
		else ans+=a*x;
	}
	
	printf("%lld",ans);
	return 0;
}

洛谷P1115

之前做这道题好像是用dp做的,转移方程大概是dp[i]=max{dp[i-1]+a[i],a[i]};

现在尝试用前缀和做,也就是找一对l,r,使得sum[r]-sum[l]最大,从左往右维护sum[i]的最小值,然后与sum[i]相减(注意不可以sum[i]-sum[i]),求最大(好乱)

#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

ll n,ans;
ll sum[200010];

int main(){
	n=read();
	ll minn=21474836470000;
	for(int i=1,x;i<=n;i++) {
		x=read();
		sum[i]=sum[i-1]+x;
	}
	ans=sum[1];
	minn=sum[1];
	ll y;
	for(int i=2;i<=n;i++) {
		y=max(sum[i],sum[i]-minn);
		if(sum[i]<minn) minn=sum[i];
		ans=max(ans,y);
		
	}
	printf("%lld",ans);
	return 0;
}

洛谷P3397

二维前缀和与二维差分:

\(s[x][y]=s[x][y-1]+s[x-1][y]-s[x-1][y-1]+a[x][y]\)

二维差分:

对于(x1,y1)~(x2,y2) 的区间A+1;

等价于:

差分数组D:\(D[x1][y2+1]-=1 \ \ D[x2+1][y1]-=1 \ \ D[x2+1][y2+1]+=1 \ \ D[x1][y1]+=1;\)

for (x = 1 ~ N)
	for (y = 1 ~ M)
		S[x][y] = S[x - 1][y] + S[x][y - 1] - S[x - 1][y - 1] + A[x][y];
//表示不懂下面在干什么:

for (x = 1 ~ N)//对每一行做一个一维前缀和
	for (y = 1 ~ M)
		S[x][y] = S[x][y - 1] + A[x][y];
for (y = 1 ~ M)//对列做前缀和
	for (x = 1 ~ N)
		S[x][y] += S[x - 1][y];

维护二维数组D,然后最后做二维前缀和:

#include<bits/stdc++.h>
#define ll long long
using namespace std;

inline ll read(){
	ll ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

int n,m;
int D[1010][1010],s[1010][1010];

int main(){
	n=read();
	m=read();
	int x1,y1,x2,y2;
	for(int i=1;i<=m;i++) {
		x1=read();y1=read();
		x2=read();y2=read();
		D[x1][y1]+=1;
		D[x2+1][y2+1]+=1;
		D[x1][y2+1]-=1;
		D[x2+1][y1]-=1;
	}
	for(int x=1;x<=n;x++) 
		for(int y=1;y<=n;y++) 
			s[x][y]=s[x-1][y]+s[x][y-1]-s[x-1][y-1]+D[x][y];
	
	for(int i=1;i<=n;i++) {
		for(int j=1;j<=n;j++) 
			printf("%d ",s[i][j]);
		puts("");
	}
	return 0;
}

树上差分:

对边差分:

(u,v)全部加上w,对于差分数组D就是:

D[u]+=w;D[v]+=w;D[lca(u,v)]-=2*w;

用子树中差分数组的和来还原信息:即将子树中所有的D[i] i∈son(r) 相加;

每个点的信息记录的是其到父亲的边的信息

对点差分:

(u,v)全部加上w,对于差分数组D就是:

D[u]+=w;D[v]+=w;D[lca(u,v)]-=w;Fatherlca-=w;

感性李姐

洛谷P3258

利用上面对点差分的思想,进行加减操作,注意因为下一段路径的起点是上一段路径的终点,所以除a[1]外,其他经过的点的值要顺次减一。求和是做子树和不是树上前缀和。

#include<bits/stdc++.h>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-' ) ans=-ans;
	return ans;
}
const int mxn=300010;
int n,ecnt;
int a[mxn],head[mxn],d[mxn];
int fa[mxn][30],dep[mxn];
bool vis[mxn];
struct node {
	int to,nxt;
}e[mxn<<1];

void add(int u,int v) {
	++ecnt;
	e[ecnt].to=v;
	e[ecnt].nxt=head[u];
	head[u]=ecnt;
	++ecnt;
	e[ecnt].to=u;
	e[ecnt].nxt=head[v];
	head[v]=ecnt;
}

void dfs(int u,int f) {
	vis[u]=1;
	for(int i=head[u],v;i;i=e[i].nxt) {
		v=e[i].to;
		if(vis[v]) continue;
		dep[v]=dep[u]+1;
		fa[v][0]=u;
		dfs(v,u);
	}
}

void fill () {
	for(int i=1;i<=29;i++) 
		for(int j=1;j<=n;j++) 
			fa[j][i]=fa[fa[j][i-1]][i-1];
}


int lca(int x,int y) {
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=29;i>=0;i--) 
		if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
		
	if(x==y) return x;
	for(int i=29;i>=0;i--) {
		if(fa[x][i]!=fa[y][i]) {
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	return fa[x][0];
}

void sum(int u,int f) {
	for(int i=head[u],v;i;i=e[i].nxt) {
		v=e[i].to;
		if(v==f) continue;
		sum(v,u);
		d[u]+=d[v];
	}
}

int main(){
	n=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int i=1,x,y;i<n;i++) x=read(),y=read(),add(x,y); 
	dep[1]=1;
	dfs(1,0);
	fill();
	for(int i=1;i<n;i++) {
		int L=lca(a[i],a[i+1]);
		d[a[i]]++;
		d[a[i+1]]++;
		d[L]--;
		d[fa[L][0]]--;
	}
	sum(1,0);
	for(int i=2;i<=n;i++) d[a[i]]--;
	for(int i=1;i<=n;i++) printf("%d\n",d[i]);
	return 0;
}

树上前缀和

定义为一个点到根路径的点权和

差分和数据结构的结合

对于一个支持单点修改、区间求和的数据结构,如果使用差分,就可以支持区间加法、单点查询(维护差分数组)
甚至可以支持区间加法、区间求和
一个经典的例子就是用树状数组来完成这些事情
用DFS序还可以把放到树上,区间变成子树

贪心

大胆猜想,无需证明

luoguP1376

第i周生产需要代价\(x=min\{c[i]*y[i],c[j]*y[i]+s*y[i]*(i-j)\} \ j\in [1,i-1]\)

那么对于第i周,我们贪心的选择最小的即可:

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
	ll ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-' ) ans=-ans;
	return ans;
}

ll n,s;
ll c[10010],y[10010];

int main(){
	n=read();s=read();
	for(int i=1;i<=n;i++) {
		c[i]=read();
		y[i]=read();
	}
	ll minn;
	ll ans=0;
	for(int i=1;i<=n;i++) {
		minn=y[i]*c[i];
		for(int j=1;j<i;j++) 
			minn=min(minn,c[j]*y[i]+y[i]*s*(i-j));
		ans+=minn;
	}
	printf("%lld",ans);
	return 0;
}

排序不等式:

正序和不小于乱序和,乱序和不小于逆序和

A[i] B[i]

均从小到大排序,则:

\(\sum A[i]*B[j]=A数组从小到大排序*B数组从小到大排序(max) > \sum A[i]*B[j]A数组从大到小排序*B数组从小到大排序\)

洛谷P1842

只想大胆猜想,不想小心证明:

把W+S较小的放在上面

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
	ll ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-' ) ans=-ans;
	return ans;
}
const int mxn=50010;

ll n;
struct node {
	ll s,w;
}cow[mxn];

bool cmp(node a,node b) {
	return a.s+a.w<b.s+b.w;
}

int main(){
	n=read();
	for(int i=1;i<=n;i++) {
		cow[i].w=read();
		cow[i].s=read();	
	}
	sort(cow+1,cow+n+1,cmp);
	ll ans=-2147483647;
	ll sum=0;
	for(int i=1;i<=n;i++) {
		ans=max(ans,sum-cow[i].s);
		sum+=cow[i].w;
	}
	printf("%lld",ans);
	return 0;
}

p1223√

p1012√

p1080 高精×

一个有点厉害的题

有一个初始为0的数X
有若干个操作,第i个操作是给X先加上Ai再减去Bi,这两个都是非负的数
找到一个操作的排列,使得进行完操作之后,X最大的时候最小

考虑一个完整的不被分割的序列,可以用一个二元组(sum, max)来表示。 sum是序列中的和, max是最大前缀和

那么每个操作单独看成的序列,就是\((A_i − B_i, A_i)\)

两个序列分别是\((suma, maxa)\)\((sumb, maxb)\),如果把第二个序列接到第一个序列后面,新序列就是\((suma +sumb, max(maxa, suma + maxb))\)

按照前面的贪心方法,比较两个元素排序的关键字,就是两种方法新得到的max比大小

相等的时候显然把sum较小的放到前面对全局的影响更优

这个题还可以放到树上 ,具体而言就是对于操作的先后顺序有一些限制,而且限制关系形成了一颗树

哈夫曼编码
Kruskal求最小生成树
Dijkstra求单源最短路

调整法贪心

p1230

(没删调试信息wa好久)

#include<bits/stdc++.h>
#define ll long long

using namespace std;

inline ll read() {
	ll ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-' ) ans=-ans;
	return ans;
}
const int mxn=510;
bool vis[mxn];
int m,n;
struct node {
	int ti,mon;
}a[mxn];

bool cmp(node x,node y) {
	return x.mon>y.mon;
}

int main(){
	m=read();
	n=read();
	for(int i=1;i<=n;i++) a[i].ti=read();
	for(int i=1;i<=n;i++) a[i].mon=read();

	sort(a+1,a+n+1,cmp);
	bool bj=0;
	int ans=0;
	for(int i=1;i<=n;i++) {
		bj=0;
		for(int j=a[i].ti;j;j--) {
			if(!vis[j]){
				bj=1;
				vis[j]=1;
				break;
			}
		}
		if(!bj) {
			for(int j=n;j;j--) {
				if(!vis[j]) {
					vis[j]=1;
					break;
				}
			}
		
			ans+=a[i].mon;
		}
	}
	int k=m-ans;
	printf("%d\n",k);
}

p4053(gugugu)

考虑按照截止时间排序,能修就修

一个关于匹配的模型:

max>sum-max 无法匹配

否则一定存在构造:

按照a1……an依次排开:

a11,a12,a13,……,b11,b12………………n11,n12,……

i与i+sum/2匹配

posted @ 2019-10-04 18:56  Sweetness  阅读(184)  评论(0编辑  收藏  举报