训练实录 | 2021牛客暑期多校训练营6

2021牛客暑期多校训练营6

传送门

C - Delete Edges(待补)

听说有论文文献,里面直接有给出结论可以用,先鸽一下。。。

F - Hamburger Steak

solved by oye && Micky. (after)

题意: 给出 n n n 个汉堡和 m m m 个锅,再给出 n n n 行表示第 i i i 个汉堡煎熟所需时间 t [ i ] t[i] t[i] ,找出时间最少的方案(如有多种输出任意一种),输出 n n n 行每行包含四个参数“k id l r”,第 i i i 行表示第 i i i 个汉堡需要煎几次( k k k ),在几号锅煎( i d id id ),煎的时间区间( l , r l,r lr )。

思路: 找规律。要使时间最少,就要尽量每个锅平摊时间。通过找规律可以发现时间最少的情况是每个锅煎 a v e = m a x ( 最 大 值 , c e i l ( t [ i ] 总 时 间 / m 锅 的 个 数 ) ) ave=max(最大值,ceil(t[i]总时间/m锅的个数)) ave=maxceilt[i]/m 。因为不管是完整地煎完一整个汉堡还是分开煎两次,在锅上留的时间都一样,所以就不需要过于纠结汉堡的完整性和顺序,直接想象成按汉堡序号依次先在一个锅煎满ave时间后完成使命换下一个锅继续煎,然后模拟一下输出即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn=1e5+5;
ll n,m,ave,sum=0,t[maxn],maxx,l=0,id=1;
int main()
{
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&t[i]);
        sum+=t[i];
    }
    maxx=*max_element(t+1,t+1+n);
    ave=ceil(1.0*sum/m);
    ave=max(maxx,ave);
    ll i=1;
    while(i<=n)
    {
        if(l+t[i]<ave)
        {
            printf("1 %lld %lld %lld\n",id,l,l+t[i]);
            l+=t[i];
            i++;
        }
        else if(l+t[i]==ave)
        {
            printf("1 %lld %lld %lld\n",id,l,ave);
            l=0;
            id++;
            i++;
        }
        else
        {
            printf("2 %lld 0 %lld %lld %lld %lld\n",id+1,l+t[i]-ave,id,l,ave);
            l=l+t[i]-ave;
            id++;
            i++;
        }
    }
    return 0;
}

H - Hopping Rabbit

solved by YukiSam && Micky. (after)

题意: 给出n个矩形陷阱,每行给出矩形的左下角和右上角坐标,一兔子步长为d,可上下左右按照步长跳,问是否存在一个起始点使得兔子无论怎么跳都不会进入陷阱,有则输出坐标。

思路: 线段树+扫描线。
①将所有矩形转移到第一象限中 ( d − 1 ) ∗ ( d − 1 ) (d-1)*(d-1) d1d1 的同一单位方格中,找其中是否存在没有被矩形覆盖的空白点即为所求点。转移方法:矩阵取模,因为所有点都能看成是(x+kd,y+kd),并且一个矩阵转移前后在单位方格中相对位置是一样的,且至多分成两个或四个小矩阵。


请添加图片描述

②利用扫描线把矩阵的所有边扫进去,并且标记入边和出边、cnt=入-出,建立线段树,离散化后分别对各个子区间进行操作(左图中红色为入边,绿色为出边)


请添加图片描述

③区间查找,如果存在未被矩形覆盖的空缺点,说明是合法的起始点,只需找出纵坐标输出即可。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
const int maxn=1e5+5;
int n,d;
int subl,subr,subf;
//结构体subseg存所有子区间的信息,len为区间长度,cnt为所有入边-出边之差,下标为区间编号
struct subseg
{
    int len,cnt;
}sub[maxn*4];
//结构体seg存扫描线的下界、上界、标记(入边为1,出边为-1)
struct seg
{
    int l,r,f;
};
vector<seg>s[maxn];   //开vector存扫描线
//记录矩阵的入边和出边两条扫描线
void add(int x1,int x2,int y1,int y2)
{
    s[x1].pb( (seg){y1,y2,1} );
    s[x2+1].pb( (seg){y1,y2,-1} );
}
//区间合并
void pushup(int l,int r,int root)
{
    if(sub[root].cnt)  sub[root].len=r-l+1;
    else if(l==r)  sub[root].len=0;  //最小单位子区间
    else sub[root].len=sub[2*root].len+sub[2*root+1].len;  //合并自己向下的子区间长度
}
//离散化 并存储区间信息
void change(int l,int r,int root)
{
    if(subl<=l && subr>=r)
    {
        sub[root].cnt+=subf;
        pushup(l,r,root);
        return;
    }
    int mid=(l+r)/2;
    if(subl<=mid)  change(l,mid,2*root);
    if(mid<subr)  change(mid+1,r,2*root+1);
    pushup(l,r,root);
}
//获取合法点的纵坐标
void gety(int l,int r,int root)
{
    if(sub[root].len==0)
    {
        printf("%d\n",l);
        return;
    }
    int mid=(l+r)/2;
    if(sub[2*root].len<mid-l+1)  gety(l,mid,2*root);
    else gety(mid+1,r,2*root+1);
}
int main()
{
    scanf("%d%d",&n,&d);  //n矩形个数  d单位长度
    //将所有矩阵取模分块后转化到同一单位方格中
    for(int i=0;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;
        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,0,y2),add(0,x2,y1,d-1),add(x1,d-1,y1,d-1);
        }
    }
    for(int i=0;i<d;i++)
    {
        for(int j=0;j<s[i].size();j++)
        {
            subl=s[i][j].l , subr=s[i][j].r , subf=s[i][j].f;
            change(0,d-1,1);
        }
        if(sub[1].len<d)  //说明存在未被覆盖的空缺点
        {
            printf("YES\n%d ",i);
            gety(0,d-1,1);
            return 0;
        }
    }
    printf("NO\n");
    return 0;
}

I - Intervals on the Ring

solved by oye && Yuki Sam. 04:38:20(-8)

题意: 给出1~n的数环,依次排列,其中1和n首尾相邻。T组数据,每组给出n和m,再给出m行,每行有 l l l r r r 表示给定区间。现要求构造k个区间,使得给定区间的并集=构造区间的交集。输出k和k个构造区间。

思路: 我们把给定区间称为有效区间,不在给定区间并集范围内的称为无效区间。如果从设法把一个个有效区间交起来着手会有点繁琐和绕,所以正难则反,我们可以把切入口变成有效区间并集=构造区间的交集=无效区间补集的交集


请添加图片描述

如图的红色区间为有效区间,蓝色为无效区间,那么 [ l , r ] [l,r] [l,r]就是这一个无效区间的补集, 那么其他无效区间的补集也同理可以这样表示,我们就只要输出前后两个区间中间的无效区间的补集即可(前提是存在)。注意要特判首尾衔接处。

#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
int t,n,m,k;
struct node
{
    int l,r;
}a[maxn],b[maxn];
bool cmp(node a,node b)
{
    if(a.l==b.l)  return a.r<b.r;
    return a.l<b.l;
}
int main()
{
    scanf("%d",&t);
    while(t--)
    {
        int cnt=0;
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++)
            scanf("%d%d",&a[i].l,&a[i].r);
        sort(a+1,a+1+m,cmp);
        for(int i=2;i<=m;i++)
        {
            if(a[i].l-a[i-1].r>1)
            {
                cnt++;
                b[cnt].l=a[i].l,b[cnt].r=a[i-1].r;
            }
        }
        if((a[1].l-a[m].r>1 && a[m].l>a[m].r) || (n-a[m].r+a[1].l-1>=1 && a[m].l<=a[m].r))
        {
            cnt++;
            b[cnt].l=a[1].l,b[cnt].r=a[m].r;
        }
        if(cnt==0)  printf("1\n%d %d\n",a[1].l,a[m].r);
        else
        {
            printf("%d\n",cnt);
            for(int i=1;i<=cnt;i++)
               printf("%d %d\n",b[i].l,b[i].r);
        }
    }
    return 0;
}

J - Defend Your Country

solved by Micky(after)

题意:给出一个联通的无向图,每个点都有权值。定义一个联通分量的权值是这个联通分量的点的权值的和,如果连通分量有奇数个点,权值再乘-1。
现在要删除图中的边,使得所有连通图的权值之和最大,可以不删最大。

思路:如果初始点数是偶数,显然什么都不删权值最大, 最大权值为所有点权之和。

那么如果是奇数,我们必须删除点使得一些连通图的点数为偶数。

因为已经知道了,初始的图是连通图,那么如果我们移出一个点,这个图的连通分量变成2(即删除一个非割点)。那么两个连通分量一个点数是1,另一个点数是n-1。如果再移出非割点只会让权值变得更小,只移出非割点,最多删一个。
如果移出一个点是割点,那么会让图变成三个连通分量。如果除了这个割点有一个连通分量的点数是奇数,那么另一个联通分量点数也是奇数。都是奇数,不可能是最优结果。如果一个联通分量点数是偶数,那么另一个连通分量点数也是偶数。最终权值与上面只移出一个非割点的情况一样。所以要保证移出割点的点数能让剩下的两个联通分量的点数为偶数。
如果移出多个点显然不能达到最优情况,因为每次移除一个点,这个点都会变成一个含有一个点的联通分量。都会使最终结果变小。

可以的出结论,如果n为偶数,不用删边。如果n为奇数,要么删一个割点连的边或一个非割点连的边,如果删除割点连的边,那么要保证这个删完后,生成的两个联通分量点数都为偶数。

用tarjan求出割点,再求割点的过程中记录每个点作为根时,子树节点的数量,用sz记录。

#pragma warning(disable:4996)
#include<iostream>
#include<string.h>
#include<queue>
#include<stack>
#include<math.h>
#include<map>
#include<set>
#include<algorithm>
#include<sstream>
#include<vector>
#include<ctype.h>
#include<list>
//#include <unordered_map>
#include<deque>
#include<functional>
using namespace std;
#define ll long long
const int N = 1e6 + 9;
const int INF = 0x3f3f3f3f;

int n, m;
int w[N];
vector<int>g[N];
int dfn[N], low[N], cut[N], deep, tag[N], sz[N];

void tarjan(int u, int r) {  // v:当前点    r:本次搜索树的root
	dfn[u] = low[u] = ++deep;
	ll child = 0;
	sz[u] = 1;
	for (auto v : g[u]) {
		if (!dfn[v]) {
			tarjan(v, u);    //以u为根节点往子节点继续搜索。
			sz[u] += sz[v];  //加上u的子节点的子树的节点数。
			if (low[v] == dfn[u]) {//v不是根而且他的孩子无法跨越他(u)回到祖先
				if (r)cut[u] = 1;
				if (sz[v] % 2)tag[u] = 1; //v是u的子节点,sz[v]是以v为根点节点数。
			}
			low[u] = min(low[u], low[v]);
			if (!r)child++; //如果是搜索树的根,统计子树数目。
		}
		else low[u] = min(low[u], dfn[v]);
	}
	if (child >= 2)cut[u] = 1;
}


int main(void)
{
	int t;
	scanf("%d", &t);
	while (t--) {
		scanf("%d%d", &n, &m);
		long long sum = 0;
		for (int i = 1; i <= n; i++) {
			scanf("%d", &w[i]);
			sum += w[i];
		}
		for (int i = 0; i < m; i++) {
			int x, y;
			scanf("%d%d", &x, &y);
			g[x].push_back(y);
			g[y].push_back(x);
		}

		if (n % 2 == 0) {
			printf("%lld\n", sum);
		}
		else {
			int ans = 1e9;
			tarjan(1, 0);
			for (int i = 1; i <= n; i++ ) {
				if (!cut[i] || !tag[i]) {
					ans = min(ans, w[i]);
				}
			}
			printf("%lld\n", sum - 2 * ans);
		}
		for (int i = 0; i <= n; i++) {
			dfn[i] = low[i] = cut[i] = deep = tag[i] = sz[i] = 0;
			g[i].clear();
		}
	}
	return 0;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值