【日记】12.16

12.16

Hint

VScode里,Alt+Z可以一行变多行,再按一次可以变回来。

线段树

  1. HDU4553:http://acm.hdu.edu.cn/showproblem.php?pid=4553

    寒假来了,又到了小明和女神们约会的季节。
      小明虽为屌丝级码农,但非常活跃,女神们常常在小明网上的大段发言后热情回复“呵呵”,所以,小明的最爱就是和女神们约会。与此同时,也有很多基友找他开黑,由于数量实在过于巨大,怎么安排时间便成了小明的一大心事。
      我们已知小明一共有T的空闲时间,期间会有很多女神或者基友来找小明。
      作为一个操作系统曾经怒考71分的大神,小明想到了一个算法,即“首次适应算法”,根据操作系统课本的描述,就是找一段最靠前的符合要求的连续空间分配给每个请求,由此小明做出了一个决定:
      当一个基友来找小明时,小明就根据“首次适应算法”来找一段空闲的时间来和基友约好,如果找到,就说“X,let’s fly”(此处,X为开始时间),否则就说“fly with yourself”;
      当女神来找小明时,先使用一次“首次适应算法”,如果没有找到,小明就冒着木叽叽的风险无视所有屌丝基友的约定,再次使用“无视基友首次适应算法”,两次只要有一次找到,就说“X,don’t put my gezi”(此处,X为开始时间),否则就说“wait for me”
      当然,我们知道小明不是一个节操负无穷的人,如果和女神约会完,还有剩余时间,他还是会和原来约好的基友去dota的。(举个例子:小西(屌丝)和小明约好在1~5这个时间单位段内打dota,这时候,女神来和小明预约长度为3的时间段,那么最终就是1~3小明去和女神约会,搞定后在4~5和小西打dota)
      小明偶尔也会想要学习新知识,此时小明就会把某一个时间区间的所有已经预定的时间全部清空用来学习并且怒吼“I am the hope of chinese chengxuyuan!!”,不过小明一般都是三分钟热度,再有人来预定的话,小明就会按耐不住寂寞把学习新知识的时间分配出去。

思路:这题就是麻烦,没什么技术含量。设置两个区间信息,一个是最靠左的全0区间,一个是最靠左的全01区间(方便没有全0的时候给女神找01区间),之后和原先思路一样。这里可以用结构体写个inline函数再减少一下工作量。

  1. HDU3333:询问每个区间不同数的和。离线。

思路:与数区间不同数个数一样。首先按右端点排序,之后将每个数最后一次出现的位置设置为原数,其他位置均为0,求区间和即可。这里为单点修改+区间求和,树状数组即可。同样还是用last(这里必须用unordered_map)数组实现。

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int M=1e5+20;
struct Query{
	int l,r,no;
	bool operator<(const Query &x)const{return r<x.r;}
}qu[M];
int a[M],n;
LL c[M],ans[M];
unordered_map<int,int> last;
inline int lowbit(int x){return x&-x;};
inline void operate(int x,int k){
	for(int i=x;i<=n;i+=lowbit(i))
		c[i]+=k;
}
inline LL query(int x){
	LL ans=0;
	for(int i=x;i;i-=lowbit(i))
		ans+=c[i];
	return ans;
}
inline LL BITquery(int l,int r){return query(r)-query(l-1);}
int main(){
	int T;
	scanf("%d",&T);
	for(int z=1;z<=T;++z){
		scanf("%d",&n);
		last.clear();
		for(int i=1;i<=n;++i)
			scanf("%d",&a[i]),c[i]=0;
		int q;
		scanf("%d",&q);
		for(int i=1;i<=q;++i)
			scanf("%d%d",&qu[i].l,&qu[i].r),qu[i].no=i;
		sort(qu+1,qu+q+1);
		int p=0;
		for(int i=1;i<=q;++i){
			while(p<qu[i].r){
				++p;
				if (last[a[p]])
					operate(last[a[p]],-a[p]);
				operate(p,a[p]);
				last[a[p]]=p;
			}
			ans[qu[i].no]=BITquery(qu[i].l,qu[i].r);
		}
		for(int i=1;i<=q;++i)
			printf("%lld
",ans[i]);
	}
	return 0;
}

CF

  1. 1157E:https://codeforces.com/problemset/problem/1157/E

思路:这是1700的题??这也太水了吧。set里lowerbound即可。

#include<bits/stdc++.h>
using namespace std;
const int M=2e5+20;
int a[M];
multiset<int> s;
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	for(int i=1;i<=n;++i){
		int c;
		scanf("%d",&c),s.insert(c);
	}
	for(int i=1;i<=n;++i){
		multiset<int>::iterator c=s.lower_bound(n-a[i]);
		if (c==s.end())
			c=s.begin();
		printf("%d ",((*c)+a[i])%n);
		s.erase(c);
	}
	return 0;
}
  1. 1154E:给n个1-n之间的能力值和k,两个教练依次挑人。每次选择当前能力值最强的人,及左边k个和右边k个共2k+1个人进入他的队伍(如果没有k个,就到头为止)。两个教练依次这样做直到没人为止。输出每个人最终的队伍。(nleq 2e5)

思路:对于这种带删除但仍然求左右两边人的问题,可以用链表来记录和删除。不能用set,因为set的左右是排序之后的左右,不是原顺序。再之后利用线段树维护最大值即可。

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
using namespace std;
const int M=2e5+20;
int ans[M],n,k,a[M];
struct Node{
	int l,r;
};
struct List{
	Node list[M];
	int num;
	inline void init(int n){
		for(int i=1;i<=n;++i)
			list[i].l=i-1,list[i].r=i+1;
		list[n].r=-1,num=n;
	}
	inline void del(int x){
		list[list[x].l].r=list[x].r,list[list[x].r].l=list[x].l,--num;
	}
}l;
int v[M*4];
inline void pushup(int id){
	v[id]=max(v[id*2],v[id*2+1]);
}
void build(int id,int l,int r){
	if (l==r){
		v[id]=a[l];
		return;
	}
	build(id*2,l,mid),build(id*2+1,mid+1,r);
	pushup(id);
}
int query(int id,int l,int r){
	if (l==r)
		return l;
	if (v[id*2]>v[id*2+1])
		return query(id*2,l,mid);
	else
		return query(id*2+1,mid+1,r);
}
void operate(int id,int l,int r,int pos,int x){
	if (l==r){
		v[id]=x;
		return;
	}
	if (pos<=mid)
		operate(id*2,l,mid,pos,x);
	else
		operate(id*2+1,mid+1,r,pos,x);
	pushup(id);
}
int main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;++i)
		scanf("%d",&a[i]);
	build(1,1,n);
	int t=2;
	l.init(n);
	while(l.num){
		t=(t==1?2:1);
		int c=query(1,1,n),ca=k;
		while(ca&&l.list[c].l)
			--ca,ans[l.list[c].l]=t,operate(1,1,n,l.list[c].l,0),l.del(l.list[c].l);
		ca=k;
		while(ca&&l.list[c].r!=-1)
			--ca,ans[l.list[c].r]=t,operate(1,1,n,l.list[c].r,0),l.del(l.list[c].r);
		ans[c]=t,operate(1,1,n,c,0),l.del(c);
	}
	for(int i=1;i<=n;++i)
		printf("%d",ans[i]);
	return 0;
}
  1. 1083A:https://codeforces.com/problemset/problem/1083/A

给定一棵树,每个点有权值,每个边也有权值,求一条路径,使得路径上点权和-边权和最大。

思路:树形DP,首先随便指定一个根,mxp[i]=j表示以i为根的子树中,以i为一个端点的路径最大值为j。则显然mxp[i]可以从他的所有儿子转移过来。

再之后,考虑每个节点i,考虑i作为两端点的lca的路径。即以i为根的子树内的路径最大值。显然是要求所有儿子中最大的两个mxp[i]-边权,之后val[i]+mx1+mx2即为当前子树的最优值,取所有值中的最大即为答案。

时间复杂度(O(m))

#include<bits/stdc++.h>
using namespace std;
#define db(x) cout<<#x<<":"<<x<<endl;
const int M=1e6+20;
#define LL long long
struct Edge{
	int to,w,next;
}edge[M];
int head[M],cnt,fa[M];
inline void add(int u,int v,int w){
	edge[++cnt].to=v,
	edge[cnt].w=w,
	edge[cnt].next=head[u],
	head[u]=cnt;
}
LL mxp[M];
int val[M];
LL dfs1(int now){
	mxp[now]=val[now];
	for(int i=head[now];i;i=edge[i].next){
		if (edge[i].to==fa[now])
			continue;
		fa[edge[i].to]=now,
		mxp[now]=max(mxp[now],val[now]-edge[i].w+dfs1(edge[i].to));
	}
	return mxp[now];
}
int main(){
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;++i)
		scanf("%d",&val[i]);
	for(int i=1;i<=n-1;++i){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w),add(u,v,w),add(v,u,w);
	}
	dfs1(1);
	LL ans=0;
	for(int i=1;i<=n;++i){
		LL mx1=0,mx2=0;
		for(int j=head[i];j;j=edge[j].next){
			if (edge[j].to==fa[i])
				continue;
			LL c=mxp[edge[j].to]-edge[j].w;
			if (c>mx1)
				mx2=mx1,mx1=c;
			else if (c>mx2)
				mx2=c;
		}
		ans=max(ans,val[i]+mx1+mx2);
	}
	printf("%lld
",ans);
	return 0;
}

图论

因为需要学链式前向星存树,于是就顺便学了一下spfa。

  1. P3371:spfa模板题

此处使用链式前向星。

#include<bits/stdc++.h>
using namespace std;
const int M=5e5+20,INF=2147483647;
struct Edge{
	int to,w,next;
}edge[M];
int head[M],cnt;
inline void add(int u,int v,int w){
	edge[++cnt].to=v,
	edge[cnt].w=w,
	edge[cnt].next=head[u],
	head[u]=cnt;
}
int n,dist[M],vis[M];
inline void spfa(int start){
	queue<int> q;
	for(int i=1;i<=n;++i)
		dist[i]=INF,vis[i]=0;
	dist[start]=0,q.push(start),vis[start]=1;
	while(!q.empty()){
		int u=q.front();
		q.pop(),vis[u]=0;
		for(int i=head[u];i;i=edge[i].next){
			int v=edge[i].to;
			if (dist[v]>dist[u]+edge[i].w){
				dist[v]=dist[u]+edge[i].w;
				if (!vis[v])
					vis[v]=1,q.push(v);
			}
		}
	}
}
int main(){
	int m,s;
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=m;++i){
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w),add(u,v,w);
	}
	spfa(s);
	for(int i=1;i<=n;++i)
		printf("%d ",dist[i]);
	putchar('
');
	return 0;
}

总结

今天就先到这里吧,做了好多题。本来还看了LIS,不打算写了,干点正事。

原文地址:https://www.cnblogs.com/diorvh/p/12052220.html