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 l,r )。
思路: 找规律。要使时间最少,就要尽量每个锅平摊时间。通过找规律可以发现时间最少的情况是每个锅煎 a v e = m a x ( 最 大 值 , c e i l ( t [ i ] 总 时 间 / m 锅 的 个 数 ) ) ave=max(最大值,ceil(t[i]总时间/m锅的个数)) ave=max(最大值,ceil(t[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)
(d−1)∗(d−1) 的同一单位方格中,找其中是否存在没有被矩形覆盖的空白点即为所求点。转移方法:矩阵取模,因为所有点都能看成是(x+kd,y+kd),并且一个矩阵转移前后在单位方格中相对位置是一样的,且至多分成两个或四个小矩阵。
![请添加图片描述](https://img-blog.csdnimg.cn/6e8eeb1ea5ad4e678b435fba5955e95d.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzYwMjQzOTE1,size_16,color_FFFFFF,t_70)
②利用扫描线把矩阵的所有边扫进去,并且标记入边和出边、cnt=入-出,建立线段树,离散化后分别对各个子区间进行操作(左图中红色为入边,绿色为出边)
![请添加图片描述](https://img-blog.csdnimg.cn/6f1eca6509dd4771a30be066dfe04957.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzYwMjQzOTE1,size_16,color_FFFFFF,t_70)
③区间查找,如果存在未被矩形覆盖的空缺点,说明是合法的起始点,只需找出纵坐标输出即可。
#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个构造区间。
思路: 我们把给定区间称为有效区间,不在给定区间并集范围内的称为无效区间。如果从设法把一个个有效区间交起来着手会有点繁琐和绕,所以正难则反,我们可以把切入口变成有效区间并集=构造区间的交集=无效区间补集的交集 。
![请添加图片描述](https://img-blog.csdnimg.cn/81bdca9cad72446792bf2c222f0e4a9a.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L20wXzYwMjQzOTE1,size_16,color_FFFFFF,t_70/resize,p_30)
如图的红色区间为有效区间,蓝色为无效区间,那么 [ 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;
}