《The 13th Shandong ICPC Provincial Collegiate Programming Contest》 vp

简介

挂个 Link 先。

队伍配置:ddlbeiwen 和我

先%%%主力 ddl+beiwen,我属于小躺一把。

有三台机,是挂了属于。

赛时

放个结果
image

随机开题,我先开了 D,ddl 先去开了 B,此时我旁边的 bw 仍然在杀!杀!杀!,但是没关系(bushi

我也变看他抽边写 D,ddl 因为一时没想出来 B 解法转去开 I,bw 也终于加入战场并且去开了 A,但是不知道怎么了,在吃了两罚之后签的(有点唐..

紧接着 ddl 也签了 I,我 D 也想到二分,写了一会调了调签了(我开的最早写得最慢速呜呜/cai。三道签到开完了,运气有点好..

这时 ddl 去开了 G,我先去开了 M,发现是计算几何,遂摆烂去开了 L,bw 去开了 J。过了一会 ddl 杀了 G,%%%,我想到了 L 的构造开码,bw 好像在思考 J。

ddl 大神又去开了 F,但是写了几发被卡了..过了一会我过了 L,bw还在调 J。

这是一看排名,30多,嗯形势大好:) 我本来想去开 E,但是 ddl 让我帮他调 F,我一看,哎这不是这题吗,ddl 一听[兴奋地]让我试试,然后我拷个代码发现样例都过不了,再看一眼题,oh,是数数啊,那没事了,但是总体思路应该差不多,于是看看 ddl 代码,确实好像是这么回事,但是其实我们俩都错了(赛后想了下其实是数数和 \(\max\) 不一样),调了一会无果遂摆烂,想我的 E 去了,让 ddl 自己调。

过了一会,E 好像有点思路,首先一定是先操作 2 再操作 1,那不妨枚举操作 2 次数再判断多少次 1 满足条件,最后答案一定形成一个区间,所以维护 \([L,R]\)。代码很好写,交了一发 WA 了,不知道为什么。这时 ddl 也对着 F 很无奈,于是我们又开始交换调题,调调调,还是不知道 ddl 哪错了,很愤怒,于是叫 ddl 自己解决去了~ 我又回去调我的 E。

到这时已经接近 1h 没有切题了,bw 小盆友对着他的 J 死磕了接近 1.4h 无果,小炸,不过问题不大。突然我灵光一现,感觉可能是我炸 \(long long\) 了,改了个 \(int128\) 竟然过了!!!比较兴奋,然后我去开 K,读下题,嗯貌似有点熟悉?一看样例,wahts?这不 ccz 出的原题吗,赶紧准备开贺。

然后出现了滑稽的对内三人争抢谁拿白给题~ 这时 bw 说他就过了一题,想捡个白给的,就让给他了..正当他拷完代码准备交时,ddl [啪的一声很快啊] 抢先交了,场面比较好笑。

于是我去开了 C,ddl 重回 B,这时到了饭点,我和 ddl 先去干饭了。干饭路途想到了 C 错解,回到机房路上看见 bw,他还没调过 J,于是将重任交给了 ddl 就摆烂了...继续码码码,ddl 调调调,很快我 C 写完一交 WA...想了一会 ddl 想到了 hack,然后开始思考维护长度,但是发现可能类似启发式合并?比较难码,而且不会相同的怎么处理,遂摆烂...看 ddl 狂调 J,硬是将 \(O(60q\log n)\) 优化掉了个 \(\log\) 也过不去...然后要上晚修也开摆了...

但是 M 属于我唐错误判断了...背锅

题解:

B

类拓扑排序。

我们维护每项工程还有几条要求没有满足,并将不同工种的要求分别维护。所有要求都被满足的工程将加入队列。从队列中取出一项工程后,我们会获得该工程的奖励。当工种为 \(t\) 的员工加入公司后,我们检查和工种 \(t\) 有关的人数最少的未满足需求。如果这项需求被满足了,则对应工程的要求数减 \(1\)。要求数减少为 \(0\) 的工程继续加入队列。

答案就是从队列中取出的工程数。复杂度 \(O(n\log n)\),主要是每次拿出“人数最少的需求”需要预先排序或使用堆等数据结构。

Code
#include<bits/stdc++.h>
#define il inline
#define rint register int
#define ll long long
#define pii pair<int,int>
using namespace std;
const int N=1e5+10,INF=2147483647;
char *p1,*p2,buf[N];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define gc() getchar()
il int rd(){
	int x=0,f=1;
	char ch=gc();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=gc();
	return x*f;
}
int g,n;
int a[N][2],m[N];
map<int,priority_queue<pii,vector<pii>,greater<pii>>>mp;
vector<pii>bonus[N];
map<int,int>have;
queue<int>G;
void add(int id,int val){
	have[id]+=val;
	priority_queue<pii,vector<pii>,greater<pii>> &q=mp[id];
	while(!q.empty()){
		pii tmp=q.top();
		if(tmp.first>have[id])break;
		q.pop();
		if(!(--m[tmp.second]))G.push(tmp.second);
	}
}
void Main(){
	g=rd();
	for(int i=1; i<=g; ++i)a[i][0]=rd(),a[i][1]=rd();
	n=rd();
	for(int k,i=1; i<=n; ++i){
		m[i]=rd();
		for(int a,b,j=1; j<=m[i]; ++j){
			a=rd(),b=rd();
			mp[a].push({b,i});
		}
		k=rd();
		for(int a,b,j=1; j<=k; ++j){
			a=rd(),b=rd();
			bonus[i].push_back({a,b});
		}
	}
	for(int i=1; i<=n; ++i)if(m[i]==0)G.push(i);
	for(int i=1; i<=g; ++i)add(a[i][0],a[i][1]);
	int ans=0;
	while(!G.empty()){
		int id=G.front();G.pop();
		++ans;
		for(pii p:bonus[id])add(p.first,p.second);
	}
	cout<<ans<<endl;
}
signed main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=1;
	while(T--)Main();
	return 0;
}

C

对于同一个节点,考虑如何将字母分给它的所有子树。显然,\(a\) 应该分给最小的子树,\(b\) 应该分给第二小的子树...

但是这里考虑“小”并不是字数内关键节点谁多,因为长度决定优先/

这里,子树 \(i\) 小于子树 \(j\)指的是子树 \(i\) 里最小的关键字符串比子树 \(j\) 里的小,如果一样就比第二小的关键字符串... 如果某个子树里的字符串已经比完了,那么哪个子树有更多的字符串,哪个子树就更小。

这时我们类似 \(\text{SA}\),给每个子树标上一个 \(rk_i\) 表示排名,\(rk\) 越小子树越小。

一个子树的 \(rk\) 取决于其儿子节点子树 \(rk\) 加上自身节点是否有关键节点,那我们可以用一个 \(vector\) 表示其 \(rk\),将这个 \(vector\) 排序,相当于将子树内部按字典序排序。然后再比较不同子树的 \(vector\)(按照上面的规则),给每个子树重新标号 \(rk\),这样就可以完成计算。

Code
#include<bits/stdc++.h>
#define il inline
#define rint register int
#define ll long long
#define pvi pair<vector<int>,int>
#define pii pair<int,int>
using namespace std;
const int N=2e5+10,INF=2147483647;
char *p1,*p2,buf[N];
#define nc() (p1==p2 && (p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
#define gc() getchar()
il int rd(){
	int x=0,f=1;
	char ch=gc();
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=gc();}
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=gc();
	return x*f;
}
int n,m;
vector<int>G[N];
int fl[N],rk[N];
vector<int>dep[N];
void pre(int x,int d){
	dep[d].push_back(x);
	for(auto y:G[x])pre(y,d+1);
}
char ans[N];
void dfs(int x){
	vector<pii>ran;
	for(auto y:G[x])ran.push_back({rk[y],y});
	sort(ran.begin(),ran.end());
	for(int i=0; i<ran.size(); ++i)ans[ran[i].second]='a'+i;
	for(auto y:G[x])dfs(y);
}
void Main(){
	n=rd(),m=rd();
	for(int i=0; i<=n; ++i)G[i].clear(),fl[i]=0,dep[i].clear();
	for(int x,i=1; i<=n; ++i){
		x=rd();
		G[x].push_back(i);
	}
	for(int x,i=1; i<=m; ++i){
		x=rd();
		fl[x]=1;
	}
	pre(0,0);
	for(int i=n; i>=0; --i){
		vector<pvi>ran;
		for(auto x:dep[i]){
			vector<int>son;
			if(fl[x])son.push_back(0);
			for(auto y:G[x]){
				son.push_back(rk[y]);
			}
			sort(son.begin(),son.end());
			ran.push_back({son,x});
		}
		sort(ran.begin(),ran.end(),[](pvi &a,pvi &b){
			for(int i=0; i<min(a.first.size(),b.first.size()); ++i){
				if(a.first[i]!=b.first[i])return a.first[i]<b.first[i];
			}
			return a.first.size()>b.first.size();
		});
		int now=1;
		for(int j=0; j<ran.size(); ++j){
			rk[ran[j].second]=now;
			if(j+1<ran.size()&&ran[j].first!=ran[j+1].first)++now;
		}
	}
	dfs(0);
	ans[n+1]=0;
	printf("%s\n",ans+1);
}
signed main(){
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	int T=rd();
	while(T--)Main();
	return 0;
}

F

我的评价是化身唐B。

还是 czz 之前模拟赛里的一道题...看着眼熟不是没道理的(

考虑为什么 ddl 那个做法假了。

一个 hack:

3
2 3 0
4 5 1
1 6 1

具体不细说了,看之前总结。

posted @ 2023-09-18 21:17  J1mmyF  阅读(162)  评论(0)    收藏  举报