略
略
略
容易证明要判定是否存在venue-sensitive的非空子集,只需判定所有大小为 2 2 2的子集。也即判定是否存在区间 x ≠ y x\neq y x=y,使得 [ [ s a x , e a x ] ∩ [ s a y , e a y ] = ∅ ] ≠ [ [ s b x , s b x ] ∩ [ s b y , e b y ] = ∅ ] [[sa_x,ea_x]\cap[sa_y,ea_y]=\empty]\neq [[sb_x,sb_x]\cap[sb_y,eb_y]=\empty] [[sax,eax]∩[say,eay]=∅]=[[sbx,sbx]∩[sby,eby]=∅]。
比较简单的做法是考虑异或哈希,对每个区间附上一个随机权值,分别求出每个区间在 a a a和 b b b中与它不相交的区间集合的哈希值,判定是否相等。注意到对于一个区间 [ l 1 , r 1 ] [l_1,r_1] [l1,r1],区间 [ l 2 , r 2 ] [l_2,r_2] [l2,r2]与它不相交当且仅当 l 1 > r 2 l_1>r_2 l1>r2或 r 1 < l 2 r_1
时间复杂度为 O ( n log n ) \mathcal O(n\log n) O(nlogn)。
#include
#include
#define FR first
#define SE second
#define y1 yy
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pr;
mt19937 rnd(time(0));
ull randull() {
ull s=0;
for(int i=0;i<4;i++)
s=(s<<16)|((int)abs((int)rnd())%(1LL<<16));
return s;
}
ull s1[100005],s2[100005],num[100005];
pr a[100005],b[100005],c[100005],d[100005];
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
int x1,y1,x2,y2;
scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
a[i]=pr(x1,i);
b[i]=pr(y1,i);
c[i]=pr(x2,i);
d[i]=pr(y2,i);
num[i]=randull();
}
sort(a+1,a+n+1);
sort(b+1,b+n+1);
sort(c+1,c+n+1);
sort(d+1,d+n+1);
int r=0;
ull s=0;
for(int i=1;i<=n;i++) {
while (r<n&&b[r+1].FR<a[i].FR) {
r++;
s^=num[b[r].SE];
}
s1[a[i].SE]^=s;
}
int l=n+1;
s=0;
for(int i=n;i>0;i--) {
while (l>1&&a[l-1].FR>b[i].FR) {
l--;
s^=num[a[l].SE];
}
s1[b[i].SE]^=s;
}
r=0;
s=0;
for(int i=1;i<=n;i++) {
while (r<n&&d[r+1].FR<c[i].FR) {
r++;
s^=num[d[r].SE];
}
s2[c[i].SE]^=s;
}
l=n+1;
s=0;
for(int i=n;i>0;i--) {
while (l>1&&c[l-1].FR>d[i].FR) {
l--;
s^=num[c[l].SE];
}
s2[d[i].SE]^=s;
}
for(int i=1;i<=n;i++)
if (s1[i]!=s2[i]) {
puts("NO");
return 0;
}
puts("YES");
return 0;
}
考虑点 p p p可以在另 4 4 4个点 a , b , c , d a,b,c,d a,b,c,d所构成的某个多边形中的条件,可以发现这当且仅当 p p p在 a , b , c , d a,b,c,d a,b,c,d的凸包内成立。这里必要性显然,充分性只讨论 a , b , c , d a,b,c,d a,b,c,d的凸包大小为 3 3 3的情况,不妨设 d d d在三角形 a b c abc abc内,由于没有重点和三点共线,对该三角形以 d d d为中心作三角剖分,则 p p p必然落在其中某个三角形内,显然能构造出一个包含它的四边形。
直接对每个点计数它在多少个大小为 4 4 4的点集的凸包内有点困难,考虑计算反面,即它不在多少个大小为 4 4 4的点集的凸包内。这是比较容易的,枚举一个点 p p p,以 p p p为原点,对其他点按极角排序,枚举排序后选的第一个点 q q q,则要求剩余的 3 3 3个点极角序在 q q q后且与 q q q极角差 < π <\pi <π,这可以简单two pointer求解。
时间复杂度为 O ( n 2 log n ) \mathcal O(n^2\log n) O(n2logn)。
#include
#include
#define FR first
#define SE second
#define y1 yy
using namespace std;
typedef __int128 lll;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pr;
struct Point {
ll x,y;
int quad;
Point() {}
Point(ll a,ll b):x(a),y(b) {
if (x>=0&&y>0) quad=1;
else if (x<=0&&y>0) quad=2;
else if (x<0&&y<=0) quad=3;
else quad=4;
}
Point operator - (Point b) {return Point(x-b.x,y-b.y);}
};
inline lll cross(Point x,Point y) {
return (lll)x.x*y.y-(lll)x.y*y.x;
}
bool cmp(Point x,Point y) {
if (x.quad!=y.quad) return x.quad<y.quad;
else return cross(x,y)>0;
}
Point p[3005],q[6005];
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
int x,y;
scanf("%d%d",&x,&y);
p[i]=Point(x,y);
}
ll ans=(lll)n*(n-1)*(n-2)*(n-3)/24*(n-4);
for(int i=1;i<=n;i++) {
int sz=0;
for(int j=1;j<=n;j++)
if (j!=i) q[++sz]=p[j]-p[i];
sort(q+1,q+sz+1,cmp);
for(int j=1;j<=sz;j++) q[sz+j]=q[j];
int r=1;
for(int j=1;j<=sz;j++) {
if (r<j) r=j;
while (r<2*sz&&cross(q[j],q[r+1])>0) r++;
int len=r-j;
if (len>=3) ans-=(ll)len*(len-1)*(len-2)/6;
}
}
printf("%lld\n",ans);
return 0;
}
显然一条 T 1 T1 T1中的边 ( u 1 , v 1 ) (u_1,v_1) (u1,v1)能被一条 T 2 T2 T2中的边 ( u 2 , v 2 ) (u_2,v_2) (u2,v2)替换,当且仅当 T 1 T1 T1中 u 2 u_2 u2到 v 2 v_2 v2的简单路径包含边 ( u 1 , v 1 ) (u_1,v_1) (u1,v1)。
事实上,一定存在一个 T 1 T1 T1中边与 T 2 T2 T2中边的完美匹配。证明可以考虑Hall定理,对于一个 T 1 T1 T1中边大小为 k k k的子集,删去这些边后会将 T 1 T1 T1分成 k + 1 k+1 k+1个连通块,考虑每个连通块的点集 S 1 , S 2 , . . . , S k + 1 S_1,S_2,...,S_{k+1} S1,S2,...,Sk+1,在 T 2 T2 T2上显然必须至少有 k k k条两个端点在不同点集中的边,这些边一定能替换 T 1 T1 T1中选的大小为 k k k的边子集中至少一条边。
考虑如何构造出一组解。我们每次从 T 2 T2 T2找一条边 ( u , v ) (u,v) (u,v),使得 u u u为 T 2 T2 T2叶子,尝试在 T 1 T1 T1中找一条边 ( u ′ , v ′ ) (u',v') (u′,v′)与它匹配。事实上,我们只需选择 T 1 T1 T1中 u u u到 v v v的简单路径上第一条边 ( u , w ) (u,w) (u,w)即可。随后我们不再会用到点 u u u了,那么可以在 T 2 T2 T2上删除这个叶子,并且在 T 1 T1 T1上合并点 u u u和点 w w w(这样显然不会影响之后的匹配),归纳构造即可。
事实上显然没有必要每次在 T 1 T1 T1上缩点,只需标记每条边是否已经被用过了,每次在初始的 T 1 T1 T1上找到简单路径上第一条没用过的边即可。这可以简单地用LCT维护,不过我选择了一个比较好写的做法:用并查集维护所有的合并,每次倍增找到应该合并的边。
时间复杂度为 O ( n log n ) \mathcal O(n\log n) O(nlogn)或 O ( n log n α ( n ) ) \mathcal O(n\log n\alpha(n)) O(nlognα(n))。
#include
#include
#define FR first
#define SE second
#define y1 yy
using namespace std;
typedef __int128 lll;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pr;
namespace SETS {
int fa[300005];
void init(int n) {
for(int i=1;i<=n;i++) fa[i]=i;
}
int find_father(int x) {
return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}
bool check(int x,int y) {
x=find_father(x);y=find_father(y);
return x!=y;
}
void merge(int x,int y) {
x=find_father(x);y=find_father(y);
if (x==y) return;
fa[x]=y;
}
}
vector <int> e1[300005],e2[300005];
int id[300005],fa1[300005],dfs_cnt;
int fa2[300005][20],dep[300005];
void dfs1(int x) {
id[++dfs_cnt]=x;
for(int i=0;i<e1[x].size();i++)
if (e1[x][i]!=fa1[x]) {
int u=e1[x][i];
fa1[u]=x;
dfs1(u);
}
}
void dfs2(int x) {
for(int i=0;i<e2[x].size();i++)
if (e2[x][i]!=fa2[x][0]) {
int u=e2[x][i];
dep[u]=dep[x]+1;
fa2[u][0]=x;
for(int j=1;j<20;j++) fa2[u][j]=fa2[fa2[u][j-1]][j-1];
dfs2(u);
}
}
int lca(int x,int y) {
if (dep[x]<dep[y]) swap(x,y);
int d=dep[x]-dep[y];
for(int i=0;i<20;i++)
if ((d>>i)&1) x=fa2[x][i];
if (x==y) return x;
for(int i=19;i>=0;i--)
if (fa2[x][i]!=fa2[y][i]) {
x=fa2[x][i];
y=fa2[y][i];
}
return fa2[x][0];
}
int jump(int x,int v,int d) {
for(int i=19;i>=0;i--)
if (fa2[x][i]&&dep[fa2[x][i]]>=d&&SETS::find_father(fa2[x][i])!=v) x=fa2[x][i];
return x;
}
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<n;i++) {
int x,y;
scanf("%d%d",&x,&y);
e2[x].push_back(y);
e2[y].push_back(x);
}
dfs2(1);
for(int i=1;i<n;i++) {
int x,y;
scanf("%d%d",&x,&y);
e1[x].push_back(y);
e1[y].push_back(x);
}
dfs1(1);
printf("%d\n",n-1);
SETS::init(n);
for(int i=n;i>1;i--) {
int x=id[i],y=fa1[x];
int p=lca(x,y);
if (SETS::check(x,p)) {
int u=SETS::find_father(x);
SETS::merge(u,fa2[u][0]);
printf("%d %d %d %d\n",u,fa2[u][0],x,y);
}
else {
int u=jump(y,SETS::find_father(x),dep[p]);
SETS::merge(u,fa2[u][0]);
printf("%d %d %d %d\n",u,fa2[u][0],x,y);
}
}
return 0;
}
一个挺神仙的题,范围比较有迷惑性。
问题可以描述为给定一个连通网格图,对图黑白染色( ( 1 , 1 ) (1,1) (1,1)为黑点),要求找出一棵生成树使得所有非 ( 1 , 1 ) (1,1) (1,1)的黑点都度数 > 1 >1 >1,或判定无解。事实上,标准算法可以扩展到一般无向图,且给定限制的点形成独立集的情况。
令给定限制的点集为 S S S。可以发现有解当且仅当能选出一个无环的边集,使得 S S S中每个点的度数恰为 2 2 2。这里必要性由于 S S S为独立集,可以在真实的生成树上保留一部分边得到;充分性考虑将选出的边集扩展为一棵生成树即可。
这个充要条件还是不好直接考虑。我们考虑理解为选出一个大小最大的无环边集,使得每条边有一个端点在 S S S中,且 S S S中每个点的度数不超过 2 2 2,问该最大大小是否为 2 ∣ S ∣ 2|S| 2∣S∣。限制选出的边集满足 S S S中每个点的度数不超过 2 2 2,这要求了选出的边集集合是一个度数限制拟阵的独立集(事实上无向图中选择一个边集,要求选出集合满足某个点独立集中每个点有度数上限都成立),这里的遗传性和交换性都很显然。那么问题可以理解为只考虑有一个端点在 ∣ S ∣ |S| ∣S∣中的边集,求图拟阵与度数限制拟阵的交的最大独立集。这是一个不超过 2 n m 2nm 2nm条边的拟阵交问题,直接套用拟阵交算法即可。
单组数据视实现时间复杂度为 O ( n 3 m 3 ) \mathcal O(n^3m^3) O(n3m3)或 O ( n 3 m 3 α ( n ) ) \mathcal O(n^3m^3\alpha(n)) O(n3m3α(n))。
#include
#define FR first
#define SE second
using namespace std;
typedef pair<int,int> pr;
namespace SETS {
int fa[405];
void init(int n) {
for(int i=1;i<=n;i++) fa[i]=i;
}
int find_father(int x) {
return (fa[x]==x)?x:fa[x]=find_father(fa[x]);
}
bool check(int x,int y) {
x=find_father(x);y=find_father(y);
return x!=y;
}
void merge(int x,int y) {
x=find_father(x);y=find_father(y);
if (x==y) return;
fa[x]=y;
}
}
char str[25][25];
int id[25][25];
bool kind[405];
int d[1005];
pr ee[1005];
bool in[1005];
vector <int> e[1005];
bool spos[1005],tpos[1005];
void build(int n,int m) {
memset(d,0,sizeof(d));
memset(spos,0,sizeof(spos));
memset(tpos,0,sizeof(tpos));
for(int i=1;i<=m;i++) vector<int>().swap(e[i]);
for(int i=1;i<=m;i++)
if (in[i]) {
if (kind[ee[i].FR]) d[ee[i].FR]++;
if (kind[ee[i].SE]) d[ee[i].SE]++;
}
for(int i=1;i<=m;i++)
if (in[i]) {
SETS::init(n);
for(int j=1;j<=m;j++)
if (in[j]&&j!=i) SETS::merge(ee[j].FR,ee[j].SE);
if (kind[ee[i].FR]) d[ee[i].FR]--;
if (kind[ee[i].SE]) d[ee[i].SE]--;
for(int j=1;j<=m;j++)
if (!in[j]) {
int u=ee[j].FR,v=ee[j].SE;
if (!kind[u]&&!kind[v]) continue;
if (SETS::check(u,v)) e[i].push_back(j);
if ((!kind[u]||d[u]<2)&&(!kind[v]||d[v]<2)) e[j].push_back(i);
}
if (kind[ee[i].FR]) d[ee[i].FR]++;
if (kind[ee[i].SE]) d[ee[i].SE]++;
}
SETS::init(n);
for(int i=1;i<=m;i++)
if (in[i]) SETS::merge(ee[i].FR,ee[i].SE);
for(int i=1;i<=m;i++)
if (!in[i]) {
int u=ee[i].FR,v=ee[i].SE;
if (!kind[u]&&!kind[v]) continue;
if (SETS::check(u,v)) spos[i]=1;
if ((!kind[u]||d[u]<2)&&(!kind[v]||d[v]<2)) tpos[i]=1;
}
}
bool vis[1005];
int p[1005];
queue <int> q;
bool bfs(int n) {
while (!q.empty()) q.pop();
memset(vis,0,sizeof(vis));
memset(p,0,sizeof(p));
for(int i=1;i<=n;i++)
if (spos[i]) {
vis[i]=1;
q.push(i);
}
while (!q.empty()) {
int x=q.front();q.pop();
if (tpos[x]) {
for(int i=x;i;i=p[i]) in[i]^=1;
return 1;
}
for(int i=0;i<e[x].size();i++)
if (!vis[e[x][i]]) {
int u=e[x][i];
vis[u]=1;
p[u]=x;
q.push(u);
}
}
return 0;
}
char ans[45][45];
pr pos[405];
int main() {
int cases;
scanf("%d",&cases);
for(;cases;cases--) {
memset(id,0,sizeof(id));
memset(kind,0,sizeof(kind));
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) scanf("%s",str[i]+1);
int cnt=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if (str[i][j]=='O') {
id[i][j]=++cnt;
kind[cnt]=((i!=1||j!=1)&&(!((i+j)&1)));
pos[cnt]=pr(i,j);
}
int sz=0,s=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
if (str[i][j]=='O') {
s+=kind[id[i][j]];
if (j<m&&str[i][j+1]=='O') ee[++sz]=pr(id[i][j],id[i][j+1]);
if (i<n&&str[i+1][j]=='O') ee[++sz]=pr(id[i][j],id[i+1][j]);
}
memset(in,0,sizeof(in));
bool ok=1;
for(int i=1;i<=2*s;i++) {
build(cnt,sz);
if (!bfs(sz)) {
ok=0;
break;
}
}
if (!ok) {
puts("NO");
continue;
}
puts("YES");
SETS::init(cnt);
for(int i=1;i<=sz;i++)
if (in[i]) SETS::merge(ee[i].FR,ee[i].SE);
for(int i=1;i<=sz;i++)
if (!in[i]&&SETS::check(ee[i].FR,ee[i].SE)) {
in[i]=1;
SETS::merge(ee[i].FR,ee[i].SE);
}
memset(ans,0,sizeof(ans));
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) ans[2*i-1][2*j-1]=str[i][j];
for(int i=1;i<=sz;i++)
if (in[i]) {
int u=ee[i].FR,v=ee[i].SE;
if (pos[u].FR==pos[v].FR)
ans[2*pos[u].FR-1][2*pos[u].SE]='O';
else
ans[2*pos[u].FR][2*pos[u].SE-1]='O';
}
for(int i=1;i<=2*n-1;i++) {
for(int j=1;j<=2*m-1;j++) putchar((ans[i][j])?ans[i][j]:' ');
putchar('\n');
}
}
return 0;
}