2024.10.31模拟赛(*^▽^*)

一定要好好睡觉啊,不然打模拟赛的时候会困死的!!!

非常非常困的7:50时就开始打模拟赛,还是打了四个小时。打了T1、T2的正解,T3的5分特殊样例、T4的10分特殊样例,预计总215分。
然后经过漫长的三个小时的等待,出现了 T1 100分,T2 65分,T3 60分,T4 10分、总分235分 的神奇成绩。虽然结果比预估的高,但我的T2挂了整整35分!这让我非常的不爽。当我发现是因为没开long long时,不开心的心情到达了顶峰————

不然我就可以排名第二了!!

题目小链接


T1【恋曲】

题目大意:

给出n,t,x,与长度为n(1<=n<=1e6)的两个序列a,b。每次操作可以使\(c_{i}=min(c_{i}+b_{i},a_{i})\),最多操作t(1<=t<=1e9)次,问是否可以使\(\Sigma c_{i}>x\)(\(c_{i}\)初始化为0)

解题思路:

感觉可以贪心。每次操作花费代价一定,所以能取大的\(b_{i}\)就取。于是乎,想到用大根堆维护最大值,类型为pair<int,int>,第一个为\(b_{i}\),第二个为可使用的次数。我们注意到,对于每个\(b_{i}\)可以分为两个部分,一部分是每次取\(b_{i}\),一共可取\(\lfloor\frac{a_{i}}{b_{i}}\rfloor\)次;另一部分是取\(a_{i}\)%\(b_{i}\),只可以取一次(总和\(a_{i}\)可以分成大小为\(b_{i}\)的若干整份,但总会剩下分不成\(b_{i}\)的一份),在输入时预处理为两部分、插入大根堆即可。

完了后每次取堆顶,然后操作,判断操作次数与总和的边界输出即可。

可爱的小代码
#incIude <bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,t,x;
int a[N],b[N];
long long sum;
long long tol;
priority_queue < pair<int,int> > q;

void read()
{
	scanf("%d%d%d",&n,&t,&x);
	for (int i=1;i<=n;i++) 
	{
		scanf("%d",&a[i]);
		sum+=a[i];
	}
	for (int i=1;i<=n;i++) 
	{
		scanf("%d",&b[i]);
		if (b[i]>a[i]) b[i]=a[i];//防止溢出 
		q.push(make_pair(b[i],a[i]/b[i]));
		if (a[i]%b[i]) q.push(make_pair(a[i]%b[i],1));
	}
}
int main()
{
	read();
	if (sum<x)//就算全部取到也不够
	{
		printf("No");
		return 0;
	}
	while (!q.empty())
	{
		int bb=q.top().first,dd=q.top().second;
		q.pop();
		tol+=min(dd,t)*bb;
		t-=min(t,dd);
		if (t==0)//边界 
		{
			if (tol>=x) printf("Yes");
			else printf("No");
			break;
		}
		if (tol>=x)//边界 
		{
			printf("Yes");
			break;
		}		
	}
	return 0;
}

T2【留校丕】

题目大意:

给出一个长度为\(n(2\leqslant n\leqslant 10^{6})\)的序列p(保证p是排列),每次操作可以交换p中相邻的两个数,求使p变成单峰的最小操作次数

解题思路:

本来想的p中最大值一定是会是那个“峰”,但显然看最大值很不好操作。通过群中fjj思路的启示,我们想到p中的最小值一定需要在序列两边;又因为每次操作只能交换相邻的两个数,所以移动最小值后序列中的其他数不变(比如序列3 1 2 4 5,不论是将1移到序列左边或是右边,3 2 4 5的序列位置都没有改变)。

于是乎,可以把移动mn看作删除mn(显而易见,mn是指当前序列中的最小值),那么每次移动一定要往移动次数少的一边移动,这样的贪心才可使ans最小。但是因为“删除mn”的操作,序列中数的个数会发生改变,也就是说我们要对这个序列进行单点修改与区间查询,一下子就想到了树状数组。但是,作为一个大蒟蒻,我只能写一个线段树来(还好没被卡)。

但!是!不!要!忘!记!ans!要!开!long!long!

记录一下模拟赛时的各种若至小错误
虚空调试的第一步,题意看错,怒失半小时思考时间
虚空调试的第二步,简单问题复杂化,明明从小到大枚举就行,却仍想着用线段树维护区间最小值,怒失20分钟思考时间
虚空调试的第三步,忘记特判边界,本以为是l==r的问题,结果是l>r的问题,怒调20分钟
于是乎,时间所剩不多、没来得及注意细节,ans忘记开long long
再于是乎,痛失T4的打暴力时间
不可爱的小代码
#incIude <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e6+5;
int n;
int p[N],t[N]; 
int pos[N];
int sum[N*4];
long long ans;

void read()
{
	scanf("%lld",&n);
	for (int i=1;i<=n;i++)
	{
		scanf("%lld",&p[i]);
		pos[p[i]]=i;//记录一下每个值的位置 
	}
}
void pushup(int id)
{
	sum[id]=sum[id*2]+sum[id*2+1];
}
void build(int id,int l,int r)
{
	if (l==r)
	{
		sum[id]=1;
		return ;
	}
	int mid=(l+r)/2;
	build(id*2,l,mid);
	build(id*2+1,mid+1,r);
	pushup(id);
}
int query2(int id,int l,int r,int i,int j)
{
	if (j==0||i==n+1) return 0;//特判目前最小值已在序列边界 不然会死循环 
	if (l>=i&&r<=j) return sum[id];
	int mid=(l+r)/2,sm=0;
	if (i<=mid) sm+=query2(id*2,l,mid,i,j);
	if (mid+1<=j) sm+=query2(id*2+1,mid+1,r,i,j);
	return sm;
}
void del(int id,int l,int r,int x)
{
	if (x==l&&x==r)
	{
		sum[id]-=1;
		return ;
	}
	int mid=(l+r)/2;
	if (x<=mid) del(id*2,l,mid,x);
	if (mid+1<=x) del(id*2+1,mid+1,r,x);
	pushup(id);
}
signed main()
{
	read();
	build(1,1,n);//建树 
	int mn=0;//序列当前最小值 
	for (int i=1;i<=n;i++)
	{
		mn++;
		ans+=min(query2(1,1,n,1,pos[mn]-1),query2(1,1,n,pos[mn]+1,n));//取移到最左边、最右边的更小值 
		del(1,1,n,pos[mn]);
	}
	printf("%lld",ans);
	return 0;
}

T3【移球游戏】

题目大意:

给定n个长度为m的区间\((1\leqslant n,m\leqslant400)\),在n*m+1的位置有一个空位,每次操作可以将任意区间的一个数移到空位,求使每个区间内的数都不相同的操作数与方案(也就是说,当操作完成后,每个区间都应由1,2,…,m-1,m组成)。

解题思路:

作为一个蒟蒻,首先要恶补一下二分图欧拉回路的小知识。

我们可以感性地想象到, 根据数字守恒定律, 在一个区间中多出的值一定会是另一个区间中少的值。进而,我们可以想象到,我们肯定是先从一个区间里拎出来多的值,再塞到另一个少这个值的区间中。那么我们该怎样实现呢?

我们可以:对于一个区间,从它少的值向该区间连边,再从该区间向它多的值连边, 因为多的值也会是少的值,所以 跑一遍这个图就相当于我们上方提到的操作。跑完后我们会发现,它一定会形成环 因为数字守恒定律 ;又因为我们需要把所有的边都跑完,所以这些个环会形成欧拉回路。

当跑完所有的欧拉回路时,这n个区间就一定都合法了。此时,ans1就是欧拉回路的总长度加上欧拉回路的个数(因为每对一个欧拉回路操作,都要先拎出一个值到空位上才能一个个地变),方案就是跑欧拉回路的过程(需要注意图内节点有区间也有点,不要把区间输出来了)。

一大坨代码
#incIude <bits/stdc++.h>
using namespace std;
const int N=405,M=160000;
int n,m;
int fa[N*2];
vector <int> box[N*2][N*2];//桶数组 
vector <int> e[N*2];//图 
int d[N*2];//度数 
int id[N*2];//欧拉回路每条边只能走一遍,走过这条就要走下一条了
int w[N*2][N*2];
int tol;
struct node { int x,y; } ans[N*N];

void read()
{
	int a;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
		{
			scanf("%d",&a);
			box[i][a].push_back((i-1)*m+j);
		}
	}
}
int s[M*2],top;
void build_G()//建图 
{
	for (int i=1;i<=n;i++)
	{
		for (int j=1;j<=m;j++)
		{
			if (box[i][j].size()==0) //少的数 
			{
				e[n+j].push_back(i);
				d[n+j]++; 
			}
			else
			{
				for (int k=0;k<box[i][j].size()-1;k++)//多的个数
				{
					e[i].push_back(n+j);
					d[i]++;
				} 
			}
		}
	} 
}
void dfs(int x)
{
	for (;id[x]<e[x].size();)
	{
		int v=e[x][id[x]++];
		d[x]--;
		dfs(v);
	}
	s[++top]=x;
}
int main()
{
	read();
	build_G();
	for (int i=1;i<=n+m;i++)//点 
	{
		if (d[i])//一个没走过的欧拉回路 
		{
			int space=n*m+1;
			top=0;
			dfs(i);
			for (int j=3;j<=top;j+=2)//在栈中,点、区间交错放入 偶为点,奇为区间 
			{
				ans[++tol]={box[s[j]][s[j-1]-n][w[s[j]][s[j-1]-n]],space};
				space=ans[tol].x;
				w[s[j]][s[j-1]-n]++;//为什么要有w?因为一个区间里可能会多出很多个相同的数,每个数都要拿出去,拿完一个就要拿下一个了
			}
			ans[++tol]={n*m+1,space};//走完一个回路后要把拿出来的数再放回去
		}
	}
	printf("%d\n",tol);
	for (int i=1;i<=tol;i++) printf("%d %d\n",ans[i].x,ans[i].y);
	return 0;
}

小小的东西
突然发现图真是好东西,能把有指向性的抽象东西化成有顺序的形象东西。今天的T3是这样,有一天的T2小恐龙也是。
建图真是个好东西!

posted @ 2024-10-31 18:38  还是沄沄沄  阅读(23)  评论(5编辑  收藏  举报