2022“杭电杯”中国大学生算法设计超级联赛(6)(6,10,12)

第6题

给定一棵n个节点的树,点权未知且各不相同。定义点 u 的 bu 为mex(sub(u))。其中mex(集合),值为其集合中最小的未出现的非负整数。sub(u)为子树中的点权的集合。求b的和的最大值。当某个叶子节点的权值为0时,从根节点到该叶节点的那一条链上的mex(u)最大可以为sub(u)的值,所以遍历整个树取最大值。

我们用了好久才看懂MEX,所以也就慢了一点,就是遍历整个树,寻找一条链上的mex(u)的最大值就可以了,不过为了减少循环量,可以直接用2次遍历,小小的优化。

#include<bits/stdc++.h>
using namespace std;
int n;
const int N=5e5+10;
int h[2*N],e[2*N],ne[2*N],idx;
long long siz[N],siz1[N];
void add(int a,int b){
	e[++idx]=b;
	ne[idx]=h[a];
	h[a]=idx;
}
void dfsz(int u,int fa){
	siz[u]=1;
	for(int i=h[u];i!=-1;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfsz(j,u);
		siz[u]+=siz[j];
	}
}
void dfs(int u,int fa){
	
	siz1[u]=siz[u]+siz1[fa];
	
	for(int i=h[u];i!=-1;i=ne[i]){
		int j=e[i];
		if(j==fa) continue;
		dfs(j,u);
	}
}

void solve(){
	memset(h,-1,sizeof h);
	memset(e,0,sizeof e);
	memset(ne,0,sizeof ne);
	
	memset(siz,0,sizeof siz);
	memset(siz1,0,sizeof siz1);
	idx=0;
	cin>>n;
	for(int i=1;i<n;i++){
		int a,b;
		cin>>a>>b;
		add(a,b),add(b,a);
	}
	dfsz(1,0);
	
	dfs(1,0);
	
	long long ans=0;
	for(int i=1;i<=n;i++){
		if(siz1[i]>ans) ans=siz1[i];
	}
	cout<<ans<<endl;
}
int main()
{
	cin.tie(0);
	ios::sync_with_stdio(false);
	int t;
	cin>>t;
	while(t--){
		solve();
	}

}

第10题

我们说无向图是平面图,如果它存在一种在平面上绘制它的方法,使得除了端点之外没有两条边有交点。例如,下图是一个平面图:

但是下面的这张图不是平面图,因为可以证明,无论如何在平面上绘制此图,至少有两条边具有不是端点的交集:

对于平面图,它具有一些由边分隔的区域。例如,下面的平面图具有44区域(注意该区域11是外部的无限平面):

给你一个平面图nn顶点和mm边缘。每个地区设定一个国家。你是设计师,你想在边缘上建造一些隧道,这样:从一个城市出发,你可以通过一些隧道或经过一些城市(即,除非它设置隧道,否则你不能越过一个边缘)。例如,对于上图,您可以像这样构建隧道:

在上图中,您可以从城市出发22到城市33通过隧道11,经过城市11,然后通过隧道33,经过城市44,终于通过隧道22,你可以到达城市33.你可以检查从任何城市你可以旅行到任何其他城市。

您希望隧道的数量尽可能小。打印最小隧道数和在其上构建隧道的边的 ID。

这道题目我没有特别搞懂,是队友带飞AC的,比赛的时候我连题目都没看wwww。

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

const int N=1e5+7;
const int M=2e5+7;

int f[N];

struct node{
    int st,en;
}edge[M];

int getf(int x){
    if(f[x]==x)return x;
    return f[x]=getf(f[x]);
}
void solve(){
    bool is[M];
    memset(is,0,sizeof(is));
    int ans=0,num[M];
    int n,m,u,v;
    cin>>n>>m;
    for(int i=1;i<=m;i++){
        cin>>u>>v;
        edge[i].st=u;
        edge[i].en=v;
    }
    ans=m;
    int cnt=m;
    for(int i=1;i<=n;i++)f[i]=i;
    int k=1;
    int fta,ftb;
    for(int i=m;i>=1;i--){
        fta=getf(edge[i].st);
        ftb=getf(edge[i].en);
        if(fta!=ftb){
            f[fta]=ftb;
            cnt--;
            is[i]=1;
        }
    }
    cout<<cnt<<endl;
    for(int i=1;i<=m;i++){
        if(is[i]==0)cout<<i<<' ';
    }
    cout<<endl;
}

int main(){
    cin.tie(0);
    ios::sync_with_stdio(false);
    int t;
    cin>>t;
    while(t--)solve();
}

第12题

给定一个数组 a ,可以执行一个操作k次。操作为选择一个区间 [L,R],让区间整体左移一位,a[L]到a[R]的位置上。求a的最大字典序。

明显就是贪心。这个操作等价于选择一个数,然后把它放到后面的任意一个位置。但我们也是这种思路,不知道为什么一直在wa,最后也没写出来。

#include<bits/stdc++.h>
using namespace std;
int t,n,k,a[300005],num,ti;
struct op{
	int x,p;
}; 
op b[300005];
bool flag;
bool cmp(int x,int y)
{
	if (x>y)
		return 1;
	else
		return 0;
}
bool cmpt(op x,op y)
{
	if (x.x>y.x)
		return 1;
	else if (x.x==y.x && x.p<y.p)
		return 1;
	return 0;
}
int main()
{
 	scanf("%d",&t);
	for (int i=1;i<=t;i++){
		scanf("%d%d",&n,&k);
		for (int j=1;j<=n;j++)
			scanf("%d",&a[j]);
	/*	if (k>=n*n){
			sort(a+1,a+1+n,cmp);
			for (int j=1;j<=n;j++)
				printf("%d",a[j]);
			continue;
		}*/
		num=0;
		while(k--){
		
			for (int j=1;j<n;j++){	
			    flag=true;
				if (a[j]==-1)
					continue;
				int x=j+1;
				while (a[x]==-1)
					x++;
				if (a[j]<a[x]){
					num++;
					b[num].x=a[j]; 
					b[num].p=j;
					a[j]=-1;
					flag=false;
					break;	
				}
			}
			if (flag)
				break;
		}
		sort(b+1,b+num+1,cmpt);
	//	for (int j=1;j<=num;j++)
	//	 	cout<<b[j].x<<" ";
	ti=0;
		for (int j=1;j<=n;j++){
			if (a[j]==-1)
				continue;
			for (int l=1;l<=num;l++)
			{
				if (b[l].x>a[j] && b[l].x!=-1 && b[l].p<j){
					if (ti!=n-1)
						{
						cout<<b[l].x<<" ";
						ti++;
					//	cout<<ti<<" ";
					     }
				else
						cout<<b[l].x;
					b[l].x=-1;
				}
			}
			if (ti!=n-1)
						{
						cout<<a[j]<<" ";
						ti++;
				//			cout<<ti<<" ";
					     }
					else
						cout<<a[j];
		
		}
		for (int j=1;j<=num;j++)
		    if (ti!=n-1)
						{
				   	cout<<b[j].x<<" ";
						ti++;
				//			cout<<ti<<" ";
					     }
					else
						cout<<b[j].x;
		}
	return 0;	
} 

但看其他大佬的代码,明显会更简洁清晰,所以能搞懂,但确实不知道自己哪错了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 3e5 + 5;
const int Max = 0x3f3f3f3f;
int t, n, k;
int a[maxn];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin >> t;
    while (t--)
    {
        cin >> n >> k;
        vector<int> sta, res;
        priority_queue<int> q;
        for (int i = 1; i <= n; i++)
            cin >> a[i];
        for (int i = 1; i <= n; i++)
        {
            while (sta.size() && sta.back() < a[i] && k)
            {
                q.push(sta.back());
                sta.pop_back();
                k--;
            }
            sta.push_back(a[i]);
        }
        int id = 0;
        sta.push_back(-Max);
        q.push(-Max);
        while (res.size() < n)
        {
            if (sta[id] >= q.top())
                res.push_back(sta[id]), id++;
            else
                res.push_back(q.top()), q.pop();
        }
        cout << res[0];
        for (int i = 1; i < res.size(); i++)
            cout << " " << res[i];
        cout << endl;
    }
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值