用户工具

站点工具


2020-2021:teams:die_java:front_page_summertrain2

这是本文档旧的修订版!


2020牛客暑期多校训练营(第二场)

训练结果

  • 时间:2020-7-13 12:00~17:00
  • rank:145/1159
  • 完成情况:4/8/11

题解

题目名字

题意

题解

 

H.Happy Triangle

题意

一个multiset,支持如下操作:

  • 插入一个数$x$
  • 从中删除一个数$x$(如果有重复的,只删除一个)
  • 给定$x$,问集合中是否存在两个数$a,b$,使得$a,b,x$组成一个非退化三角形。

题解

补题 by fyh

询问即问是否存在两个数$a,b$,使得$|a-b|<x<a+b$。这个很明显是一个区间覆盖问题,但是因为集合中两两组成的区间是$n^2$级别的,考虑减少区间:$a>b>c$,其中$(a,b)$与$(a,c)$组成的开区间分别是$(a-b,a+b),(a-c,a+c)$。后者是完全被前者包含的,所以对于每一个数,只需要找他的前驱,用线段树维护一下这$n$个区间的并即可。

代码

代码

#include<bits/stdc++.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<int,int> PII;
#define X first
#define Y second
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=200010;
int n,q,tp[maxn],a[maxn],b[maxn],tag[maxn<<2],w[maxn<<2];
multiset <int> S; 
multiset <int>::iterator it,it2;
void pushdown(int L,int R,int o)
{
    int mid=L+R>>1,lo=o<<1,ro=lo|1;
    tag[lo]+=tag[o];tag[ro]+=tag[o];
    w[lo]+=tag[o];w[ro]+=tag[o];
    tag[o]=0;
}
void update(int L,int R,int o,int ql,int qr,int v) 
{
    if(L==ql && R==qr)
    {
        w[o]+=v;
        tag[o]+=v;
        return;
    }
    pushdown(L,R,o);
    int mid=L+R>>1,lo=o<<1,ro=lo|1;
    if(qr<=mid)update(L,mid,lo,ql,qr,v);
    else if(ql>mid)update(mid+1,R,ro,ql,qr,v);
    else update(L,mid,lo,ql,mid,v),update(mid+1,R,ro,mid+1,qr,v);
}
int query(int L,int R,int o,int qx)
{
    if(L==R)return w[o];
    pushdown(L,R,o);
    int mid=L+R>>1,lo=o<<1,ro=lo|1;
    if(qx<=mid)return query(L,mid,lo,qx);
    else return query(mid+1,R,ro,qx);   
}
void insert(int A)
{
    int pos=lower_bound(b+1,b+n+1,A)-b,l,r;
    it=S.lower_bound(A);
    if(it!=S.end() && it!=S.begin())
    {
        it--;it2=it;it++;
        l=lower_bound(b+1,b+n+1,(*it)-*(it2))-b;
        r=lower_bound(b+1,b+n+1,(*it)+*(it2))-b-1;
        if(*it-*it2==b[l])l++;
        update(1,n,1,l,r,-1);
    }
    if(it!=S.end())
    {
        l=lower_bound(b+1,b+n+1,*it-A)-b;
        r=lower_bound(b+1,b+n+1,*it+A)-b-1;
        if(*it-A==b[l])l++;
        update(1,n,1,l,r,1);
    }
    if(it!=S.begin())
    {
        it--;it2=it;it++;
        l=lower_bound(b+1,b+n+1,A-*it2)-b;
        r=lower_bound(b+1,b+n+1,*it2+A)-b-1;
        if(A-*it2==b[l])l++;
        update(1,n,1,l,r,1);
    } 
    S.insert(A);
}
void delt(int A)
{
    int pos=lower_bound(b+1,b+n+1,A)-b,l,r;
    it=S.find(A);
    S.erase(it);
    it=S.lower_bound(A);
    if(it!=S.end() && it!=S.begin())
    {
        it--;it2=it;it++;
        l=lower_bound(b+1,b+n+1,(*it)-*(it2))-b;
        r=lower_bound(b+1,b+n+1,(*it)+*(it2))-b-1;
        if(*it-*it2==b[l])l++;
        update(1,n,1,l,r,1);
    }
    if(it!=S.end())
    {
        l=lower_bound(b+1,b+n+1,*it-A)-b;
        r=lower_bound(b+1,b+n+1,*it+A)-b-1;
        if(*it-A==b[l])l++;
        update(1,n,1,l,r,-1);
    }
    if(it!=S.begin())
    {
        it--;it2=it;it++;
        l=lower_bound(b+1,b+n+1,A-*it2)-b;
        r=lower_bound(b+1,b+n+1,*it2+A)-b-1;
        if(A-*it2==b[l])l++;
        update(1,n,1,l,r,-1);
    } 
}
void ask(int x)
{
    int pos=lower_bound(b+1,b+n+1,x)-b;
    if(query(1,n,1,pos))puts("Yes");
    else puts("No");
}
int main()
{
    q=read();
    for(int i=1;i<=q;i++)tp[i]=read(),a[i]=b[i]=read();
    sort(b+1,b+q+1); 
    n=unique(b+1,b+q+1)-b-1;
    b[n+1]=1e9;
    for(int i=1;i<=q;i++)
    {
        if(tp[i]==1)insert(a[i]);
        else if(tp[i]==2)delt(a[i]);
        else ask(a[i]);
    }
    return 0;
}

题目名字

题意

题解

 

J.Just Shuffle

题意

一个排列初始时是$1,2,\ldots,n$,存在某种置换$p$,使得排列在置换$k$次后的排列为$A_1,\ldots,A_n$。求置换$p$。

题解

solved by fyh

本题的一大特性是$k$是质数,也就是$k$模任何数都非0。

置换其实就是若干个循环。每个循环都是独立的,现考虑某个长度为$size$的环,置换$k$次的结果是等效于置换为$k\%size$的结果。设$k\%size=m$,则我们当前得到的环其实是走$m$步的结果,我们要得到走1步的结果,便可以考虑在这个环上每步都好几倍,最后等效为走一步,即解$m*k\%size=1$的模方程的$x$。至此,我们成功构造出了原置换。

代码

代码

#include<bits/stdc++.h>
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
typedef long long LL;
typedef pair<int,int> PII;
#define X first
#define Y second
inline int read()
{
    int x=0,f=1;char c=getchar();
    while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
    while(isdigit(c)){x=x*10+c-'0';c=getchar();}
    return x*f;
}
const int maxn=100010;
int n,k,to[maxn],col[maxn],cnt,size[maxn],ans[maxn];
bool vis[maxn];
vector <int> V[maxn];
int main()
{
    n=read();k=read();
    for(int i=1;i<=n;i++)to[i]=read();
    for(int i=1;i<=n;i++)
        if(!vis[i])
        {
            col[i]=++cnt;
            V[cnt].push_back(i);
            int now=i;
            while(!vis[to[now]])
                V[cnt].push_back(to[now]),
                vis[to[now]]=1,col[to[now]]=cnt,
                now=to[now],size[cnt]++;
        }
    for(int i=1;i<=cnt;i++)
    {
        if(size[i]==1)ans[V[i][0]]=V[i][0];
        else
        {
            int m=k%size[i],x=0;
            while(x*m%size[i]!=1)x++;
            for(int j=0;j<V[i].size();j++)
            {
                int now=V[i][j];
                ans[V[i][j]]=V[i][(j+x)%size[i]];
            }
        }
    }
    for(int i=1;i<n;i++)printf("%d ",ans[i]);
    printf("%d\n",ans[n]);
    return 0;
}

I.Interval

题意

对一个区间$[l,r]$可进行两种操作:

1、将$[l,r]$变为$[l-1,r]$或$[l+1,r]$

2、将$[l,r]$变为$[l,r-1]$或$[l,r+1]$

现在有$m$个限制,限制区间$[l_i,r_i]$不能进行操作$c$($c=L$或$c=R$),但是开启这个限制需要$w_i$的费用

问最少的费用花费,使得区间$[1,n]$不能转移到任意一个区间$[l,r]$使$l=r$

题解

补题 solved by hxm

将每个区间看做二维平面上的点,我们的目标就是阻止从$(1,n)$走到任意一个$(x,x)$

显然相邻的点可以连边,我们把$(1,n)$看做源点的话,新建一个汇点将所有$(x,x)$连向汇点,那么这就是一个最小割问题

但是会T。

发现这是一个平面图,转化为对偶图的最短路即可

代码

代码

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#include<cmath>
#include<map>
#include<set>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define cls(s,v) memset(s,v,sizeof(s))
#define mp(a,b) make_pair<int,int>(a,b)
#define cp pair<int,int>
using namespace std;
const int maxn = 250005,maxm = 100005;
const LL INF = 1000000000000000000ll;
inline int read(){
	int out = 0,flag = 1; char c = getchar();
	while (c < 48 || c > 57){if (c == '-') flag = 0; c = getchar();}
	while (c >= 48 && c <= 57){out = (out << 1) + (out << 3) + c - 48; c = getchar();}
	return flag ? out : -out;
}
int h[maxn],ne;
struct EDGE{
	int to,w,nxt;
}ed[maxn * 2];
void build(int u,int v,int w){
	ed[++ne] = (EDGE){v,w,h[u]}; h[u] = ne;
	ed[++ne] = (EDGE){u,w,h[v]}; h[v] = ne;
	//printf("build %d to %d costs %d\n",u,v,w);
}
int C[505][505],R[505][505];
int n,m,S,T;
int id(int x,int y){
	return x * (x - 1) / 2 + y;
}
LL d[maxn],vis[maxn]; 
struct node{int u; LL d;};
inline bool operator <(const node& a,const node& b){return a.d > b.d;}
priority_queue<node> q;
void dijkstra(){
	for (int i = 1; i <= T; i++) d[i] = INF,vis[i] = false;
	d[S] = 0;
	node u;
	q.push((node){S,d[S]});
	while (!q.empty()){
		u = q.top(); q.pop();
		if (vis[u.u]) continue;
		vis[u.u] = true;
		Redge(u.u) if (!vis[to = ed[k].to] && d[to] > d[u.u] + ed[k].w){
			d[to] = d[u.u] + ed[k].w;
			q.push((node){to,d[to]});
		}
	}
}
int main(){
	n = read(); m = read();
	int l,r,w; char c;
	for (int i = 1; i <= m; i++){
		l = read(); r = read(); scanf("%c",&c); w = read();
		if (c == 'L') R[l][r] = w;
		else C[l][r] = w;
	}
	for (int i = 1; i <= n - 1; i++)
		for (int j = 1; j <= i; j++){
			//puts("LXT");
			//printf("[%d,%d] [%d,%d]\n",i + 1,j + 1,i + 1,j);
			//cout << C[i + 1][j + 1] << ' ' << R[i + 1][j] << endl;
			if (j < i && C[j + 1][i + 1]) build(id(i,j),id(i,j + 1),C[j + 1][i + 1]);
			if (i < n - 1 && R[j][i + 1]) build(id(i,j),id(i + 1,j),R[j][i + 1]);
		}
	S = 0; T = n * (n - 1) / 2 + 1;
	for (int i = 1; i <= n - 1; i++) if (C[1][i + 1]) build(S,id(i,1),C[1][i + 1]);
	for (int i = 1; i <= n - 1; i++) if (R[i][n]) build(id(n - 1,i),T,R[i][n]);
	dijkstra();
	if (d[T] != INF) printf("%lld\n",d[T]);
	else puts("-1");
	return 0;
}

训练实况

开场 发现D很简单
12:08 hxm 过D wxg想出B题做法 fyh开写B
12:08~12:50 fyh狂waB wxg hxm想出C hxm开写C
13:33 hxm过C 在想B的错误和F,尝试用分数处理精度问题 改为wxg写B
13:33~14:30 B卡常 又wa又T hxm想出F 开写
15:04 hxm过F wxg继续尝试B,放弃 ,wxg开想G
15:04~16:00 ??
16:00 看J题过得人多 fyh想J
16:36 fyh 过J wxg写G,未知原因段错误,结束。

训练总结

因为很少人过,没有读I
K题因为过的人少没有深想
H没有仔细想
B陷入无解,纠结太久
总结:计算几何最后再写 对榜的利用价值??

2020-2021/teams/die_java/front_page_summertrain2.1594977798.txt.gz · 最后更改: 2020/07/17 17:23 由 mychael