ARC076 Solution Set
前面两个 ABC 级别题没做。
C. Reconciled?
犬猿の仲。
注意到狗和猴不能放一块儿,那显然只能隔一个放一个。记两者数量为 \(n,m\),那么显然 \(|n-m|\leq 1\)。否则答案为 \(0\)。
首先狗和猴可以分别乱排,方案数为 \(n!m!\)。
接下来,如果 \(n=m\),那么狗和猴可以整体换位置,答案再多乘个 \(2\) 就好了。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read()
{
int x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
const int MOD=1e9+7;
inline int Add(int u,int v){return u+v>=MOD?u+v-MOD:u+v;}
inline int Sub(int u,int v){return u-v>=0?u-v:u-v+MOD;}
inline int Mul(int u,int v){return LL(u)*LL(v)%MOD;}
int QuickPow(int x,int p)
{
if(p<0) p+=MOD-1;
int ans=1,base=x;
while(p)
{
if(p&1) ans=Mul(ans,base);
base=Mul(base,base);
p>>=1;
}
return ans;
}
inline int Abs(int x){return x>0?x:-x;}
int n,m,fac[100005];
int main(){
fac[0]=1;
for(int i=1;i<=100000;++i) fac[i]=Mul(fac[i-1],i);
n=read(),m=read();
if(Abs(n-m)>1) return puts("0")&0;
if(n<m) swap(n,m);
if(n==m) write(Mul(Mul(fac[n],fac[n]),2));
else write(Mul(fac[n],fac[m]));
return 0;
}
D. Built?
比较显然,对于 \(i,j,k\) 满足 \(x_i < x_j < x_k\),我不可能连边 \((i,k)\),因为连 \((i,j),(j,k)\) 价格不变且更优。
那么按 \(x,y\) 连一下,最小生成树就好了。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
LL read()
{
LL x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(LL x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
struct node{
LL x,id;
node(){}
node(LL X,LL I){x=X,id=I;}
bool operator < (node ano) const {return x<ano.x;}
}X[100005],Y[100005];
struct unionFindSet{
LL fa[100005];
void makeSet(LL up){for(LL i=0;i<=up;++i) fa[i]=i;}
LL findSet(LL x){return fa[x]==x?x:fa[x]=findSet(fa[x]);}
bool unionSet(LL x,LL y)
{
LL xx=findSet(x),yy=findSet(y);
if(xx==yy) return false;
fa[yy]=xx;
return true;
}
}ufs;
struct Edge{
LL u,v,w;
Edge(){}
Edge(LL U,LL V,LL W){u=U,v=V,w=W;}
bool operator < (Edge ano) const {return w<ano.w;}
}ed[200005];
LL n,m,cnt;
LL Kruskal()
{
ufs.makeSet(n);
sort(ed+1,ed+1+m);
LL ans=0;
for(LL i=1;i<=m;++i)
{
LL u=ed[i].u,v=ed[i].v,w=ed[i].w;
if(ufs.unionSet(u,v)) ans+=w;
}
return ans;
}
int main(){
n=read();
for(LL i=1;i<=n;++i)
{
LL x=read(),y=read();
X[i]=node(x,i),Y[i]=node(y,i);
}
sort(X+1,X+1+n),sort(Y+1,Y+1+n);
for(LL i=2;i<=n;++i)
{
ed[++cnt]=Edge(X[i-1].id,X[i].id,X[i].x-X[i-1].x);
ed[++cnt]=Edge(Y[i-1].id,Y[i].id,Y[i].x-Y[i-1].x);
}
m=cnt;
write(Kruskal());
return 0;
}
E. Connected?
显然只有两个端点都在边界上才会影响答案。
那么,我们把边界展开抽成一个序列,相当于有 \(O(n)\) 种括号,问是否是一个合法的括号串。乱做就好了。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
LL read()
{
LL x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(LL x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
LL n,R,C;
LL cnt;
inline bool onEdge(LL x,LL y){return x==0 || y==0 || x==R || y==C;}
#define mp make_pair
pair<LL,LL> brk[100005];
LL tmp[200005];
inline LL getId(LL x,LL y)
{
if(x==0) return y;
else if(y==C) return C+1e10+x;
else if(x==R) return C+C-y+2e11+R;
else return 8e12+C-x;
}
pair<LL,LL> pos[200005];
bool vis[100005];
int main(){
R=read(),C=read(),n=read();
for(LL i=1;i<=n;++i)
{
LL xp=read(),yp=read(),xq=read(),yq=read();
if(!onEdge(xp,yp) || !onEdge(xq,yq))
{
--n,--i;
continue;
}
tmp[2*i-1]=getId(xp,yp),tmp[2*i]=getId(xq,yq);
brk[i]=mp(getId(xp,yp),getId(xq,yq));
}
sort(tmp+1,tmp+1+n+n);
for(LL i=1;i<=n;++i)
{
brk[i].first=lower_bound(tmp+1,tmp+1+n+n,brk[i].first)-tmp;
brk[i].second=lower_bound(tmp+1,tmp+1+n+n,brk[i].second)-tmp;
if(brk[i].first>brk[i].second) swap(brk[i].first,brk[i].second);
}
for(LL i=1;i<=n;++i)
{
pos[brk[i].first]=mp(i,0);
pos[brk[i].second]=mp(i,1);
}
stack<LL> S;
for(LL i=1;i<=2*n;++i)
{
if(!vis[pos[i].first]) S.push(pos[i].first),vis[pos[i].first]=true;
else
{
if(S.top()!=pos[i].first) return puts("NO")&0;
S.pop();
}
}
puts("YES");
return 0;
}
F. Exhausted?
显然原问题就是问一开始最多能坐下多少个人。首先可以用反悔贪心,但是懒得做贪心咋办啊?
根据 Hall 定理,一个二分图的最大匹配是 \(\displaystyle |S| - \max_{T ⊆ S}\{ |T| - C(T) \}\),其中 \(C(T)\) 表示与 \(T\) 邻接的结点集合。
回到这个问题。我们直接连边会太多边了……并且是两个区间的并,然后再一起取交,显然不优美。我们取两个区间并的补集再取交就好看多了。
那我们要做的是,找到一个集合 \(T\),最大化:
简单做一下之后,发现我们要最大化 \(|∩_{v \in T} (l_i,r_i)|\)。考虑交的左端点为 \(l\) 时,这个东西的值。假设交为 \((l,r)\),那么其值就是 \(r-l+\sum_{j}[l_j \leq l][r_j \geq r]\)。考虑构建线段树,先把第 \(i\) 个叶子的值赋上 \(i\) 处理掉 \(r\),\(-l\) 可以在我们处理的时候做。考虑扫描线保证 \(l_j \leq l\),这个时候我们只要找值最大的 \(r\) 就好。显然 \(l\) 一定是某个 \(l_i\),那么我们要在 \([l_i,r_i]\) 找到一个值最大的 \(r\) 就好了。线段树维护区间加区间最大值,随便做。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
char buf[1<<21],*p1=buf,*p2=buf;
#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
int read()
{
int x=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9') x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
return x;
}
void write(int x)
{
if(x>9) write(x/10);
putchar(x%10+'0');
}
struct Segment{
int l,r;
inline void Scan(){l=read()+1,r=read()-1;}
inline bool operator < (Segment ano) const {return l<ano.l || (l==ano.l && r<ano.r);}
}p[200005];
int n,m;
#define lc(x) (x<<1)
#define rc(x) (lc(x)|1)
#define Mm int mid=(l+r)>>1
int tag[800005],maxn[800005];
inline void push_up(int now){maxn[now]=max(maxn[lc(now)],maxn[rc(now)]);}
void build(int l,int r,int now)
{
if(l==r) return void(maxn[now]=l);
Mm;
build(l,mid,lc(now)),build(mid+1,r,rc(now));
push_up(now);
}
inline void push_down(int now)
{
if(tag[now])
{
tag[lc(now)]+=tag[now];
tag[rc(now)]+=tag[now];
maxn[lc(now)]+=tag[now];
maxn[rc(now)]+=tag[now];
tag[now]=0;
}
}
void modify(int l,int r,int now,int x,int y)
{
if(x>y) return ;
if(x<=l && r<=y)
{
++tag[now];
++maxn[now];
return ;
}
Mm;
push_down(now);
if(x<=mid) modify(l,mid,lc(now),x,y);
if(mid<y) modify(mid+1,r,rc(now),x,y);
push_up(now);
}
int query(int l,int r,int now,int x,int y)
{
if(x>y) return -2e7;
if(x<=l && r<=y) return maxn[now];
Mm,ans=0;
push_down(now);
if(x<=mid) ans=max(ans,query(l,mid,lc(now),x,y));
if(mid<y) ans=max(ans,query(mid+1,r,rc(now),x,y));
return ans;
}
#undef lc
#undef rc
#undef Mm
int main(){
n=read(),m=read();
for(int i=1;i<=n;++i) p[i].Scan();
sort(p+1,p+1+n);
int ans=max(n-m,0);
build(1,m,1);
for(int i=1;i<=n;++i)
{
modify(1,m,1,1,p[i].r);
ans=max(ans,query(1,m,1,p[i].l,p[i].r)-p[i].l+1-m);
}
write(ans);
return 0;
}