首页 > 其他分享 >两个有趣的题目

两个有趣的题目

时间:2023-11-15 13:57:51浏览次数:24  
标签:10 ch 两个 int 拓扑 namespace MAXN 题目 有趣

\(\text{Luogu8099}\)

题目描述

现在有一个长度为 \(n\) 的序列 \(\{h\}\) 。定义一次操作为选择两个 相邻 下标 \(i,j\) ,若 \(|h_i-h_j|\leqslant k\) 就交换 \(h_i,h_j\) 。你可以进行上述操作无数次。

问可以得到的最小字典序序列是什么?

\(n \leqslant 10^5\) 。

思路点拨

不论你如何操作,如果两个位置 \(i,j\) 有 \(|h_i-h_j|>k\) 那么 \(h_i,h_j\) 的相对位置关系是不会变得,别的我们显然可以任意变动。最终我们需要在保持若干个相对位置关系的前提下让字典序最小不难想到一个暴力:

  • 对于 \(i,j(i<j)\) ,如果 \(|h_i-h_j|>k\) 我们就让 \(i\) 向 \(j\) 连有向边。

  • 所以一个答案对应了有向图上的一个拓扑序,我们希望这个拓扑序字典序尽可能的小直接将拓扑排序的队列换成堆即可。

时间复杂度上界 \(O(n^2\log n)\) 。

考虑进行优化,我们连边的方式的关系比较特殊,对于节点 \((i,h_i)\) 向 \((j,h_j)\) 连边的前提就是 \(j>i\) 并且 \(h_j \notin [h_i-k,h_i+k]\) 。一开始我们可以简单求出对于一个节点 \(i\) 他在有向图中的入度是多少。

拓扑排序的过程中我们每取出一个节点就将满足 \(j>i\) 并且 \(h_j \notin [h_i-k,h_i+k]\) 的节点入度减一,使用树套树做到 \(O(n \log^2 n)\) ,已经足够通过本题。

我们发现树套树没有必要维护 \(j>i\) 的关系,因为如果对于节点 \(i\) 他在入度的边全部被删除的情况下,存在 \(j<i\) 并且 \(h_j \notin [h_i-k,h_i+k]\) 这个与我们的假设是矛盾的。故没有必要维护下标。时间优化到 \(O(n \log n)\) 。

代码实现时离散化会更加方便。

#include<bits/stdc++.h>
#define int long long
#define lowbit(x) ((x)&(-x)) 
using namespace std;
namespace IO{
	inline int read(){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
		return x*f;
	}
	inline void print(int x){
		if(x>9) print(x/10);
		putchar(x%10+'0');
	}
}
using namespace IO;
const int MAXN=1e5+10,inf=1e9;
int n,k;
int a[MAXN],deg[MAXN],tmp[MAXN];
map<int,int> vis;
int bit[MAXN];
void add(int x){
	for(int i=x;i<=n;i+=lowbit(i)) bit[i]++;
}
int query(int l,int r){
	int cnt=0;
	for(int i=r;i;i-=lowbit(i)) cnt+=bit[i];
	for(int i=l-1;i;i-=lowbit(i)) cnt-=bit[i];
	return cnt;
}
struct node{
	int x,y;
	node(int x_=0,int y_=0){x=x_,y=y_;}
}t[MAXN<<2];
int tag[MAXN<<2];
void pushup(int i){
	if(t[i<<1].y==t[i<<1|1].y)
		t[i]=node(min(t[i<<1].x,t[i<<1|1].x),t[i<<1].y);
	else t[i]=(t[i<<1].y<t[i<<1|1].y)?t[i<<1]:t[i<<1|1];
}
void pushdown(int i){
	t[i<<1].y+=tag[i],t[i<<1|1].y+=tag[i];
	tag[i<<1]+=tag[i],tag[i<<1|1]+=tag[i];
	tag[i]=0;
}
void build(int i,int l,int r){
	if(l==r){
		t[i]=node(l,deg[l]);
		return ;
	} 
	int mid=(l+r)>>1;
	build(i<<1,l,mid),build(i<<1|1,mid+1,r);
	pushup(i); 
}
void update(int i,int l,int r,int L,int R,int w){
	if(l>R||r<L) return ;
	if(L<=l&&r<=R){
		t[i].y+=w,tag[i]+=w;
		return ;
	} 
	int mid=(l+r)>>1;
	pushdown(i);
	update(i<<1,l,mid,L,R,w);
	update(i<<1|1,mid+1,r,L,R,w);
	pushup(i);
}
signed main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++)
		a[i]=tmp[i]=read();
	sort(tmp+1,tmp+n+1);
	for(int i=1;i<=n;i++){
		vis[a[i]]++;
		a[i]=lower_bound(tmp+1,tmp+n+1,a[i])-tmp-1+vis[a[i]];
	}
	for(int i=1;i<=n;i++){
		int w=tmp[a[i]];
		int L=lower_bound(tmp+1,tmp+n+1,w-k)-tmp,R=upper_bound(tmp+1,tmp+n+1,w+k)-tmp-1;
		deg[a[i]]=(i-1)-query(L,R);
		add(a[i]);
	}
	build(1,1,n);
	for(int i=1;i<=n;i++){
		int w=t[1].x;
		print(tmp[w]),putchar('\n');
		int L=lower_bound(tmp+1,tmp+n+1,tmp[w]-k)-tmp,R=upper_bound(tmp+1,tmp+n+1,tmp[w]+k)-tmp-1;
		if(L>1) update(1,1,n,1,L-1,-1);
		if(R<n) update(1,1,n,R+1,n,-1);
		update(1,1,n,w,w,inf);
	}
	return 0;
}

\(\text{Luogu8100}\)

题目描述

现在有一个长度为 \(n\) 的序列 \(\{h\}\) 。定义一次操作为选择两个 相邻 下标 \(i,j\) ,若 \(|h_i-h_j|=1\) 就交换 \(h_i,h_j\) 。你可以进行上述操作无数次。

问可以通过操作得到多少本质不同的序列?

\(n \leqslant 5000\) 。

思路点拨

我们同样按照上述的想法去连边,例如:如果 \(i<j\) 并且 \(|h_i-h_j|\neq 1\) 就让 \(i\) 向 \(j\) 连有向边。我们认为两个元素的 \(h\) 相同也是不可交换的,因为交换了也没有意义,这样会更加方便。

然后答案对应了这个有向图上的一个拓扑序。但是拓扑序计数是npc问题,所以这个图一定是比较特殊的。

题目讲到 \(|a_i-a_j|=1\) ,那么 \(a_i,a_j\) 的奇偶性就不同,最终对于同种奇偶的数而言,他们的相对位置就不会变化,所以这个有向图如果我们只看同奇偶的节点之间的连边等价于一条链,所以这个有向图就是两条链之间连了一些边。

考虑进行动态规划,定义 \(f_{i,j}\) 表示考虑到第 \(1\) 条链的第 \(i\) 个元素,第 \(2\) 条链的第 \(j\) 个元素,拓扑序的数量是多少?我们每一次转移的例如向 \(f_{i+1,j}\) 转移就看第 \(2\) 条链中向 \(i+1\) 连边的节点是否都小于等于 \(j\) 即可;向 \(f_{i,j+1}\) 转移类似。

#include<bits/stdc++.h>
#define int long long
using namespace std;
namespace IO{
	inline int read(){
		int x=0,f=1;char ch=getchar();
		while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}
		while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
		return x*f;
	}
	inline void print(int x){
		if(x>9) print(x/10);
		putchar(x%10+'0');
	}
}
using namespace IO;
const int MAXN=5e3+10,mod=1e9+7;
int T,n,a[MAXN];
int f[MAXN][MAXN],pos[MAXN][2],pre[MAXN]; 
signed main(){
	T=read();
	while(T--){
		n=read();
		for(int i=1;i<=n;i++) a[i]=read();
		int cnt[2]={0,0};
		for(int i=1;i<=n;i++){
			pos[++cnt[a[i]&1]][a[i]&1]=i;
			pre[i]=0;
			for(int j=i-1;j;j--)
				if((a[i]&1)!=(a[j]&1)&&abs(a[i]-a[j])!=1){
					pre[i]=j;
					break;
				}
		} 
		for(int i=0;i<=cnt[0];i++)
			for(int j=0;j<=cnt[1];j++) f[i][j]=0;
		f[0][0]=1;
		for(int i=0;i<=cnt[0];i++)
			for(int j=0;j<=cnt[1];j++){
				if(i<cnt[0]&&pre[pos[i+1][0]]<=pos[j][1])
					f[i+1][j]=(f[i+1][j]+f[i][j])%mod;
				if(j<cnt[1]&&pre[pos[j+1][1]]<=pos[i][0])
					f[i][j+1]=(f[i][j+1]+f[i][j])%mod;
			}
		print(f[cnt[0]][cnt[1]]),putchar('\n');
	}
	return 0;
} 

标签:10,ch,两个,int,拓扑,namespace,MAXN,题目,有趣
From: https://www.cnblogs.com/Diavolo/p/17833647.html

相关文章

  • this的题目,我都是这样理解的,,除了箭头函数,this的指向就看它的直接调用者是谁!而箭头函数
    假设document是HTML文档中的一个节点,点击该节点后会发生什么?functiontest(){this.flag=false;this.change=()=>{this.flag=true;console.log(button.flag);};}constbutton=newtest();document.addEventListener("click",button.change);A......
  • 关于两个实体类之间相同字段的赋值
    1.可以使用以下方法:BeanUtils.copyProperties(one,two)2.相关依赖:<dependency><groupId>commons-beanutils</groupId><artifactId>commons-beanutils</artifactId><version>number</version><!--替换为正确的版本号-->可以是:1.9......
  • js 计算两个地点坐标之间的间距
    /***计算两个地点坐标之间的间距*@param{array}location1[lon:string,lat:string]地点坐标*@param{array}location2[lon:string,lat:string]地点坐标*/exportfunctioncalculateDistance(location1,location2){ constearthRadius=6371//地球半......
  • (链表)04-合并两个排序的链表
    /*publicclassListNode{intval;ListNodenext=null;ListNode(intval){this.val=val;}}*/publicclassSolution{publicListNodeMerge(ListNodelist1,ListNodelist2){//添加头节点ListNoderoot=ne......
  • el-table两个表尾合计行联动同步滚动条代码
    效果图我们先看一下效果图思路获取对应的两个表格设置滚动条的dom,并通过Element.scrollLeft去设置滚动的距离官方文档:滚动容器(审查元素即可得知):完整代码自己演示的话,直接复制粘贴即可,代码中包含注释<template><divclass="kkk"><divclass="myWrap"><el......
  • excel对比两个文档,判断范围内的取值是否在另一个列表内存在(vlookup函数的使用)
    背景:sheet1表为原始数据:sheet2表为新的数据副本,目标是查询sheet2列表中是否存在sheet1表的数据,并且标记出来,且获取sheet2列表的一些数据至sheet1列表中,补充D与E两列的数据情况: 一、vlookup函数介绍:作用:垂直查找(按列号查找)函数说明:vlookup(lookup_value,table_array,col_......
  • 力扣2578 排序后两个数依次选择
    2578. 最小和分割隔空依次取值,相加最小,原理暂不清楚,举例演示就可发现。classSolution{public:intsplitNum(intnum){intnum1=0,num2=0;vector<int>a;while(num){a.push_back(num%10);num/=10;}......
  • 求两个数的最大公约数
    #include<stdio.h>intmain(){ inti,j,n=0; printf("请输入两个数:"); scanf_s("%d,%d",&i,&j);   while(i%j) {   n=i%j;   i=j;   j=n;  } printf("最大公约数为%d",n);  retu......
  • concat()方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数
    下面js数组的方法中,哪些方法不能改变自身数组?ApopBspliceCsortDconcat正确答案:Dpop()方法用于删除数组的最后一个元素,并返回被删除的最后一个元素,这样的话数组就被改变了。splice()方法可以对数组中已经存在元素进行删除,也可以添加元素到数组中。sort()方法对数组中所有......
  • 1.两个数的最大公约数;2.输出某个范围的素数
    给定两个数,求其最大公约数#include<stdio.h>intmain(){ intm=24,n=18,r=0; while(m%n)//辗转相除法,改成"while(r=m%n)",下面的"r=m%n"可以省略 { r=m%n; m=n; n=r; } printf("%d\n",n); return0; }输出100-200内的素数#include<stdio.h>......