ARC076简要题解

赛中三题,10:02分开打,居然没罚时。
在这里插入图片描述
最后一题膜了发题解,贪心没想到,霍尔定理不会。
qwq。
C题:
看看n和m是否差在1以内,如果差1,则俩阶乘相乘
如果相等,则2*俩阶乘的乘积。

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

long long ans = 0;
long long jc1 = 1, jc2 = 1;
int n, m;
const int mod = 1000000007;
int main() { 
	cin>>n>>m;
	for(int i=1;i<=n;++i) 
		jc1 = jc1 * i % mod;
	for(int i=1;i<=m;++i) 
		jc2 = jc2 * i % mod;
	if(n < m) 
		swap(n, m);
	if(n - m == 1) 
		ans = jc1 * jc2 % mod;
	else if(n == m) 
		ans = 2 * jc1 * jc2 % mod;
	else
		ans = 0 * jc1 * jc2 % mod;
	cout<<ans;
	return 0;
} 

D:
一开始拿到没反应过来,是先做了E才出的D。
显然是MST模型,大力建图会直接去世。根据经验不难想到,那个距离的式子,可以把坐标值拉成一条长链。
在这里插入图片描述
然后你就可以脑补出来这么一个玩意,蓝色是点,数字是xy坐标对应下来的点,绿色边是0权,紫色边是两个数字的差。
建图之后kruscal一下就好了。
注意好好判重(我又是用Umap搞得

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

struct edge { 
	int l, r, val;
	bool operator < (const edge &a) const { 
		return val < a.val;
	} 
};
edge ed[500010];
int ecnt, n;
int px[100010], py[100100];
int pnum[100100], pcnt;
int x[100010], y[100010];
int fa[300010];
unordered_map<int, int>xlis;
unordered_map<int, int>ylis;

int lf(int k) { 
	return fa[k] == k ? k : fa[k] = lf(fa[k]);
} 

void merge(int l,int r) { 
	l = lf(l), r = lf(r);
	fa[l] = r;	
} 

inline void ade(int l,int r,int val) { 
	ed[++ecnt] = {l, r, val};
} 

inline long long kruscal() { 
	for(int i=1;i<=pcnt;++i) 
		fa[i] = i;
	sort(ed + 1, ed + ecnt + 1);
	long long ans = 0;
	for(int i=1;i<=ecnt;++i) { 
		int l = ed[i].l, r = ed[i].r;
		if(lf(l) != lf(r)) 
			merge(l, r), ans += ed[i].val;
	} 
	return ans;
} 

int main() { 
	scanf("%d",&n);
	for(int i=1;i<=n;++i) { 
		scanf("%d%d", &px[i], &py[i]);
		x[i] = px[i], y[i] = py[i];
		pnum[i] = ++pcnt;
	} 
	sort(x + 1, x + n + 1);
	sort(y + 1, y + n + 1);
	x[0] = y[0] = -123123;
	for(int i = 1; i <= n; ++i) { 
		if(x[i] == x[i-1])  
			x[i-1] = 2000000000;
		if(y[i] == y[i-1])  
			y[i-1] = 2000000000;
	} 
	sort(x + 1, x + n + 1);
	sort(y + 1, y + n + 1);
	int xlim = 0, ylim = 0;
	for(int i = 1;i <= n; ++i) { 
		if(x[i] > 1500000000) 
			break;
		xlim++, xlis[x[i]] = ++pcnt;
	} 
	for(int i = 1;i <= n; ++i) { 
		if(y[i] > 1500000000) 
			break;
		ylim++, ylis[y[i]] = ++pcnt;
	} 
	for(int i=1;i<=n;++i) 
		ade(pnum[i], xlis[px[i]], 0), ade(pnum[i], ylis[py[i]], 0);
	
	for(int i=2;i<=xlim;++i) { 
		ade(xlis[x[i-1]], xlis[x[i]], x[i] - x[i-1]);
	}
	
	for(int i=2;i<=ylim;++i) {
		ade(ylis[y[i-1]], ylis[y[i]], y[i] - y[i-1]);
	}
	
	cout<<kruscal();
	return 0;
	
}
/*
9
8 3
4 9
12 19
12 19
18 1
13 5
13 5
7 6
7 6
*/

E:
蛮好玩的题。
通过一通观察+手玩,对于那种并非两点都在矩形边界的线段,我们一定可以通过一个很诡异的姿势给他塞进去。
所以只用判一判矩形边框上的点,是否不存在交叉的情况。
然后惊奇的发现这是个括号序列,排个序之后上个栈就好了。
(这玩意我之前目测是真做不出来。貌似智力提升了?

#include<bits/stdc++.h>
using namespace std;
int n, R, C;

struct node { 
	int x, y, val;
	int xis;
	int trans() { 
		if(x == 0 && y == 0) {  
			return 0;
		} 
		
		if(x == 0 && y != 0) { 
			return y;
		} 
		
		if(y == C && x != 0) { 
			return C + x;
		} 
		
		if(x == R && y != 0) { 
			return R + C + (C - y);
		} 
		
		if(y == 0 && x != 0) { 
			return C + R + C  + (R - x);
		} 
		return -123123;
	} 
	bool operator < (const node &a) const { 
		return xis < a.xis;
	} 
};
node sth[200010];
int scnt;
node lef, rig;
stack<int>kk;
int main() { 
	scanf("%d%d%d",&R,&C,&n);
	for(int i=1;i<=n;++i) { 
		scanf("%d%d%d%d",&lef.x, &lef.y, &rig.x, &rig.y);
		lef.xis = lef.trans(), rig.xis = rig.trans();
		lef.val = rig.val = i;
		if(lef.xis >= 0 && rig.xis >= 0) 
			sth[++scnt] = lef, sth[++scnt] = rig;
	} 
	sort(sth + 1, sth + scnt + 1);
	for(int i=1;i<=scnt;++i) { 
		if(!kk.empty()) { 
			if(kk.top() == sth[i].val) { 
				kk.pop();
				continue;
			} 
			else
				kk.push(sth[i].val);
		} 
		else 
			kk.push(sth[i].val);
	} 
	printf(kk.empty() ? "YES" : "NO");
	return 0;
} 

F题(赛后看了题解:
不得不说第一眼看成候选区间是 [ l , r ] [l,r] [l,r]了。
“这辣鸡玩意不是一个人拆成一个入和一个出,时间轴上在入点加入一下,搞个优先队列维护出,瞎贪心就好了???这居然是F题?”
写了一半发觉样例过不去(

重新读了个题之后感觉可以网络流,看到那个20w之后就被劝退了。

正解有两种操作,第一种是一个妙妙的贪心。
题目里面的限制是,一个人只能在一段前缀以及一段后缀里面选椅子。
两边都有不好办,我们先遮住一边,来看看只有前缀咋整。
显然把人放在坐标点上,从前往后扫,记录当前空余几个椅子。
然后贪心的放一放,我们考虑,当我们在一个坐标点上遇到了 n > m n>m n>m个人,而现在有m个空余的椅子。由于这n个人是本质相同的,所以我们任选其中的 m m m个,让他们坐下去就了事。
同时,由于这 n n n个人和之前已经找到座位的若干人,对于答案的贡献也是相同的,所以我们并不需要做“先把一个人轰起来,然后再把这个人swap上去”的操作。

考虑加上后缀的限制。
那么,假设在某个坐标点,我们至今有 m m m个空余的椅子,有 n > m n>m n>m个等待椅子的人,此时我们基于贪心的想,肯定优先把这 m m m个人里面对于后缀限制最苛刻的 n n n个人给安排了。
同时注意,我们还有把人轰起来的操作。所以如果我们在之前坐下来的人里面发觉了一位对于后缀限制非常宽泛的兄弟,那么他可能就要被轰走了(雾
不难发现这个操作可以用个堆来维护。
在操作的时候,顺手维护哪些人已经被塞进去了。
为什么我们不用关心哪些椅子被占领了呢?假设这一趟安排好了 T T T个人,那么被占领的椅子必定是 [ 1 , T ] [1,T] [1,T],可以说是非常显然了。
贴个自己写的版本:

#include<bits/stdc++.h>
using namespace std;
int L[200010], R[200010];
int n, m;
struct info { 
	int l, r, val;
	bool operator < (const info &a) const { 
		return r > a.r;
	} 
};

vector<info>sth[200010];
priority_queue<info>ins;
int cnt[200010];
bool vis[200010];
int main() { 
	scanf("%d%d", &n, &m);
	for(int i=1;i<=n;++i) 
		scanf("%d%d",&L[i], &R[i]), sth[L[i]].push_back({L[i], R[i], i});
	int res = 0, ok = 0;
	for(int i=1;i<=m;++i) { 
		res++;
		for(int j = 0;j < sth[i].size();++j) { 
			if(res > 0) 
				res--, ok++, ins.push(sth[i][j]);
			else { 
				if(!ins.empty() && ins.top().r < sth[i][j].r) 
					ins.pop(), ins.push(sth[i][j]);
			} 
		} 
	} 
	
	while(!ins.empty()) { 
		int nval = ins.top().val;
		vis[nval] = 1;
		ins.pop();
	} 
	
	for(int i=1;i<=n;++i) 
		if(!vis[i])
			cnt[R[i]]++;
	int pok = ok;
	//pok是指,在从左往右处理的时候,被占据的椅子们。
	res = 0;
	for(int i=m; i >= 1; --i) { 
		if(i > pok) res++;
		if(res >= cnt[i]) 
			ok += cnt[i], res-= cnt[i];
		else
			ok += res, res = 0;
	} 
	printf("%d", n - ok);
	return 0;
} 

第二种正解:Hall定理 + 线段树
先来看看定理。
对于一个二分图,两个点集分别为 X , Y X,Y X,Y
不妨设 ∣ X ∣ ≤ ∣ Y ∣ |X|\le|Y| XY,则该二分图有完美匹配的充要条件是,对于 X X X的任意一个子集 X 1 X_1 X1,定义其邻居集合为 N e i ( X 1 ) Nei(X_1) Nei(X1),若总有 ∣ X 1 ∣ ≤ |X_1|\le X1 N e i ( X 1 ) Nei(X_1) Nei(X1),则这个二分图有完美匹配。
这定理本体没什么作用,但是有一个很猛的推论。
依然不妨设 ∣ X ∣ ≤ ∣ Y ∣ |X|\le|Y| XY
二分图的匹配数 K = M a x ( ∣ X 1 ∣ − ∣ N e i ( x 1 ) ∣ ) , X 1 ⊂ X K = Max(|X_1|-|Nei(x_1)|) , X_1 \subset X K=Max(X1Nei(x1)),X1X
这玩意就很好用了。
变个形, A n s = n − M a x ( ∣ X 1 ∣ − ∣ N e i ( x 1 ) ∣ ) Ans =n - Max(|X_1|-|Nei(x_1)|) Ans=nMax(X1Nei(x1))
子集枚举人显然会GG,试图来点贪心。
对于一坨人来讲,我们只关心其中最大的 L L L限制和最小的 R R R限制。
这坨人的邻居数 N e i ( X 1 ) = L + m − R + 1 Nei(X_1) = L + m - R + 1 Nei(X1)=L+mR+1(如果有 L > R L>R L>R则就是 m m m
则上面那个式子就可以写成 ∣ X 1 ∣ + R − L − m − 1 |X_1|+R-L-m-1 X1+RLm1
然后由于我们需要求这堆东西的max,那么我们不妨按住 L L L,去找一个使得 ∣ X 1 ∣ + R − L − m − 1 |X_1|+R-L-m-1 X1+RLm1最大的 R R R,即对于每个 L L L,最大化 ∣ X 1 ∣ + R |X_1|+R X1+R
按照 L L L的顺序把人放在线段树上面,放进去的时候在线段树上对应修改一哈就好。代码懒得写,贴个STD。
似乎这线段树用到了扫描线的思想。没想太清楚,先咕在这里。

//by Judge
#include<bits/stdc++.h>
#define Rg register
#define fp(i,a,b) for(Rg int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(Rg int i=(a),I=(b)-1;i>I;--i)
#define ll long long
using namespace std;
const int M=2e5+3;
#ifndef Judge
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
#endif
char buf[1<<21],*p1=buf,*p2=buf;
inline int read(){ int x=0,f=1; char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0'; return x*f;
} int n,m,ans,t[M<<2],Tag[M<<2]; vector<int> val[M];
#define ls k<<1
#define rs k<<1|1
#define mid ((l+r)>>1)
#define lson ls,l,mid
#define rson rs,mid+1,r
inline int Max(int x,int y){ return x>y?x:y; }
inline void pushup(int k){ t[k]=Max(t[ls],t[rs]); }
inline void pushdown(int k){ if(!Tag[k]) return ;
    Tag[ls]+=Tag[k],Tag[rs]+=Tag[k];
    t[ls]+=Tag[k],t[rs]+=Tag[k],Tag[k]=0;
}
void build(int k,int l,int r){
    if(l==r) return t[k]=l,void();
    build(lson),build(rson),pushup(k);
}
void update(int k,int l,int r,int L,int R){
    if(L<=l&&r<=R) return ++Tag[k],++t[k],void(); if(l>R||L>r) return ;
    pushdown(k),update(lson,L,R),update(rson,L,R),pushup(k);
}
int query(int k,int l,int r,int L,int R){
    if(L<=l&&r<=R) return t[k]; if(l>R||L>r) return 0;
    return pushdown(k),Max(query(lson,L,R),query(rson,L,R));
}
int main(){ int l,r; n=read(),m=read()+1,ans=n-m+1;
    fp(i,1,n) l=read(),r=read(),val[l].push_back(r);
    build(1,0,m);
    fp(L,0,m-1){
        fp(j,0,val[L].size()-1) update(1,0,m,0,val[L][j]);
        ans=Max(ans,query(1,0,m,L+1,m)-m-L);
    } return !printf("%d\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值