9.16集训小结

刚被搜索给暴揍了之后,又来挨数据结构的打QAQTwT

昨天饱受TLE之快,今天尽观RE之美

T1:

T1http://118.31.67.228/problem.php?cid=1025&pid=0icon-default.png?t=L892http://118.31.67.228/problem.php?cid=1025&pid=0

样例输入:

5 10
2 3 7
1 3
2 4 7
3 3 6 10
7 1 2 3 4 7 8 9
6
0 3 3 4 5
0 3 1 3 4
1 2 1 3
0 1 5
1 1 2
1 2 3 5

样例输出:

5
1 2 5 8 9
7
5 6 10
3
4 7

题解:

         这道要吸取大教训,set不要拿来当成数组用,只能拿来去重用,要不然会莫名其妙的WA

并且相当玄虚,DE不出BUG

#include<cstdio>
#include<set>
#include<cstring>
#include<string>
#define itat set<int>::iterator

using namespace std;

int n,m,k;
int x;
int ma[300][300][2];
int p[300][2];
int sai[300];

set<int>st;

int main(void)
{
	scanf("%d%d",&n,&m);
	int x;
	for(int i=1;i<=n;i++){
		scanf("%d",&p[i][0]);
		st.clear();
		for(int j=1;j<=p[i][0];j++){
			scanf("%d",&x);
			st.insert(x);
		}
		p[i][0]=p[i][1]=0;
		bool w[100005]={};
		for(itat it=st.begin();it!=st.end();it++){
			p[i][0]++;
			ma[i][p[i][0]][0]=*it;
			w[*it]=1;
		}
		for(int j=1;j<=m;j++){
			if(w[j]==0){
				p[i][1]++;
				ma[i][p[i][1]][1]=j;
			}
		}
	}
	scanf("%d",&k);
	int le,re;
	while(k--){
		scanf("%d%d",&le,&re);
		memset(sai,0,sizeof(sai));
		st.clear();
		for(int i=1;i<=re;i++){
			scanf("%d",&x);
			st.insert(x);
		}
		re=0;
		for(itat i=st.begin();i!=st.end();i++){
			re++;
			sai[re]=*i;
		}
		if(le==0){
			bool w[10005]={};
			for(int i=1;i<=re;i++){
				for(int l=1;l<=p[sai[i]][0];l++){
					w[ma[sai[i]][l][0]]=1;
				}
			}
			for(int i=1;i<=m;i++){
				if(w[i]==0)printf("%d ",i);
			}
		}
		else{
			bool w[10005]={};
			for(int i=1;i<=re;i++){
				for(int l=1;l<=p[sai[i]][1];l++){
					w[ma[sai[i]][l][1]]=1;
				}
			}
			for(int i=1;i<=m;i++){
				if(w[i]==0)printf("%d ",i);
			}
		}
		putchar('\n');
	}
}

T2:

T2icon-default.png?t=L892http://118.31.67.228/problem.php?cid=1025&pid=1

样例输入:

3
1 2 5
2 4 7

样例输出:

3
4
5

题解:

        以n=3为例,下表是两两配对的所有情况:

ABC
1a[1]+b[1]a[1]+b[2]a[1]+b[3]
2a[2]+b[1]a[2]+b[2]a[2]+b[3]
3a[3]+b[1]a[3]+b[2]a[3]+b[3]

 

        上表中(i,j)格子中的数一定小于等于(i,j+1),因为只有a[i]是不变的只有b[j]进行变化,而b[]是有序的
因为表中每一行都是有序的,因此第1小的数一定出现在第1列里面:

 

ABC
1a[1]+b[1]a[1]+b[2]a[1]+b[3]
2a[2]+b[1]a[2]+b[2]a[2]+b[3]
3a[3]+b[1]a[3]+b[2]a[3]+b[3]

        假设第1小的数为(1,1),记录一下答案,现在要求第2小的,如何求出第二小的呢?
可以仿照上面的想法,因为当前最小的一定是每行最左边的那一列里面最小的,现在去除了(1,1)
那么第1行最左边就是(1,2)了,如下图,对这三个数取min就是第2小的数:

ABC
1a[1]+b[1]a[1]+b[2]a[1]+b[3]
2a[2]+b[1]a[2]+b[2]a[2]+b[3]
3a[3]+b[1]a[3]+b[2]a[3]+b[3]

 

        不断进行类似操作直到取出n个数即可。
动态取min的操作可以用小根堆实现。
小根堆的每个节点记录{值,横坐标,纵坐标}

总结:
        根据表的性质可以发现当前最小一定是表中每行最左边一列的所有数中的最小值
每次取出一个数的时候,就将这个数在表中的右边一个数加入待选队伍
每次取出的最小值就是当前最小值。—— live4m

#include<cstdio>
#include<queue>

using namespace std;

struct Node{
	long long val;
	int i,j;
};

int n;
long long a[400100],b[400100];
priority_queue<Node>q;

inline bool operator <(Node xx,Node yy)
{
	return xx.val>yy.val;
}

int main(void)
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for(int i=1;i<=n;i++)scanf("%lld",&b[i]);
	for(int i=1;i<=n;i++){
		q.push((Node){a[i]+b[1],i,1});
	}
	for(int i=1;i<=n;i++){
		printf("%lld\n",q.top().val);
		int x=q.top().i,y=q.top().j;
		q.pop();
		if(y+1<=n){
			q.push((Node){a[x]+b[y+1],x,y+1});
		}
	}
}

T4:

T4icon-default.png?t=L892http://118.31.67.228/problem.php?cid=1025&pid=3

样例输入:

10 20
0 1 10
1 1 4
0 6 6
1 4 10
1 8 9
1 4 9
0 10 2
1 1 8
0 2 10
1 3 9
0 7 8
0 3 10
0 1 1
1 3 8
1 6 9
0 5 5
1 1 8
0 4 2
1 2 8
0 1 1

样例输出:

10
6
0
6
16
6
24
14
50
41

题解:

        线段树不多说(树状数组也可以)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#define ls (x<<1)
#define rs (x<<1|1)

using namespace std;

struct Node{
	int l,r;
	long long sum,lazy;
}seg_tr[1000010*4];

inline void build(int x,int l,int r)
{
	seg_tr[x].l=l;seg_tr[x].r=r;
	if(l==r){
		seg_tr[x].lazy=seg_tr[x].sum=0;
		return ;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
}

inline void pushdown(int x)
{
	if(seg_tr[x].lazy==0)return ;
	seg_tr[ls].sum+=seg_tr[x].lazy*(seg_tr[ls].r-seg_tr[ls].l+1);
	seg_tr[ls].lazy+=seg_tr[x].lazy;
	
	seg_tr[rs].sum+=seg_tr[x].lazy*(seg_tr[rs].r-seg_tr[rs].l+1);
	seg_tr[rs].lazy+=seg_tr[x].lazy;
	
	seg_tr[x].lazy=0;
}

inline void updata(int x,int l,int r,long long vv)
{
	if(seg_tr[x].r<l || seg_tr[x].l>r)
		return;
	if(seg_tr[x].l>=l && seg_tr[x].r<=r){
		seg_tr[x].sum+=vv;
		seg_tr[x].lazy+=vv;
		return;
	}
	pushdown(x);
	updata(ls,l,r,vv);updata(rs,l,r,vv);
	seg_tr[x].sum=seg_tr[ls].sum+seg_tr[rs].sum;
}

inline long long query(int x,int l,int r)
{
	if(seg_tr[x].r<l || seg_tr[x].l>r)
		return 0;
	if(seg_tr[x].l>=l && seg_tr[x].r<=r)
		return seg_tr[x].sum;
	pushdown(x);
	return query(ls,l,r)+query(rs,l,r);
}

int n,m;

int main(void)
{
	scanf("%d%d",&n,&m);
	build(1,1,n);
	int k,a;
	long long b;
	while(m--){
		scanf("%d%d%lld",&k,&a,&b);
		if(k==0){
			updata(1,a,a,b);
		}
		if(k==1){
			printf("%lld\n",query(1,a,(int)b));
		}
	}
}

T5:

T5icon-default.png?t=L892http://118.31.67.228/problem.php?cid=1025&pid=4

样例输入:

5
5 3 4 1 2

样例输出:

1 2 3 4 5

题解:

        排序(我都不知道说些什么,老老实实写堆(其实该写sort)

#include<cstdio>
#include<queue>

using namespace std;

int n;
long long x;

priority_queue<long long>q;

int main(void)
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&x);
		q.push(-x);
	}
	while(!q.empty()){
		printf("%lld ",-q.top());
		q.pop();
	}
}

T6:

T6icon-default.png?t=L892http://118.31.67.228/problem.php?cid=1025&pid=5

样例输入:

6
3
6
0
9
6
3

样例输出:

2

题解:

        考试的时候感觉被set附体了,想的用set插入,如果插入后所在的位置不是队头或者队尾的话答案加一,但是这肯定是错的。

        所以WA了之后,晓得了正解(这是一句废话

        这道题目其实和双端队列没有什么关系.这道题目的解题思路大致如下:首先我们可以敏锐地找出两个性质.
        双端队列内部的所有数,一定满足单调性,也就是排好序了
所有的双端队列排在一起,肯定是满足单调性,也就是也都排好序了
        接着来思考,我们发现正面思考非常地难,所以我们反向思考问题,把一个A[1,n]的已经拍好顺序的序列,分成尽量少的段,然后每一段都是一个双端队列.
        然后我们不妨,开一个数组也就是B数组,记录原数组A的每一个数的下标,然后我们发现如果B的一段满足单峰性质,也就是书上更为确切的单谷性质,那么这一段就满足条件.因为单谷性质,前半段可以视为从队头加入,后半段视为从队尾加入.
        特殊情况是,如果有重复的点,也就是值相同的几个点.这个特别难以理解,个人觉得.比较容易地理解就是,将这个值一样的点缩点.缩成一个点就好了,反正我们只要队列的个数,不要方案,而且只要满足单调性就好,记住不是严格单调性。 ゞ小马郎君

#include<cstdio>
#include<algorithm>

using namespace std;

int n;
pair<long long ,long long> val[2000100];
long long ans,x;

int main(void)
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&x);
		val[i]=make_pair(x,i);
	}
	sort(val+1,val+n+1);
	int dic=-1,last=n+2;
	ans=1;
	for(int i=1;i<=n;){
		int j=i;
		while(j<=n && val[i].first==val[j].first)j++;
		int minn=val[i].second;
		int maxx=val[j-1].second;
		if(dic==-1){
			if(last>maxx){
				last=minn;
			}else{
				dic=1;
				last=maxx;
			}
		}else{
			if(last<minn){
				last=maxx;
			}else{
				ans++;
				last=minn;
				dic=-1;
			}
		}
		i=j;
	}
	printf("%lld\n",ans);
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值