zoukankan      html  css  js  c++  java
  • 【赛时总结】NOIP2018-三校联考1024

    ◇NOIP三校联考-1024◇

    发现以前的博客写得似乎都很水……基本上都没什么阅读量QwQ 决定改过自新╰( ̄ω ̄o) 就从这篇博客开始吧~

    现场考得无地自容,看到题解才发现一些东西……(我第三题还没有做出来,反正做出来再补上)

     


     

    ◊ 题目& 简单解析

    第一题:组合

    【题目】

    有n条线段,线段的两端点各有一个值(线段两端值可以相同,也可以存在端点值相同的多条线段)。如果线段A的端点的值为(a,b),线段B的端点的值为(b,c),则可以通过将A,B相接,使AB构成一条端点值为(a,c)的线段。求是否存在一种按顺序连接线段的方案,使得所有线段连成一条线段。

    另外给出一个参数T,如果T=1,则可以将线段调转方向,即线段 (a,b) 可以看成 (b,a);如果T=2,则线段不能调转方向。

    输入:第一行给出T和n,m,n表示线段端点的值∈[1,n],m表示共有m条线段;接下来m行每行描述一条线段(a[i],b[i])

    输出:第一行输出"YES"/"NO"表示是否存在方案;若为"YES"输出任意一组方案(按连接顺序输出线段编号,若线段连接时调转方向,则输出其编号的相反数)。

    【分析】

    由于每条线段只能使用一次且必须使用,但是端点值可以重复多次,若将线段(a,b)看作连接a,b的边,则问题转变为求一条欧拉路径(不一定是欧拉环),而T描述的是边是有向边还是无向边(可调转方向则是无向边)。根据欧拉路径的性质——若为无向图,则度数为奇数的点的个数不能超过2,如果存在度数为奇数的点,则必须以某一个度数为奇数的点作为欧拉路径;若为有向图,则出度不等于入度的点的个数不能超过2,且如果存在入度大于出度的点,则必须以该点作为欧拉路径的起点。

    根据上述特征先建图。然后判断点的度数,同时确定起点(如果无法找到起点,即上述的两种特殊情况都不存在,则形成的是欧拉回路,所以可以将任意一点作为起点(注意不要直接把1作为起点,因为数据并没有保证点1~n都出现过) 本地检测的时候SpecialJudge写得丑,然后检测器碰到这种情况自己炸了(lll¬ω¬))。然后就用到了我考试过后才学的Hierholzer算法,专门拿来求欧拉路径/欧拉回路——如果图上存在欧拉回路,则求得的就是欧拉回路,否则求得的是欧拉路径。

    Hierholzer算法大概就是从欧拉路径的起点出发,DFS选择一条没有走过的边继续走,直到不能走(没有边或者边都走过)为止,当DFS回溯时,将该边入栈。最后将栈内的所有边出栈就可以得到一条欧拉路径。

    当然这样求到的是图中最大的一条欧拉路径,如果欧拉路径的边数没有达到m,即没有走过图中所有的边,则输出"NO";比如 (↔表示连接)"1↔2,2↔1,3↔4,4↔3"显然无法走完整个图。

    第二题:统计

    【题目】

    给出一个长度为n的序列a[i],对序列进行m次操作,每次操作指定一个下标i∈(1,n),对于每一个j | j≥i且a[j]≤a[i] ,将a[j]从原数组中提出(原数组中a[j]的位置留空),再对所有满足条件的a[j]单独排序,最后按顺序放入原数组的空位中。问在操作前和进行操作后数组中逆序对的数量。

    eg: a = {1,3,4,2,6,1} →  (i=2) →  a[j] = {3(a[2]),2(a[4]),1(a[6])} →  排序 →  a[j]={1,2,3} →  放入空位 →  a = {1,1,4,2,6,3}

    【分析】

    众所周知,求逆序对除了用归并排序,还可以用树状数组。根据树状数组,我们可以求出f[i],表示在i后面的小于a[i]的数的个数(即倒序从n到1插入a[i],再询问小于a[i]的数的个数)。设操作中被选中的元素的下标集合为 pos(pos升序排列),则对于 j∈pos ,以a[j]作为较大值的逆序对仅存在 ( a[j] , a[k]|k∈pos且k>j ) ,应该很好理解吧,就不多做解释了。因此在对a[j]排序后,对于每一个 i∈pos,就不存在以a[i]为较大值的逆序对了。因此它对答案的贡献就减少了f[i],但是它的改变不会对以其他元素为较大值的逆序对的数量产生影响。

    为什么不产生影响?做一个简单的解释:假设选中a[j]的j的最小值为L。

    ①对于L之前的数a[i],a[j]排序后仍然在a[i]的后面,且a[i]与a[j]的相对大小没有改变,因此数量不会改变;

    ②对于L~n之间的数a[i],满足 a[i]>a[j](操作的要求),虽然排序后a[j]的位置改变,但是仍然小于a[i],且数目不变,因此数量不会改变;

    举个例子:

    好了,扯到贡献了。那么就相当于每次操作后,被操作的a[j]对逆序对的贡献(以a[j]为较大值的逆序对的数量)就变为0了。可以看成把a[j]删除,但是只是删去a[j]的贡献,而在统计其他数的逆序对的时候需要统计a[j]。记最初序列中逆序对的数量为sum,那么我们每进行一次操作,就需要执行 sum-=f[j](减去a[j]的贡献),顺便删除a[j]。

    删除?双向链表!从选中的下标i出发,按链表顺序遍历j,如果a[j]<=a[i],则将j的前驱接上j的后继(删除),将sum-=f[j],给f[j]赋值为0。感觉是正解对吧 QwQ?然后就发现被某chuichui tly加的一组特殊数据卡掉了……%%%

    无奈写正解,好吧,其实是线段树!用线段树维护区间最小值——如果区间[i,n]的最小值都大于a[i]的话,那么这个区间就不需要操作,否则查找子区间,直到找到叶节点,就找到了小于等于a[i]的a[j],然后将a[j]改为INF,sum-=f[j]。这样虽然和链表的思路是一样的……但是时间复杂度就由 O(n) 变成了 O(log n)!挺优秀的……(●'◡'●)

    (第三题还没做出来,太弱了……好了好了,粘代码了)

     


     ◊ 源代码

    【第一题-merge】

    /*Lucky_Glass*/
    #include<bits/stdc++.h>
    using namespace std;
    const int N=int(2e5),M=int(1e5);
    int tag,m,n,beg,cnt;
    int tot[M+5],ans[N+5];
    bool vis[N+5];
    struct LINK{int v,id;};
    vector< LINK > lnk[M+5];
    void DFS(int u,int id){
    	for(int i=0;i<(int)lnk[u].size();i++)
    		if(!vis[abs(lnk[u][i].id)]){
    			vis[abs(lnk[u][i].id)]=true;
    			DFS(lnk[u][i].v,lnk[u][i].id);
    		}
    	if(id) ans[++ans[0]]=id;
    }
    int main(){
    	freopen("merge.in","r",stdin);
    	freopen("merge.out","w",stdout);
    	scanf("%d%d%d",&tag,&m,&n);
    	if(tag==1){
    		for(int i=1;i<=n;i++){
    			int u,v;scanf("%d%d",&u,&v);beg=u;
    			tot[u]^=1;tot[v]^=1;
    			lnk[u].push_back((LINK){v,i});
    			lnk[v].push_back((LINK){u,-i});
    		}
    		for(int i=1;i<=m;i++)
    			if(tot[i])
    				cnt++,beg=i;
    		if(cnt>2)
    			printf("NO
    "),exit(0);
    		DFS(beg,0);
    	}
    	else{
    		for(int i=1;i<=n;i++){
    			int u,v;scanf("%d%d",&u,&v);beg=v;
    			lnk[u].push_back((LINK){v,i});
    			tot[u]++;tot[v]--;
    		}
    		for(int i=1;i<=m;i++){
    			if(tot[i]){
    				cnt++;
    				if(tot[i]==1) beg=i;
    			}
    		}
    		if(cnt>2)
    			printf("NO
    "),exit(0);
    		DFS(beg,0);
    	}
    	if(ans[0]!=n)
    		printf("NO
    "),exit(0);
    	printf("YES
    ");
    	for(int i=ans[0];i>=1;i--)
    		if(i==1) printf("%d",ans[i]);
    		else printf("%d ",ans[i]);
    	printf("
    ");
    	return 0;
    }
    

    【第二题(原始数据)-count】

    /*Lucky_Glass*/
    #include<bits/stdc++.h>
    using namespace std;
    #define lowbit(x) (x&-x)
    const int N=2e5;
    int n,m;
    long long sum;
    int tre[N+5],num[N+5],fal[N+5],pre[N+5],beh[N+5];
    void Insert(int pos){
    	while(pos<=n)
    		tre[pos]++,
    		pos+=lowbit(pos);
    }
    int Query(int pos){
    	int ret=0;
    	while(pos)
    		ret+=tre[pos],
    		pos-=lowbit(pos);
    	return ret;
    }
    int main(){
    	freopen("count.in","r",stdin);
    	freopen("count.out","w",stdout);
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++){
    		scanf("%d",&num[i]);
    		pre[i]=i-1;
    		beh[i]=i+1;
    	}
    	for(int i=n;i>=1;i--){
    		fal[i]=Query(num[i]-1);
    		sum+=fal[i];
    		Insert(num[i]);
    	}
    	printf("%lld",sum);
    	for(int i=1;i<=m;i++){
    		int pos;scanf("%d",&pos);
    		if(!fal[pos]){
    			printf(" %lld",sum);
    			continue;
    		}
    		for(int j=pos;j<=n;j=beh[j])
    			if(num[j]<=num[pos]){
    				beh[pre[j]]=beh[j];
    				pre[beh[j]]=pre[j];
    				sum-=fal[j];
    				fal[j]=0;
    			}
    		printf(" %lld",sum);
    	}
    	printf("
    ");
    	return 0;
    }
    

      

    【第二题(额外数据)-count】

    /*Lucky_Glass*/
    #include<bits/stdc++.h>
    using namespace std;
    const int N=2e5;
    struct TREEARRAY{
    	#define lowbit(x) (x&-x)
    	int tre[N+5];
    	TREEARRAY(){memset(tre,0,sizeof tre);}
    	void Insert(int pos,int n){
    		while(pos<=n)
    			tre[pos]++,
    			pos+=lowbit(pos);
    	}
    	int Query(int pos){
    		int ret=0;
    		while(pos)
    			ret+=tre[pos],
    			pos-=lowbit(pos);
    		return ret;
    	}
    }ary;
    struct SEGTREE{
    	struct NODE{
    		int l,r,num;
    	}tre[N*5];
    	void Update(int u){
    		tre[u].num=min(tre[u<<1].num,tre[u<<1|1].num);
    	}
    	void Init(int a[],int l,int r,int u){
    		tre[u].l=l;tre[u].r=r;
    		if(l==r){
    			tre[u].num=a[l];
    			return;
    		}
    		int mid=(l+r)>>1;
    		Init(a,l,mid,u<<1);Init(a,mid+1,r,u<<1|1);
    		Update(u);
    	}
    	void Query(int u,int l,int val,long long &sum,int f[]){
    		if(tre[u].r<l || tre[u].num>val) return;
    		if(tre[u].l==tre[u].r){
    			sum-=f[tre[u].l],tre[u].num=(1<<29);
    			return;
    		}
    		int mid=(tre[u].l+tre[u].r)>>1;
    		if(l>=mid+1) Query(u<<1|1,l,val,sum,f);
    		else{
    			Query(u<<1,l,val,sum,f);
    			Query(u<<1|1,l,val,sum,f);
    		}
    		Update(u);
    	}
    }seg;
    int n,m;
    long long sum;
    int f[N+5],num[N+5];
    int main(){
    	freopen("count.in","r",stdin);
    	freopen("count.out","w",stdout);
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)
    		scanf("%d",&num[i]);
    	for(int i=n;i>=1;i--){
    		sum+=(f[i]=ary.Query(num[i]-1));
    		ary.Insert(num[i],n);
    	}
    	seg.Init(num,1,n,1);
    	printf("%lld",sum);
    	for(int i=1;i<=m;i++){
    		int pos;scanf("%d",&pos);
    		seg.Query(1,pos,num[pos],sum,f);
    		printf(" %lld",sum);
    	}
    	return 0;
    }
    

      


     

    The End

    Thanks for reading!

    - Lucky_Glass

    (Tab:如果我有没讲清楚的地方可以直接在邮箱lucky_glass@foxmail.com email我,在周末我会尽量解答并完善博客~)
  • 相关阅读:
    swagger 兼容 docker 转发 配置
    rust 条件编译 Debug Release
    rust-must-know-crates-5ad8 100DayOfRust
    python C# DES 加密转换
    The Little Book of Rust Books
    swiper 禁止滑动
    uniapp自定义凸出的导航栏
    uniapp css实现双排菜单向左滑动
    uniapp开发公众号,微信设置字体大小后,禁止改变页面字体大小
    uniapp接口封装
  • 原文地址:https://www.cnblogs.com/LuckyGlass-blog/p/9849030.html
Copyright © 2011-2022 走看看