Loading

2022.2.11 刷题日记

模拟赛

T1

主要思路是把所有能连的边都连上,然后检查所有必须要连的边有没有连上,然后利用并查集,把所有必须要连的边连上,同时检查有没有环,然后考虑连上剩下的边,最后还需要检查是不是一个连通块。


#include<bits/stdc++.h>
#define ll long long
#define dd double
#define ld long double
#define uint unsigned int
#define ull unsigned long long
#define N 1010
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x){
	x=0;int f=1;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f*=-1;
	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
	x*=f;
}

int a[N][N];

struct Squ{
	int l,r;
}sq[N];

int n;

inline bool Insq(int x,int l,int r){
	return x>=l&&x<=r;
}

int fa[N],Size[N];

typedef pair<int,int> P;
P ans[N];
int tail;

inline int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}

int main(){
	// freopen("data6.in","r",stdin);
	// freopen("tree.out","w",stdout);
	read(n);
	for(int i=1;i<=n;i++){
		read(sq[i].l);read(sq[i].r);
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			if(i==j) continue;
			if(Insq(i,sq[j].l,sq[j].r)&&Insq(j,sq[i].l,sq[i].r)) a[j][i]=a[i][j]=1;
		}
	bool op=1;
	for(int i=1;i<=n;i++){
		if(!(a[i][sq[i].l]&&a[i][sq[i].r])){op=0;break;}
	}
	if(!op){
//		cout<<"here1"<<endl;
		puts("-1");
		return 0;
	}
	for(int i=1;i<=n;i++) fa[i]=i;
	for(int i=1;i<=n;i++) Size[i]=1;
	for(int i=1;i<=n;i++){
		a[i][sq[i].l]=a[sq[i].l][i]=2;
		a[i][sq[i].r]=a[sq[i].r][i]=2;
	}
	// printf("%d\n",a[138][176]);
	op=1;
	for(int i=1;i<=n&&op;i++){
		for(int j=i+1;j<=n&&op;j++){
			if(a[i][j]==2){
				int fai=Find(i),faj=Find(j);
				if(fai==faj){op=0;break;}
				a[i][j]=0;a[j][i]=0;
				ans[++tail]=make_pair(i,j);
				if(Size[fai]>Size[faj]) swap(fai,faj);
				fa[fai]=faj;Size[faj]+=Size[fai];
			}
		}
		if(!op) break;
	}
	// printf("%d\n",a[138][176]);
	if(!op){
//		cout<<"here2"<<endl;
		puts("-1");return 0;
	}
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			if(i==j) continue;
			if(!a[i][j]) continue;
			int fai=Find(i),faj=Find(j);
			if(fai==faj) continue;
			ans[++tail]=make_pair(i,j);
			if(Size[fai]>Size[faj]) swap(fai,faj);
			fa[fai]=faj;Size[faj]+=Size[fai];
		}
	}
	int fa1=Find(1);
	if(Size[fa1]!=n){
//		cout<<"here3"<<endl;
		puts("-1");return 0;
	}
//	assert(tail==n-1);
	for(int i=1;i<=tail;i++){
		printf("%d %d\n",ans[i].first,ans[i].second);
	}
	return 0;
}


T2

我们考虑找到 \(1\) 节点,考虑二节点位置,要么 \(2\) 节点在其父亲节点,要么在其右儿子的一条左链上。我们用一个 Solve(int l,int Lastl) 来表示当前我们的这颗子树,最小的节点编号是 \(l\),上一个最小的节点编号是 \(Lastl\)。然后这个函数返回一个 pair,第一个位置表示这颗子树的根,第二个位置表示这颗子树的最大节点编号。

我们返回刚刚的讨论。对于 \(2\) 节点位置的判断我们可以通过询问 \(query(1,1,1)\) 来得到,如果 \(2\) 是父亲,我们接下来就看 \(3\) 的位置,构造 \(2\) 的右子树。否则,我们就需要构造 \(1\) 的右子树,这就是一个子问题。递归调用函数后更新下一个测试的节点。

注意到我们如何找到当前这颗子树的根,我们每次都询问一下 \(l-1\) 这个节点是否包含 \(Last,now\) 内的所有结点,就可以知道上一个 \(now\) 是不是根了,所以我们要时刻记录 \(now\) 的上一个值。

综上所述,询问次数应该是最大 \(2n-1\)


#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include"interact.h"
using namespace std;
int n;
pair<int,int>solve(int l,int checkl)
{
	int cur=l;
	while(cur<=n)
	{
		int pre=cur;
		if(!query(cur,l,cur))
		{
			pair<int,int>ans=solve(cur+1,l);
			cur=ans.second;
			report(pre,ans.first);
		}
		if(l!=1&&query(l-1,checkl,cur))return make_pair(pre,cur);
		cur++;
		if(cur<=n)report(pre,cur);
	}
	return make_pair(0,0);
}
void guess(int N)
{
	n=N;
	solve(1,0);
}


T3

因为所有度数为 \(k\) 的点不相邻,我们很容易想到了利用二分图来构造,那么让左部点全部是度数为 \(k\) 的点,右部点全部是度数为 \(k+1\) 的点。我们考虑一下左右两边的点数。

显然,如果连完边之后,左边节点都是度数为 \(k\) 右边都是 \(k+1\) 的点是最优的。然而这种情况的出现需要满足 \(2k+1|n\),如果不满足,那么我们就有两种点的方案,我们都试一下就可以知道哪个更优。

值得一提的是,出题人吴清月说下取整或上取整中有一个一定是更优的,但是实测中却是不一定,换句话说,我们需要在一定范围内随机扰动一下。


#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define uint unsigned int
#define ull unsigned long long
#define N 1001000
#define M number
using namespace std;

const int INF=0x3f3f3f3f;

template<typename T> inline void read(T &x) {
	x=0; int f=1;
	char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
	for(;isdigit(c);c=getchar()) x=x*10+c-'0';
	x*=f;
}

int n,k,R,L;

typedef pair<int,int> P;
P ans[N],ans2[N];
int tail,tail2,d[N];

int main(){
	// freopen("my.out","w",stdout);
	read(n);read(k);R=n*k/(2*k+1);L=n-R;
	// printf("L=%d R=%d\n",L,R);
	int now=0;
	for(int i=1;i<=L;i++){
		for(int j=1;j<=k;j++){
			ans[++tail]=make_pair(i,now+1+L);
			now++;now%=R;
		}
	}
	R++;L=n-R;
	// printf("L=%d R=%d\n",L,R);
	now=0;
	for(int i=1;i<=L;i++){
		for(int j=1;j<=k;j++){
			ans2[++tail2]=make_pair(i,now+1+L);
			d[now+1+L]++;
			now++;now%=R;
		}
	}
	while(d[now+1+L]<=k){
		// printf("now=%d\n",now);
		int nxt=now+1;nxt%=R;
		ans2[++tail2]=make_pair(nxt+1+L,now+1+L);d[now+1+L]++;d[nxt+1+L]++;
		now=nxt;now++;now%=R;
	}
	// printf("tail1=%d tail2=%d\n",tail,tail2);
	if(tail<tail2){
		printf("%d\n",tail);
		for(int i=1;i<=tail;i++) printf("%d %d\n",ans[i].first,ans[i].second);
	}
	else{
		printf("%d\n",tail2);
		for(int i=1;i<=tail2;i++) printf("%d %d\n",ans2[i].first,ans2[i].second);
	}
	return 0;
}

刷题笔记

AGC30C

我们找到一个 \(n\),如果 \(k\) 小于 \(500\),就把它设为 \(n\),否则 \(n\) 为 500。我们开大一点,给我们操作的空间。

那么我们首先在每一行循环的填上 \(n\),然后把偶数行的 \(1\)\(k-n\) 都设为 \(n+1\)\(k\) 即可。

循环数组的目的是为了保证填完之后每个数旁边的数字都相同。

CF1365F

我们观察到,位置相对的两个值在操作的过程中位置永远相对,而且经过手玩我们可以知道,我们可以任意的把一些数对移动到相应位置。这启发我们如果两个序列相对位置数字相同,换句话说,这样的数对相同,就可以进行操作。利用 multiset 进行判断就可以了。

CF1558C

经过手玩我们就可以知道我们可以用 5 补摆好两个位置,模拟即可。

CF1333E

只要构造出局部,这个题目就可以做完,所以我们需要想办法构造出最小的解。
容易发现大小为 3 的矩阵可以被构造出来。

CF1375E

考虑把最大的值换到最后一个位置,找到最大值位置,考虑一定存在这样一个逆序对,所以我们可以先把其他与最后一个位置有关的值交换完,在把这个位置与我们的最大值位置进行交换。

CF1375E

对于一个排列来说,是很好做的。我们把每个值加上下标变成一个二元组,然后做就可以了。

CF1392E

考虑一共有 \(2n-1\) 条边,考虑开的下,所以我们考虑用二进制存边。不难想到利用对角线。

CF1375D

考虑最终我们把数组转化成的形式,然后简化操作步骤。

CF1485D

经过数值分析可以简单进行构造。主要依据是所有的 \(a\) 都很小,所以它们的 lcm 也不是很大,完全可以承受的住,限制就很好满足了。

AT4363

这个题目的性质比较强,除了逆序对个数和位置奇偶性之外,我们还可以发现可以对序列进行分段,然后考虑每一段内向左和向右的数字分别是单调的。

posted @ 2022-02-18 15:07  hyl天梦  阅读(46)  评论(2编辑  收藏  举报