20230707-NOIP模拟赛(多校联训)

发布时间 2023-07-08 09:54:44作者: H_W_Y

20230707

T1.信号传输(signal)

考场思路

先把这\(n+k+1\)个点都转化到平面直角坐标系上面

又是没有想清楚就开始打代码(但至少比昨天好,懂得放弃)
本来想的是按照x轴从左到右扫一遍
每一次处理这一列上的每个点
复杂度是\(O(n)\)
但是后面想到有可能信号是从后面的点传送过来的
所以我又再从右到左扫一遍
最后测试样例时发现这个想法是错误的
如果要全部遍历完需要不断地扫
因为这道题每条边的代价都是1

所以我果断放弃
在点之间建边,复杂度\(O(n^2)\)
用digstra打了30分的暴力

Solution

发现对于横坐标或者纵坐标相等的点
再那一条线上是可以随便传到任意一个点上的
所以或许可以把再这样一条平行于坐标轴的点看做一个整体
而每个点的作用就是转弯90度
也就是把它所在的两条直线(分别平行于x轴和y轴)
建立一条边连起来
而对于最后的\(k\)个点,就不用建边
这样在查询时讨论信号是从哪条直线来即可

这样就相当于在我的暴力上面进行优化
把点数和边数都减小了

#include <bits/stdc++.h>
using namespace std;

const int maxn=1e6+10,inf=0x3f3f3f3f,mx=2e6+1;
int n,k,m,head[maxn<<2],tot=0,dis[maxn<<2],x;
bool vis[maxn<<2];
struct edge{
  int v,nxt,val;
}e[maxn<<3];
struct node{
  int x,y;
}a[maxn];

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
}

void add(int u,int v){
  dis[u]=dis[v]=inf;
  e[++tot]=(edge){v,head[u],1};
  head[u]=tot;
  e[++tot]=(edge){u,head[v],1};
  head[v]=tot;
}

priority_queue<pair<int,int> > q;
void dijstra(){
  memset(vis,false,sizeof(vis));
  dis[a[0].x]=0;dis[a[0].y+mx]=0;
  q.push(make_pair(0,a[0].x));
  q.push(make_pair(0,a[0].y+mx));
  while(!q.empty()){
  	int u=q.top().second;q.pop();
  	if(vis[u]) continue;
  	vis[u]=true;
  	for(int i=head[u];i;i=e[i].nxt){
  	  int v=e[i].v,val=e[i].val;
	  if(dis[v]>dis[u]+val){
	  	dis[v]=dis[u]+val;
	  	q.push(make_pair(-dis[v],v));
	  }	
	}
  }
}

int main(){
  freopen("signal.in","r",stdin);
  freopen("signal.out","w",stdout);
  memset(dis,-1,sizeof(dis));
  n=read();k=read();
  for(int i=0;i<=n;i++){
  	a[i].x=read();a[i].y=read();
    add(a[i].x,a[i].y+mx);
  }
  for(int i=1;i<=k;i++) a[i+n].x=read(),a[i+n].y=read();
  dijstra();
  m=read();
  for(int i=1;i<=m;i++){
  	x=read();int ans=inf;
  	if(dis[a[x].x]==-1&&dis[a[x].y+mx]==-1) printf("-1\n");
  	else{
  	  if(dis[a[x].x]!=-1) ans=min(ans,dis[a[x].x]);
  	  if(dis[a[x].y+mx]!=-1) ans=min(ans,dis[a[x].y+mx]);
  	  printf("%d\n",ans+2);
	}
  }
  return 0;
}

注意dijstra复杂度:\(O(m log n)\)

T2.重排序列(permutation)

考场思路

把题意理解错的还得是我
可能还是因为没有想清楚
然后写了一个自认为的网络流的60分的暴力
打完了才发现题目要求是字典序最大
而不是让你序列总和最大

于是我就又打了个\(O(n^2)\)的暴力
结果答案错误就只有10分

Solution

原题CF313E

首先在读入的时候取模(这个不用说都知道)
然后,我们得到的\(c_i\)就有两种情况
\(a[i]+b[j] \lt m\)或者是\(a[i]+b[j]-m\)
很显然,第二种是不划算的
那么我们就考虑让第一种越多越好

我们将\(a,b\)数组从小到大排序
再从小到大去枚举\(a\)
我们去找与它加起来最大的\(b[j]\)使两者相加小于\(m\)
那这样找到的\(b[j]\)一定是一段后缀
因为\(b[j]\)要么不匹配,要么和比\(a[i]\)更小的匹配
这样肯定都不是最优

但是按照这样实现就在\(a[i]\)\(a[i-1]\)淘汰之后
我们没有办法再去找\(a[i-1]\)的匹配
所以这样写正确性是不能保证的(大样例过不了)

所以我们想要从大到小枚举数组\(b\)
然后找到第一个与它相加\(\lt m\)\(a[i]\)
所以我们贪心地去匹配就可以了

而对于最后剩下来的
就是他们相加大于\(m\)的了
那我们就让大的和大的匹配从而让字典序更大即可

说起来确实听简单,但是想不到啊……

#include <bits/stdc++.h>
using namespace std;

const int maxn=5e5+10;
int n,mod,a[maxn],b[maxn],c[maxn],cnt=0,it;
bool vis[maxn];

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
} 

multiset<int> st;

int main(){
  freopen("permutation.in","r",stdin);
  freopen("permutation.out","w",stdout);
  n=read();mod=read();
  for(int i=1;i<=n;i++) a[i]=read(),a[i]%=mod,st.insert(a[i]);
  for(int i=1;i<=n;i++) b[i]=read(),b[i]%=mod;
  sort(b+1,b+n+1);
  for(int i=n;i>=1;i--){
  	multiset<int>::iterator it=st.lower_bound(mod-b[i]);
  	if(it==st.begin()) continue;
	c[++cnt]=b[i]+(*--it);
  	st.erase(it);vis[i]=true;
  }
  for(int i=1;i<=n;i++){
  	if(vis[i]) continue;
  	c[++cnt]=b[i]+*st.begin()-mod;
  	st.erase(st.begin());
  }
  sort(c+1,c+cnt+1);
  for(int i=cnt;i>=1;i--) printf("%d ",c[i]);
  printf("\n");
  return 0;
}

T3.价格调查(survey)

考场思路

写了一个\(n^3\)的dp
想要骗个10分但是写挂了

Solution

原题ARC159D

把题意转化就是一个难点
就如原题里面说的
每一次我们加入\(l \sim r\)的每一个元素
最后求最长上升子序列

我们考虑选择一个区间
就一定要选它的右端点,否则不优
所以说不同的右端点只有\(n\)

所以令\(dp_i\)表示以\(i\)结尾的最长上升子序列
那么在加入区间\([l,r]\)时,我们只用更新\(dp_r\)

这样就可以分两种情况:
\(i \lt l,dp_r=dp_i+r-l+1\)
\(l \le i \lt r,dp_r=dp_i+r-i\)

这样的\(dp\)很显然是\(O(n^2)\)
所以我们考虑当我们选\(dp_i\)时一定是选的小于\(l\)中最大的
我们可以用线段树来维护
同理,对于第二种情况的\(dp_i-i\)也可以用线段树来维护

这道题的关键就在于分析到必须要选右端点
否则肯定不是最优的

#include <bits/stdc++.h>
using namespace std;
#define mid L+((R-L)>>1)
#define lson L,mid,rt<<1
#define rson mid+1,R,rt<<1|1
#define lb(x) lower_bound(a+1,a+len+1,x)-a

const int maxn=2e5+10,inf=0x3f3f3f3f;
int a[maxn<<1],l[maxn],r[maxn],n,dp[maxn<<1],tr[2][maxn<<3],len,ans=0;

int read(){
  int x=0,f=1;char ch=getchar();
  while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
  while(isdigit(ch)){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
  return x*f;
} 

void pushup(int u,int rt){tr[u][rt]=max(tr[u][rt<<1],tr[u][rt<<1|1]);}

void update(int u,int x,int val,int L=1,int R=len,int rt=1){
  if(L==R){
  	tr[u][rt]=val;
  	return ;
  }
  if(x<=mid) update(u,x,val,lson);
  else update(u,x,val,rson);
  pushup(u,rt);
}

int query(int u,int x,int y,int L=1,int R=len,int rt=1){
  if(x>y) return 0;
  if(x<=L&&y>=R) return tr[u][rt];
  int res=-inf;
  if(x<=mid) res=max(res,query(u,x,y,lson));
  if(y>mid) res=max(res,query(u,x,y,rson));
  return res;
}

void build(int L=1,int R=len,int rt=1){
  tr[0][rt]=0;
  if(L==R){
  	tr[1][rt]=-a[L];
  	return;
  }
  build(lson);build(rson);
  pushup(1,rt);
}

int main(){
  freopen("survey.in","r",stdin);
  freopen("survey.out","w",stdout);
  n=read();
  for(int i=1;i<=n;i++) l[i]=read(),r[i]=read(),a[i]=l[i],a[i+n]=r[i];
  sort(a+1,a+n*2+1);
  len=unique(a+1,a+n*2+1)-a-1;
  build();
  for(int i=1;i<=n;i++){
  	int p=lb(r[i]),p1=lb(l[i]);
  	dp[p]=max(query(0,1,p1-1)+r[i]-l[i]+1,query(1,p1,p)+r[i]);
  	update(0,p,dp[p]);update(1,p,dp[p]-r[i]);
  	
  }
  for(int i=1;i<=len;i++) ans=max(ans,dp[i]);
  printf("%d\n",ans);
  return 0;
}

T4.哑剧(mime)

考场思路

用了一个数组,和一棵线段树
甚至不是可持久化
乱搞\(O(n^2 log n)\)
大样例都没过还有50分

Solution

还是没看懂
好像用了分块

总结

要学会对题目的特殊性质进行分析
不断优化解题过程,可以尝试合并之类的做法