三色奏玲珑
T1
statement
给定一个 \(n\) 个点 \(m\) 条边的无向图,每个点有一个颜色。
定义一次变化为,所有点同时执行:要么颜色不变,要么变成某一个邻居的颜色。
请在不超过 \(2n^2\) 次操作内把某个起始状态变成终止状态
solution
简单题
首先某个连通块分别考虑,无解的条件就是某个连通块的起始状态和终止状态的颜色集合不一样。
注意到对于一条边的两个端点,我们可以交换两点颜色,或者用一个点颜色覆盖另一个。
考虑两种操作:
- Swap:选取一系列相邻两点右边的点 \(p_1,p_2……p_k\) ,依次执行 \(swap(p_1,p_2),swap(p_2,p_3)\cdots swap(p_{k-1},p_k),swap(p_{k-2},p_{k-1})\cdots swap(p_1,p_2)\) ,可以发现这样相当于交换 \(p_1,p_k\) 的颜色,选取最短路的话,代价不超过 \(2n\) 。
- Cover:用类似的操作,我们可以用不超过 \(2n\) 的代价把一个点的颜色覆盖另一个。
记 \(a_i\) 为当前颜色,\(b_i\) 为目标颜色。
那么我们考虑所有 \(a_i\neq b_i\) 的点 \(i\),如果存在 \(j\) 满足 \(a_j\neq a_i\) 且 \(a_j=b_i\) 或者 \(a_i=b_j\) ,然后 \(Swap(i,j)\) ;否则 \(a_i\) 一点用也没用了,我们找一个 \(a_j=b_i\) 的 \(j\),执行 \(Cover(j,i)\) ,因为颜色集合不变,且 \(a_i=b_i\) 的点数每轮多一个,所以最多 \(n\) 轮,就行了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 120;
const int M = N*N;
int n,m,k;
int a[N],b[N],fa[N];
int get(int x)
{
if(x==fa[x])return x;
return fa[x]=get(fa[x]);
}
vector<int> G[N];
vector<int> seq[N];
int dis[N][N],pre[N][N];
queue<int> q;
const int inf = 1e9;
void bfs(int r)
{
for(int i=1;i<=n;i++)dis[r][i]=inf;
q.push(r);
dis[r][r]=0;
while(!q.empty())
{
int x=q.front();
q.pop();
for(int y:G[x])
{
if(dis[r][y]==inf)
{
dis[r][y]=dis[r][x]+1;
q.push(y);
pre[r][y]=x;
}
}
}
}
int vis[N],tag;
vector<vector<int> > ans;
void print()
{
vector<int> s;
for(int i=1;i<=n;i++)s.push_back(a[i]);
ans.push_back(s);
}
void Swap(int x,int y)
{
if(x==y)return;
if(pre[x][y]==x)
{
swap(a[x],a[y]);
print();
return;
}
swap(a[pre[x][y]],a[y]);print();
Swap(x,pre[x][y]);
swap(a[pre[x][y]],a[y]);print();
}
void Cov(int x,int y)
{
if(x==y)return;
if(pre[x][y]==x)
{
a[x]=a[y];
print();
return;
}
swap(a[pre[x][y]],a[y]);print();
Cov(x,pre[x][y]);
swap(a[pre[x][y]],a[y]);print();
}
void sol(int r)
{
++tag;
for(int x:seq[r])bfs(x);
for(int x:seq[r])vis[a[x]]=tag;
for(int x:seq[r])if(vis[b[x]]!=tag)
{
printf("Impossible");
exit(0);
}
while(1)
{
int u=0;
for(int x:seq[r])if(a[x]!=b[x])u=x;
if(!u)break;
int p=0;
for(int x:seq[r])if(a[x]!=b[x]&&a[x]==b[u])p=x;
if(p)
{
Swap(u,p);
continue;
}
for(int x:seq[r])if(a[x]!=b[x]&&a[u]==b[x])p=x;
if(p)
{
Swap(u,p);
continue;
}
for(int x:seq[r])if(a[x]==b[u])p=x;
Cov(u,p);
}
}
int main()
{
cin>>n>>m>>k;
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)scanf("%d",&b[i]),fa[i]=i;
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d %d",&x,&y);
G[x].push_back(y);
G[y].push_back(x);
fa[get(x)]=get(y);
}
print();
for(int i=1;i<=n;i++)seq[get(i)].push_back(i);
for(int i=1;i<=n;i++)if(get(i)==i) sol(i);
for(auto u:ans)
{
for(int x:u)printf("%d ",x);
printf("\n");
}
return 0;
}
T2
statment
有 \(n\) 个二维平面的点,每个点代表一个宝藏。
依次有 \(m\)人 个人来寻宝,第 \(i\) 个人坐标 \((x_i,y_i)\),会从 \((x-i,y_i)\) 向左向下分别修两排栅栏直到碰到边界或者碰到之前的栅栏停止,然后获得它的栅栏围起来的区域内的宝藏个数。注意一个宝藏可以被多个人获得。
solution
考虑先求出最终局面下,每个宝藏右上方的第一个人。
按照 \(x\) 从右到左扫描线,用 \(set\) 维护横着的栅栏,那么加入一个人时,相当于找到下方第一个时间早于他的栅栏,然后把这两个栅栏之前的栅栏删掉。扫到一个宝藏时查询它上面第一个栅栏即可。
同理我们可以对于每个人 \(i\),求出包含它的第一个人 \(j\),并从 \(j\) 向 \(i\) 连边,那么这构成了外向森林,对于每个点 \(i\),我们求出第一个 \(<i\) 的祖先 \(j\),那么 \(i\) 的宝藏 \(j\) 都有,树上倍增即可。
复杂度 \(O(n\log n)\)
code
#include<bits/stdc++.h>
using namespace std;
const int N = 3e5+7;
typedef long long LL;
struct node
{
int x,y;
}A[N],B[N];
int n,m;
int dct[2*N],tot=0;
set<int> st;
int id[N];
vector<int> W[N*2],Q[N*2];
unordered_map<int,int> ind;
int del[N],tmp=0,ans[N],fa[N];
vector<int> G[N];
bool vis[N];
int jump[N][20],mn[N][20];
void dfs(int x)
{
vis[x]=1;
jump[x][0]=fa[x];
mn[x][0]=fa[x];
for(int k=1;jump[x][k-1];k++)
{
jump[x][k]=jump[jump[x][k-1]][k-1];
mn[x][k]=min(mn[x][k-1],mn[jump[x][k-1]][k-1]);
}
for(int y:G[x])dfs(y);
int p=x;
for(int k=19;k>=0;k--)
if(jump[p][k]&&mn[p][k]>=x)p=jump[p][k];
if(fa[p])ans[fa[p]]+=ans[x];
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
scanf("%d %d",&A[i].x,&A[i].y);
dct[++tot]=A[i].x;
}
cin>>m;
for(int i=1;i<=m;i++)
{
scanf("%d %d",&B[i].x,&B[i].y);
dct[++tot]=B[i].x;
}
sort(dct+1,dct+tot+1);
tot=unique(dct+1,dct+tot+1)-dct-1;
for(int i=1;i<=n;i++)
{
A[i].x=lower_bound(dct+1,dct+tot+1,A[i].x)-dct;
Q[A[i].x].push_back(i);
}
for(int i=1;i<=m;i++)
{
B[i].x=lower_bound(dct+1,dct+tot+1,B[i].x)-dct;
ind[B[i].y]=i;
W[B[i].x].push_back(i);
}
for(int i=tot;i>=1;i--)
{
for(auto u:W[i])
{
int L=B[u].y;
auto it=st.upper_bound(L);
tmp=0;
while(it!=st.begin())
{
it--;
if(ind[*it]<u)break;
del[++tmp]=(*it);
}
for(int j=1;j<=tmp;j++)st.erase(del[j]);
st.insert(L);
auto p=st.upper_bound(L);
if(p!=st.end())fa[u]=ind[*p],G[fa[u]].push_back(u);
}
for(auto u:Q[i])
{
auto p=st.lower_bound(A[u].y);
if(p!=st.end())ans[ind[*p]]++;
}
}
for(int i=1;i<=m;i++)if(!fa[i]&&!vis[i])dfs(i);
for(int i=1;i<=m;i++)printf("%d\n",ans[i]);
return 0;
}
T3
statement
对于两棵有根树 𝐴 和 𝐵,令 𝑟(𝐴) 和 𝑟(𝐵) 分别表示他们的根,我们定义如下的运算:
- 𝑇=𝐴+𝐵: 将 𝑟(𝐴) 与 𝑟(𝐵) 合并为 𝑟(𝑇) ,所有 𝑟(𝐴) 与 𝑟(𝐵) 的孩子均为 𝑟(𝑇) 的孩子。
- 𝑇=𝐴×𝐵: 将 𝐴 的每个节点和 𝑟(𝐵) 合并,合并方式与加法类似。
多组询问,每次给出三棵有根树 \(A,B,C\),构造有根树 \(X,Y\) 使得 \(AX+BY=C\) 。
solution
\(O(n^3)\) 过 \(10^5\) 。
考虑一个性质,\(A\) 的叶子替换为 \(X\) 后一定是 \(C\) 的一棵子树,所以 \(X,Y\) 都是 \(C\) 的子树,这样暴力枚举,然后树哈希判断就是 \(O(n^3)\) 。
考虑一些必要条件:
- \(nA\times nX+nB\times nY-1=nC\)
- \(deg(rA)+deg(rB)+deg(rX)+deg(rY)=deg(rC)\)
- 树 \(X\) 在 \(C\) 中至少出现了 \(leaf_A\) 次
- 树 \(Y\) 在 \(C\) 中至少出现了 \(leaf_B\) 次
只枚举符合上述条件的子树对,就过了。
code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+7;
typedef long long LL;
const int mod[3]={998244353,1000000007,1000000009};
struct node
{
int x,y,z;
};
mt19937 rnd(time(0));
int ran(int l,int r){return rnd()%(r-l+1)+l;}
int base1=ran(0,mod[0]-1),base2=ran(0,mod[1]-1),base3=ran(0,mod[2]-1);
int d1=ran(0,mod[0]-1),d2=ran(0,mod[1]-1),d3=ran(0,mod[2]-1);
inline node operator +(node A,node B)
{
int x,y,z;
x=1ll*A.x*B.x%mod[0];
y=(A.y+B.y)%mod[1];
z=(A.z^B.z);
return (node){x,y,z};
}
inline node upd(node A)
{
int x=(1ll*A.x*base1%mod[0]+d1)%mod[0];
int y=(1ll*A.y*base1%mod[1]+d2)%mod[1];
int z=(1ll*A.z*base1%mod[2]+d3)%mod[2];
return (node){x,y,z};
}
node I=(node){1,0,0};
int n1,n2,n3,r1,r2,r3;
vector<int> t1[N],t2[N],t3[N];
node h3[N],h[N];
int deg[N],siz[N];
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
#define X(x) x.first
#define Y(x) x.second
inline bool operator <(node x,node y){return x.x!=y.x?(x.x<y.x):(x.y!=y.y?(x.y<y.y):x.z<y.z);}
map<PII,vector<int> > ord;
map<node,int> cnt,used;
void dfs(int x)
{
h3[x]=I;
deg[x]=0;siz[x]=1;
for(int y:t3[x])
{
dfs(y);
deg[x]++;
siz[x]+=siz[y];
h3[x]=h3[x]+h3[y];
}
h[x]=h3[x];
h3[x]=upd(h3[x]);
cnt[h3[x]]++;
}
int leaf1[N],leaf2[N];
inline bool operator ==(node x,node y){return x.x==y.x&&x.y==y.y&&x.z==y.z;}
node dfs1(int x,node V)
{
node res=V;
for(int y:t1[x])res=res+dfs1(y,V);
if(x!=r1)res=upd(res);
return res;
}
node dfs2(int x,node V)
{
node res=V;
for(int y:t2[x])res=res+dfs2(y,V);
if(x!=r2)res=upd(res);
return res;
}
bool chk(int T1,int T2)
{
node W=I;
W=dfs1(r1,h[T1])+dfs2(r2,h[T2]);
W=upd(W);
return W==h3[r3];
}
int id=0;
void put(int x,int pre)
{
int u=++id;
printf("%d ",pre);
for(int y:t3[x]) put(y,u);
}
void solve()
{
scanf("%d %d %d",&n1,&n2,&n3);
ord.clear();cnt.clear();used.clear();
for(int i=1;i<=n1;i++)t1[i].clear(),leaf1[i]=1;
for(int i=1;i<=n2;i++)t2[i].clear(),leaf2[i]=1;
for(int i=1;i<=n3;i++)t3[i].clear();
for(int i=1;i<=n1;i++)
{
int p;
scanf("%d",&p);
if(!p)r1=i;
else t1[p].push_back(i),leaf1[p]=0;
}
for(int i=1;i<=n2;i++)
{
int p;
scanf("%d",&p);
if(!p)r2=i;
else t2[p].push_back(i),leaf2[p]=0;
}
int D1=(int)t1[r1].size(),D2=(int)t2[r2].size();
for(int i=1;i<=n3;i++)
{
int p;
scanf("%d",&p);
if(!p)r3=i;
else t3[p].push_back(i);
}
dfs(r3);
int f1=0,f2=0;
for(int i=1;i<=n1;i++)if(leaf1[i])f1++;
for(int i=1;i<=n2;i++)if(leaf2[i])f2++;
for(int i=1;i<=n3;i++)if(siz[i]!=n3&&cnt[h3[i]]>=f2&&!used[h3[i]])
{
ord[mk(siz[i],deg[i])].push_back(i);
used[h3[i]]=1;
}
used.clear();
for(int i=1;i<=n3;i++)if(siz[i]!=n3&&cnt[h3[i]]>=f1&&!used[h3[i]])
{
int s=siz[i];
LL w=n3+1-n1*(s-1)-n1-n2;
if(w<0)continue;
if(w%n2!=0)continue;
LL t=w/n2+1;
if(t<0||t>=n3)continue;
int d=deg[r3]-D1-D2-deg[i];
if(d<0)continue;
if(ord[mk(t,d)].size()==0)continue;
used[h3[i]]=1;
for(int j:ord[mk(t,d)])
{
if(!chk(i,j))continue;
printf("%d %d\n",siz[i],siz[j]);
id=0;put(i,0);printf("\n");
id=0;put(j,0);printf("\n");
return;
}
}
printf("Impossible\n");
}
int main()
{
int T;
cin>>T;
while(T--)
{
solve();
}
return 0;
}

浙公网安备 33010602011771号