zoukankan      html  css  js  c++  java
  • 基础算法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,10^7],[10^5+1,10^9] $

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

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

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

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

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

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

    $[10^5+1,10^9]=>[②,③] $

    前缀和和差分

    什么是前缀和?
    对于一个数组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;Father~lca~-=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
    ",d[i]);
    	return 0;
    }
    

    树上前缀和

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

    差分和数据结构的结合

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

    贪心

    大胆猜想,无需证明

    luoguP1376

    第i周生产需要代价(x=min{c[i]*y[i],c[j]*y[i]+s*y[i]*(i-j)} jin [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
    ",k);
    }
    

    p4053(gugugu)

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

    一个关于匹配的模型:

    max>sum-max 无法匹配

    否则一定存在构造:

    按照a1……an依次排开:

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

    i与i+sum/2匹配

  • 相关阅读:
    sqlconnection 调用webservice服务
    WebService注解
    发布WebService 1.1
    soap 1.1 访问服务
    WebService一些概念
    8-7 Flutter通信机制&Dart端讲解
    8-4 Flutter Android混合开发实战-调试与发布
    8-3 Flutter Android混合开发实战-集成与调用
    8-2 Flutter混合开发流程与创建Flutter module
    7-5 高级功能列表下拉刷新与上拉加载更多功能实现
  • 原文地址:https://www.cnblogs.com/zhuier-xquan/p/11622855.html
Copyright © 2011-2022 走看看