P10277 [USACO24OPEN] Bessie's Interview S

题目传送门

我的博客 - 欢迎光顾!

闲话:机房模拟赛出的,后来补题的时候纯模拟跑过去了……还跑出了我OI生涯第一个最优解,不是?

纯模拟最优榜第一


那我们就说说纯模拟做法。

首先第一问很简单,直接用一个优先队列存当前面试官的编号,以及面试官面试完的时间,模拟这个过程就好。

相信很多人都想到了并查集。但是并查集很容易被证伪,请看以下数据:

(以下我们用方块长度表征面试时间长短)

P10277_1_3

我们发现,1 是最优解,因为 1 和 2 有一段是齐平的,所以 1 和 2 后面接的东西是可以互换的,所以 2 也是最优解。

P10277_2_3

但是 3 和 2 虽然有一段是齐平的,2 从 1 上传来的最优解并不能传递到 3 上。因为 2 换完以后和 3 的齐平面就没了。

但是我们反过来看,如果某种情况下 3 是最优解的话,1 是能通过 2 传递来最优解的。因为我们发现,后齐平面以后的部分交换后,前齐平面并不会改变。

P10277_1_5

P10277_3_2

P10277_5_1

看完这个例子后,我们发现:后齐平面改变并不会影响前齐平面,但是前齐平面的改变会影响后齐平面。

所以我们在原先优先队列模拟的基础上,开一个 vector 数组 \(g\) 和变量 \(newn\),当优先队列里有多个相同的面试结束时间,就让 \(newn++\),再让 \(g_{newn}\) 把这些有同一齐平面的面试官的编号记录下来。

同时我们要继续模拟,让优先队列把这之后面试的奶牛都随便找个面试官开始面试。

注意,最后可能有多个面试官的结束时间相同且最短。这里我的解决方案是新开一个 \(newn\),然后仍然用 \(g_{newn}\) 把这些面试官的编号存下来。

最后我们\(newn\) 从大到小倒序枚举 \(g\) 数组,让后面齐平面的合法状态传递到前面的齐平面上去。

(感觉说的不太清楚,就是说,如果某个齐平面上有一个编号可以,那这个齐平面上的所有编号都是合法的。并且后齐平面交换不影响前齐平面,所以这个合法状态可以通过后面齐平面的某个编号,传递给这个编号前面与它共齐平面的编号上)

最后输出所有编号是否合法即可。

因为 \(g\) 中最多有 \(n\) 个元素,所以最终时间复杂度是 \(O(n \log n)\) 的。

P10277正常版
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

const int N=3e5+5;
int n,k,a[N],newn,ok[N];
struct WS{
	int lst,id;
	bool operator <(const WS Ws)const{
		return lst>Ws.lst;
	}
};
priority_queue<WS> q;
//q:用来模拟的优先队列 
vector<int> g[N];
//g,newn:同题解 

signed main(){
	n=read(),k=read();
	for(int i=1;i<=n;i++){
		a[i]=read();
	}
	//初始化要让对应编号的奶牛来面试 
	for(int i=1;i<=k;i++){
		q.push((WS){a[i],i});
	}
	for(int i=k+1;i<=n;i++){
		int lst=q.top().lst,id=q.top().id;
		q.pop();
		q.push((WS){lst+a[i],id});
		if(!q.empty()&&q.top().lst==lst){//处理多人同时面试完的情况 
			newn++;
			g[newn].push_back(id);
			while(!q.empty()&&q.top().lst==lst){
				int ID=q.top().id;
				g[newn].push_back(ID);
				i++;
				if(i>n) break;//注意特判i的边界 
				q.push((WS){lst+a[i],ID});
				q.pop();
			}	
		}
	}
	//我们最后把最小的所有解法扔进g中新开的 newn 里(虽然这步好像没什么必要) 
	++newn;
	int ans1=0;
	ans1=q.top().lst;
	int id=q.top().id;
	q.pop();
	ok[id]=1;
	g[newn].push_back(id);
	while(!q.empty()&&q.top().lst==ans1){
		int ID=q.top().id;
		g[newn].push_back(ID);
		ok[ID]=1;
		q.pop();
	}
	for(int i=newn;i>=1;i--){
		int fl=0;
		for(int j=0;j<g[i].size();j++){//判断当前齐平面是否有可以最优解的 
			if(ok[g[i][j]]){
				fl=1;break;
			}
		}
		if(!fl) continue;
		for(int j=0;j<g[i].size();j++){
			ok[g[i][j]]=1;
		}
	}
	//输出答案 
	printf("%lld\n",ans1);
	for(int i=1;i<=k;i++){
		printf("%lld",ok[i]);
	}
	printf("\n");
	return 0;
}

但是后来某个@Deity_Ling 非要跟我卡常……于是最优榜第一面最终变成了这个样子。

第一面榜已经被ssfz的占领了

奉上卡常后的最优解代码:

P10277卡常版
#include<bits/stdc++.h>
#define gc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000096,stdin),p1==p2)?EOF:*p1++)
using namespace std;

char *p1,*p2,buf[1000100];
inline int read(){
	int x=0,f=1;char c=gc();
	while(c<48){
		if(c=='-') f=-1;
		c=gc();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=gc();
	return x*f;
}

inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else write(x/10),putchar(x%10+'0');
}

const int N=3e5+5;
int n,k,a[N],newn;
bitset<N> ok;
struct WS{
	long long lst;
	int id;
	bool operator <(const WS Ws)const{
		return lst>Ws.lst;
	}
};
priority_queue<WS> q;
vector<int> g[N];

signed main(){
	n=read(),k=read();
	for(register int i=1;i<=n;i++){
		a[i]=read();
	}
	for(register int i=1;i<=k;i++){
		q.push((WS){a[i],i});
	}
	for(register int i=k+1;i<=n;i++){
		long long lst=q.top().lst;int id=q.top().id;
		q.pop();
		q.push((WS){lst+a[i],id});
		if(!q.empty()&&q.top().lst==lst){
			newn++;
			g[newn].push_back(id);
			while(!q.empty()&&q.top().lst==lst){
				int ID=q.top().id;
				g[newn].push_back(ID);
				i++;
				if(i>n) break;
				q.push((WS){lst+a[i],ID});
				q.pop();
			}	
		}
	}
	++newn;
	long long ans1=0;
	ans1=q.top().lst;
	int id=q.top().id;
	q.pop();
	ok[id]=1;
	g[newn].push_back(id);
	while(!q.empty()&&q.top().lst==ans1){
		int ID=q.top().id;
		g[newn].push_back(ID);
		ok[ID]=1;
		q.pop();
	}
	for(register int i=newn;i>=1;i--){
		int fl=0;
		for(register int j=0;j<g[i].size();j++){
			if(ok[g[i][j]]){
				fl=1;break;
			}
		}
		if(!fl) continue;
		for(register int j=0;j<g[i].size();j++){
			ok[g[i][j]]=1;
		}
	}
	printf("%lld\n",ans1);
	for(register int i=1;i<=k;i++){
		write(ok[i]);
	}
	printf("\n");
	return 0;
}

另外,奉上自己造出来的几个小数据,方便大家调试。

in1:

13 6
18 14 9 1 9 11 21 9 10 20 29 29 6

ans1:

19
001010

in2:

12 5
13 28 1 13 3 16 15 6 7 16 1 6 

ans2:

19
10011

in3:

5 2
17 17 18 18 18

ans3:

35
11

in4:

6 3
6 17 17 17 17 17

ans4:

23
100
posted @ 2025-11-07 08:13  qwqSW  阅读(2)  评论(0)    收藏  举报