【题目记录】——2021牛客暑期多校训练营6


题目集地址 2021牛客暑期多校训练营6
这次题目集做的有点差,只做出了I题

C Delete Edges 思维

题意:给定一个有 n 个点的完全无向图,每次在其中选出三个点,若这每两个点间的边都存在,则删去这三条边。可操作 m 次,直到该图中剩下的边的个数小于 n 为止。( 操作次数不需要最小 )
思路:开赛时很多人都扑街的一道题,虽然表面上是图论,但考察的内容是如何避免出现重复的两两组合数,因为当删去某个三角形后,其中的任意两点将不能一起再出现在新的三角形内。
由于本题需要保证两两间的组合不重复出现,因此可以在保证输出的三个点中 第三点 > 第二点 > 第一点 的前提下进行暴力枚举即可。
结论题,结论:x+y+z=0(mod n)1<=x<y<z<=n所有解即可
证明过程可以看看下面博客讲的
讲解
我们当时连暴力都不知道怎么暴力。。

AC代码:

#include<bits/stdc++.h>

using namespace std;
const int inf=0x3f3f3f3f;
struct node{
    int x,y,z;
};
vector<node>res;
int main()
{
    freopen("in.txt","r",stdin);
	freopen("out.txt","w",stdout);
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
        for(int j=i+1;j<=n;++j)
        {
            int k=((n-i-j-1+n)%n)%n+1;
            if(k<=j) continue;
            res.push_back({i,j,k});
        }
    printf("%d\n",(int)res.size());
    for(auto x:res)
        printf("%d %d %d\n",x.x,x.y,x.z);
    return 0;
}

F Hamburger Steak 思维+模拟

题目地址【F Hamburger Steak
题意:给n个汉堡和m个锅,给出每个汉堡蒸熟所需的时间,每个汉堡最多在两个锅里面蒸。问最少需要多少时间蒸熟所有汉堡,按照每个汉堡所需时间升序输出,每个汉堡的锅号,以及汉堡在锅里蒸的时间。
思路:先找一个基准值,可以保证能把所有汉堡蒸熟,基准值是汉堡所需时间平均值和汉堡所需时间的最大值中的较大值。然后按顺序蒸汉堡,如果一个锅子无法在基准值时间内将某个汉堡蒸熟(因为这个锅子已经蒸熟了一个其它汉堡),就将这个汉堡分两个锅子蒸,先在下一个锅子蒸一段时间,到当前锅子剩余时间刚好把这个汉堡蒸熟。
AC代码:

/*
** Author:skj
** Time:8-8
** function:牛客第六场F
*/
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5+5;
int ti[maxn];

int main()
{
	//freopen("in.txt","r",stdin);
	//freopen("out.txt","w",stdout);
	int n, m;
	scanf("%d%d",&n,&m);
	ll basevalue = 0;
	int ma = 0;       //时间最大值
	for(int i = 1;i <= n;i++)
    {
        scanf("%d",&ti[i]);
        basevalue += ti[i];
        ma = max(ma,ti[i]);
    }
    int k = 0;

    if(basevalue % m)
    {
        k=1;
    }
    basevalue /= m;
    basevalue += k;
    basevalue = ma > basevalue?ma:basevalue;

    ll t1 = 0;  //当前锅子用过的时间
    int num = 1;//锅子编号
    for(int i = 1;i <= n;i++)
    {
        if(ti[i] <= basevalue-t1)
        {
            printf("1 %d %lld %lld\n",num,t1,t1+ti[i]);
            t1 += ti[i];
            if(t1 == basevalue)
            {
                num++;
                t1 = 0;
            }
        }
        else
        {
            printf("2 %d 0 %lld %d %lld %lld\n",num+1,ti[i]-basevalue+t1,num,t1,basevalue);
            t1 = ti[i]-basevalue+t1;
            num++;
        }
    }
    return 0;
}

H Hopping Rabbit 扫描线

题目地址H Hopping Rabbit
题意:兔子在草原上跳跃,需要躲避陷阱。草原视为二维平面,有 n ( 1 ≤ n ≤ 1 0 5 ) n(1\le n \le 10^5) n(1n105)个陷阱,陷阱可以看成平面上的矩形。兔子可以平行于x轴或y轴跳跃,每次跳跃距离为 d ( 1 ≤ d ≤ 1 0 5 ) d(1 \le d \le 10^5) d(1d105)
兔子希望找到一个起始点( x 0 + 0.5 , y 0 + 0.5 x_0+0.5,y_0+0.5 x0+0.5,y0+0.5),无论他怎么跳,都不会跳到陷阱内。如果存在这样的点,则输出“YES”和任意解,否则输出“NO”
思路:看起来跟矩形覆盖有关,联想到扫描线,扫描线暂时不太会,先贴一篇题解吧。。
2021牛客暑期多校训练营6-H题
AC代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<vector>
#define ll long long
using namespace std;
const int N=1e5+7;
int n,d,ql,qr,x;
struct node1{
	int s,len;//s表示tr[i]是否被覆盖,len表示有效长度 
}tr[N*4];
struct node2{
	int l,r,x;
};
vector <node2> vt[N];
void pushup(int l,int r,int rt)
{
	if(tr[rt].s) tr[rt].len=r-l+1;//tr[rt]被覆盖,长度为r-l+1 
	else if(l==r) tr[rt].len=0;//若只有一个点且未被覆盖,长度标记为0 
	else tr[rt].len=tr[rt*2].len+tr[rt*2+1].len;
}
void add(int x1,int x2,int y1,int y2)//添加新的覆盖
{
	vt[x1].push_back((node2){y1,y2,1});
	vt[x2+1].push_back((node2){y1,y2,-1});
}
void change(int l,int r,int rt)
{
	if(ql<=l && r<=qr)
	{
		tr[rt].s+=x;//改变状态
		pushup(l,r,rt);
		return;
	}
	int mid=(l+r)/2;
	if(ql<=mid) change(l,mid,2*rt);
	if(mid<qr) change(mid+1,r,2*rt+1);
	pushup(l,r,rt);
}
void search(int l,int r,int rt)
{
	if(tr[rt].len==0)//当前点无覆盖,为一种答案 
	{
		printf("%d\n",l);
		return;
	}
	int mid=(l+r)/2;
	if(tr[2*rt].len<mid-l+1) search(l,mid,2*rt);
	else search(mid+1,r,2*rt+1);
}

int main()
{
	scanf("%d%d",&n,&d);
	for(int i=1;i<=n;i++)
	{
		int x1,y1,x2,y2;
        scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		x2--; 
		y2--;
		if(x2-x1+1>=d)
		x1=0,x2=d-1;
		if(y2-y1+1>=d)
		y1=0,y2=d-1;
		
		x1=(x1%d+d)%d;
		x2=(x2%d+d)%d;
		y1=(y1%d+d)%d;
		y2=(y2%d+d)%d;
		//将矩形分解、移动到同一个d*d的正方形内 
		if(x1<=x2){
			if(y1<=y2) add(x1,x2,y1,y2);
			else add(x1,x2,0,y2),add(x1,x2,y1,d-1);
		}
		else{
			if(y1<=y2) add(0,x2,y1,y2),add(x1,d-1,y1,y2);
			else add(0,x2,0,y2),add(x1,d-1,y1,d-1),add(0,x2,y1,d-1),add(x1,d-1,0,y2);
		}
	}
	for(int i=0;i<d;i++)
	{ 
		for(int j=0;j<vt[i].size();j++)//更新 
		{
			ql=vt[i][j].l;
			qr=vt[i][j].r;
			x=vt[i][j].x;
			change(0,d-1,1);
		}
		if(tr[1].len<d)//当扫描线上有效覆盖长度<d时存在一点符合题意 
		{
			puts("YES");
			printf("%d ",i);//输出横坐标 
			search(0,d-1,1);//找到这个点的纵坐标 
			return 0;
		}
	}
	puts("NO");
	return 0;
}

I Intervals on the Ring 思维

题目地址I Intervals on the Ring
题意:给一个数n,代表序列中元素个数,序列为整数1~n-1,序列区间是成环状,l可以大于r。给出几个不相交的区间,要求找出几个区间满足区间的交集是给出区间的并集,输出区间个数和区间。
思路:
将给出的区间的并集全部标记在区间中,因为区间是整数区间,可以用数组来表示序列。
AC代码:

#include<iostream>
#include <bits/stdc++.h>

using namespace std;

int a[1005];
int k[1005];

int main()
{
    int t,n,m;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        int l,r;
        memset(a,0,sizeof(a));
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d",&l,&r);
            if(l<=r)
                for(int j=l;j<=r;j++)
                {
                    a[j]=1;
                }
            else
            {
                for(int j=1;j<=r;j++)
                {
                    a[j]=1;
                }
                for(int j=l;j<=n;j++)
                {
                    a[j]=1;
                }
            }
        }
        int num=0;
        int i;
        int kx=0;
        for(i=1;i<=n;)
        {
            if(a[i]==0)
            {
                i++;
                continue;
            }
            bool bol=true;
            while(1)
            {
                if(bol)
                {
                    bol=false;
                    k[kx]=i;
                    kx++;
                }
                i++;
                if(a[i]==0)
                {
                    k[kx]=i-1;
                    kx++;
                    num++;
                    break;
                }
            }
        }
        bool biao=true;
        if(a[1]==1&&a[n]==1&&num>1)
        {
            num--;
            biao=false;
        }
        if(num>2)
        {
            if(biao)
            {
                printf("%d\n",kx/2);
                for(int kk=0;kk<kx-1;kk+=2)
                {
                    printf("%d %d\n",k[kk],k[(kx-1+kk)%kx]);
                }
            }
            else
            {
                kx-=2;
                printf("%d\n",kx/2);
                printf("%d %d\n",k[kx],k[kx-1]);
                for(int kk=2;kk<kx-1;kk+=2)
                {
                    printf("%d %d\n",k[kk],k[(kx-1+kk)%kx]);
                }
            }
        }
        else if(num==2)
        {
            if(biao)
            {
                printf("2\n%d %d\n%d %d\n",k[2],k[1],k[0],k[3]);
            }
            else
            {
                printf("2\n%d %d\n%d %d\n",k[2],k[1],k[4],k[3]);
            }
        }
        else if(num==1)
        {
            if(biao)
            {
                printf("1\n%d %d\n",k[0],k[1]);
            }
            else
            {
                printf("1\n%d %d\n",k[2],k[1]);
            }
        }
        else
        {
            printf("-1\n");
        }
    }
    return 0;
}

补充:上面是我们现场做题的时候的思路,后来发现这只是一个定律。。。。基础太差了,哭辽。
集合的德摩根律告诉我们 ⋃ A i ‾ = ⋂ A i ‾ \overline{\bigcup A_i}=\bigcap \overline{A_i} Ai=Ai(区间的并的补等于区间的补的交),先找到所有给出区间的并的补区间,然后输出每一段区间的补区间即可。
从网上找来的简单解法。

#include <bits/stdc++.h>
#define PII pair<int, int>
#define ll first
#define rr second
#define endl '\n'
using namespace std;
const int N = 1010;
PII a[N];
void solve()
{
    int n, m;
    cin >> n >> m;
    for(int i = 0;i < m;i ++)
        cin >> a[i].ll >> a[i].rr;
    sort(a, a+m);
    cout << m << endl;	//有多少个给出的区间就有多少个区间的并的补区间,每一段补区间都有一个要输出的补区间。
    for(int i = 0;i < m;i ++)
    {
        if(i) cout << endl;
        cout << a[i].ll << ' ' << a[(i-1+m) %m].rr;
    }
}
int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        solve();
        if(T) printf("\n");
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值