ICPC World Finals 2019部分题目解析

#6577. 「ICPC World Finals 2019」瓷砖

description

有两排瓷砖,分为前排和后排,每一排都有(N)个。且对于每块瓷砖都已知其高度(h_i)和价值(w_i)
现在你可以对同一行的瓷砖进行排序,要求对于同一排瓷砖价值单调不减,对于同一列后排瓷砖高度严格大于前排瓷砖
如果无解输出(impossible)

data range

(Nle 5cdot10^5)

solution

对于每一行,容易想到先按照价格从小到大排序
现在的关键就在于对于价格相同的一段如何进行排列
不妨设当前后排有(a)个最便宜的,前排有(b)个最便宜的
不妨假设(ble a)
那么我们对前排的(b)个一个一个地考虑
如果当前瓷砖的高度为(h_i),那么贪心地想,我们只用在后排的(a)个中选出高度最小的瓷砖满足它的高度大于(h_i)
对于(b>a),讨论是类似地
我们可以用(set)维护以上的所有操作

time complexity

容易发现我们使用(mathcal O(log_2n))的时间使问题规模减小(1)
因此总复杂度(mathcal O(nlog_2n))

code

#include<bits/stdc++.h>
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef pair<int,int> pii;
const int N=5e5+5;
int n;
int pp[N],ph[N],sp[N],sh[N],a1[N],a2[N];
set<pii>s1,s2;
pii p1[N],p2[N];
inline bool work()
{
	for(int i=1;i<=n;++i)
		p1[i]=mp(sp[i],i),p2[i]=mp(pp[i],i);
	sort(p1+1,p1+n+1),sort(p2+1,p2+n+1);
	int cnt=0;bool pd1=0,pd2=0;
	while(cnt<n)
	{
		int d=cnt+1;
		if(!pd1)for(int i=d;i<=n&&p1[i].fi==p1[d].fi;++i)
			s1.insert(mp(sh[p1[i].se],p1[i].se));
		if(!pd2)for(int i=d;i<=n&&p2[i].fi==p2[d].fi;++i)
			s2.insert(mp(ph[p2[i].se],p2[i].se));
		if(s1.size()>=s2.size())
		{
			for(auto v:s2)
			{
				++cnt;auto it=s1.upper_bound(mp(v.fi,n));
				if(it==s1.end())return 0;
				a1[cnt]=(*it).se,a2[cnt]=v.se;s1.erase(it);
			}
			pd1=1,pd2=0;s2.clear();
		}
		else
		{
			for(auto v:s1)
			{
				++cnt;auto it=s2.lower_bound(mp(v.fi,1));
				if(it==s2.begin())return 0;it--;
				a1[cnt]=v.se,a2[cnt]=(*it).se;s2.erase(it);
			}
			pd1=0,pd2=1;s1.clear();
		}
	}return 1;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;++i)scanf("%d",&sp[i]);
	for(int i=1;i<=n;++i)scanf("%d",&sh[i]);
	for(int i=1;i<=n;++i)scanf("%d",&pp[i]);
	for(int i=1;i<=n;++i)scanf("%d",&ph[i]);
	if(!work())puts("impossible");
	else
	{
		for(int i=1;i<=n;++i)printf("%d ",a1[i]);puts("");
		for(int i=1;i<=n;++i)printf("%d ",a2[i]);
	}
	return 0;
}

#6581. 「ICPC World Finals 2019」断头路探测者

description

给定(N)个点(M)条边的无向图。如果从(x)进入某条边(S)后只能掉头返回(x),那么就应该(S)在入口(x)处添加一个标志
如果在某条边(S)的入口(x)处有一个标志,另一条边的入口(y)处有一个标志,且存在一条从(x)出发经过(S)到达(y)的路径
那么我们就称在(y)处的标志是不必要的
现在询问标志的最小数量,同时要求输出方案

data range

(N,Mle 5cdot 10^5)

solution

此题很妙妙
对于每一个联通块单独考虑
如果这个联通块是一棵树
考虑树上的一条边(u ightarrow v),其中(u)不是叶子节点
那么必然存在一条从某个叶子节点出发到(u)的路径,因此这个标志是不必要的
因此对于一棵树,它的标志就应该在边(u ightarrow v)上,其中(u)是一个叶子节点
如果这个联通块存在环,那么环上的边和由其他地方指向环的边都是不应该有标志的
于是乎我们只需要给那些由环到树的边上添加标志即可
具体实现可以采用拓扑排序
第一次剥离环上所有的树
第二次统计和环联通的点
然后根据所得信息就可以较容易地统计以上两种情况了

time complexity

(mathcal O(n+m))忽略了排序的复杂度

code

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,m,d[N];
bitset<N>vis;
vector<int>e[N];
queue<int>q;
vector<pair<int,int> >ans;
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,u,v;i<=m;++i)
	{
		scanf("%d%d",&u,&v);
		e[u].push_back(v),e[v].push_back(u);
		++d[u],++d[v];
	}
	for(int i=1;i<=n;++i)
		if(d[i]==1)vis[i]=1,q.push(i);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int v:e[u])
			if(!vis[v]&&--d[v]==1)vis[v]=1,q.push(v);
	}
	vis.reset();
	for(int i=1;i<=n;++i)
		if(d[i]>1)vis[i]=1,q.push(i);
	while(!q.empty())
	{
		int u=q.front();q.pop();
		for(int v:e[u])
			if(!vis[v])vis[v]=1,q.push(v);
	}
	for(int u=1;u<=n;++u)
		for(int v:e[u])
		{
			if(!vis[u]&&e[u].size()==1)ans.push_back(make_pair(u,v));
			if(vis[u]&&d[u]>1&&d[v]==1)ans.push_back(make_pair(u,v));
		}
	sort(ans.begin(),ans.end());
	printf("%lu
",ans.size());
	for(auto i:ans)printf("%d %d
",i.first,i.second);
	return 0;
}

#6584. 「ICPC World Finals 2019」Hobson 的火车

description

给出一个(N)阶有向图,满足每个点的出度为(1)
对于每个点求到它的距离不大于(k)的点的个数

data range

(kle Nle 5cdot 10^5)

solution

还是对于每个联通块单独考虑
容易发现是一个内向基环树
可以理解为有一个简单环,在其上套了若干个树,满足根节点在环上且边都由儿子指向父亲
显然,树上的点和环上的点计算答案的方式是不同的
对于树上的点,到它距离不超过(k)的点一定在它的子树中
直接统计不方便,考虑拆开贡献
一个点的答案就是以自己为根的子树大小减去所有(k+1)级后代的子树大小(如果存在的话)
对于每个点,设子树大小为(sz)
那么对自己的贡献就是(sz),对自己(k+1)级祖先的贡献就是(-sz)
可以通过(vector)来维护当前节点的所有祖先,然后就能做到线性了
对于环上的点,考虑每个点对于环上的点的贡献
分为两个部分
首先环上连续的一段都会被贡献整个树的点数(这部分也有可能不存在)
然后之后一部分,每再走一步,贡献就会相应的减少
第一部分可以差分,第二部分暴力跳就可以了
注意还有一些细节需要处理

time complexity

(mathcal O(n))

code

#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int n,k,to[N],ans[N],dep[N],sz[N],num[N],a[N],dif[N],id[N];
vector<int>pre[N],now;
bool vis[N];
void dfs(int u,int pr,vector<int>&p)
{
	sz[u]=1;now.push_back(u);vis[u]=1;
	p.push_back(u);
	for(auto v:pre[u])
	{
		dep[v]=dep[u]+1;
		p.size()>k?dfs(v,pr+1,p):dfs(v,pr,p);
		p.pop_back();sz[u]+=sz[v];
	}
	ans[u]+=sz[u];
	if(pr>=0)ans[p[pr]]-=sz[u];
}
inline void ad(int l,int r,int v){dif[l]+=v,dif[r+1]-=v;}
inline void add(int l,int r,int len,int v)
{
	l<=r?ad(l,r,v):(ad(l,len,v),ad(1,r,v));
}
inline void work(int u)
{
	int v=to[u],cnt=0;
	while(u!=v)u=to[u],v=to[to[v]];
	for(;!vis[u];u=to[u])vis[u]=1,a[++cnt]=u,dif[cnt]=0,id[u]=cnt;
	for(int p=to[u];;p=to[p])
	{
		++ans[p];now=vector<int>(1,p);
		for(auto v:pre[p])
			if(!vis[v]){vector<int>a(1,p);dep[v]=1;dfs(v,-1,a);ans[p]+=sz[v];}
		int mx=0;
		for(auto v:now)num[dep[v]]=0,mx=max(mx,dep[v]);
		for(auto v:now)++num[dep[v]];
		for(int i=1;i<=mx;++i)num[i]+=num[i-1];
		int sl=min(k-mx,cnt-1),pt=a[(id[p]+sl)%cnt+1],t;
		if(sl>0)add(id[p]%cnt+1,(id[p]+sl-1)%cnt+1,cnt,num[mx]),t=mx-1;
		else t=min(k-1,mx),pt=to[p];
		while(~t&&pt!=p)ans[pt]+=num[t],--t,pt=to[pt];
		if(p==u)break;
	}
	for(int i=1;i<=cnt;++i)dif[i]+=dif[i-1];
	for(int i=1;i<=cnt;++i)ans[a[i]]+=dif[i];
}
int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;++i)
	{
		scanf("%d",&to[i]);
		pre[to[i]].push_back(i);
	}
	for(int i=1;i<=n;++i)if(!vis[i])work(i);
	for(int i=1;i<=n;++i)printf("%d
",ans[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/zmyzmy/p/14195028.html