"蔚来杯"2022牛客暑期多校训练营8
A | B | C | D | E | F | G | H | I | J | K | L | |
赛时过题 | O | O | ||||||||||
赛后补题 | O | O |
赛后总结:
今天是码量专场orz
G题明明是很有希望做出来的。。。但是没做出来,本质原因是没有思考清楚平衡树每个节点的意义
我的平衡树的每个节点实际上代表的是原序列的一个下标,那么一个环的首位位置实际上就是这个环中的最小原序列下标
我一开始的做法是永远把一个环的首位位置放在平衡树的第一位,但是这种做法在拆环的时候会导致第二个环的首位位置不在平衡树第一位。。。
要想解决这个问题就必须在平衡树每个节点维护一个min,这样就能快速知道一个环的首位位置了。。。
以后写任何复杂数据结构之前一定要思考清楚这个数据结构每个节点的意义,并且在写的时候要能清楚的知道每个值是否成功维护了(比如维护Treap的父节点时,需要先置为0再修改。。。)
说起来今天这场排名这么高还是多亏了宇彬,他手速比较快,D题的300行程序1h就KO了,我G题250写了3h才写完,结果还因为没有理清节点意义导致WA,对拍了2.5h才在赛后过了这题。。。
赛时排名:
3题末尾:44名
4题末尾:19名
5题末尾:5名
F Longest Common Subsequence
题目难度:check-in
题目大意:长度为n的序列s 和长度为m的序列t,求二者最长公共子序列。
数据产生方式如图:
数据范围:1≤n,m≤1e6
解题分析:
显然直接n2做最长公共子序列会超时。
宇彬猜测这题可能和数据构造方式有关。
然后我发现只要两个序列中有一对数相同,那么他们之后的数一定完全相同,那么直接模拟即可。
似乎可能可以将最长公共子序列转成最长上升子序列来做?不太懂,有空看。
参考代码:
查看代码
#include<iostream>
#include<cstdio>
#include<map>
#define For(i,a,b) for(int i=a;i<=b;i++)
std::map<long long,int> first;
int main()
{
int T;scanf("%d",&T);
while (T--)
{
first.clear() ;
int n,m;long long p,x,a,b,c;
scanf("%d%d%lld%lld%lld%lld%lld",&n,&m,&p,&x,&a,&b,&c);
For(i,1,n)
{
x=(a*x%p*x%p+b*x%p+c)%p;//a*x*x会爆long long,要注意
if (!first[x]) first[x]=i;
}
int ans=0;
For(i,1,m)
{
x=(a*x%p*x%p+b*x%p+c)%p;
int first_pos=first[x];
if (first_pos) ans=std::max(ans,std::min(m-i+1,n-first_pos+1));
}
printf("%d\n",ans);
}
return 0;
}
D Poker Game: Decision
题目难度:easy
题目大意:
有共52张扑克牌,其中有A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, 2 共13种排名,以及4种花色。13*4=52。
Alice和Bob初始各有2张扑克牌,公共牌堆中有6张扑克牌,Alice和Bob轮流从公共牌堆选牌,Alice先取。
最终Alice和Bob手中都有5张扑克牌,5张扑克牌能组成10种手牌(顺子,对子,三带一对等),不同种手牌排名不同,同种手牌根据字典序不同排名也不同,最终排名大的那个人获胜。
两人都绝顶聪明,问谁获胜?
10种手牌的排名定义如下:
解题思路:
大模拟,需要注意题意理解。
根据题意 5 4 3 2 A是排名最低的顺子,但没提到A不一定仅用于5 4 3 2 A ,实际上A K Q J T是排名最大的顺子,可能出题者默认这个是最大的了,这点要注意。
主要还是得理清出题人的意图,想清楚是否每种手牌都会有排名,如果某种手牌没有排名或排名很怪,那可能是题意理解有误。
如果按我一开始想的,A仅用于5 4 3 2 A,A K Q J T不是合法顺子,那么它就会被归到最差手牌,这显然不合常理,因此是题意理解有误。
另外需要注意将Alice和Bob的最终所有手牌获胜情况进行预处理,避免超时。
参考代码:
查看代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<map>
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Frd(i,a,b) for(int i=a;i>=b;i--)
struct Card
{
int rank;
char suit;
int operator < (const Card&other) const{return rank>other.rank;}
};
void read(Card&now)
{
char c;
scanf(" %c",&c);
if (c=='A') now.rank=14;
else if (c=='K') now.rank=13;
else if (c=='Q') now.rank=12;
else if (c=='J') now.rank=11;
else if (c=='T') now.rank=10;
else now.rank=c-'0';
scanf(" %c",&now.suit );
if (now.suit=='S') now.suit='0';
else if (now.suit=='H') now.suit='1';
else if (now.suit=='C') now.suit='2';
else if (now.suit=='D') now.suit='3';
}
int hand_10(Card *card)
{
char suit=card[1].suit;
For(i,1,5) if (card[i].suit !=suit) return 0;
// std::sort(card+1,card+1+5);
if (card[1].rank==14&&card[2].rank==13&&card[3].rank==12&&card[4].rank==11&&card[5].rank==10) return 1;
else return 0;
}
int hand_9(Card *card)
{
char suit=card[1].suit;
For(i,1,5) if (card[i].suit !=suit) return 0;
int flag=1;For(i,1,4) if (card[i+1].rank!=card[i].rank-1) {flag=0;break;}
if (flag) return 1;
For(i,1,5) if (card[i].rank==14) card[i].rank=1;
std::sort(card+1,card+1+5);
For(i,1,4) if (card[i+1].rank!=card[i].rank-1) return 0;
return 1;
}
int hand_8(Card *card)
{
// std::sort(card+1,card+1+5);
int rank=card[1].rank;
if (card[1].rank==rank&&card[2].rank==rank&&card[3].rank==rank&&card[4].rank==rank) return 1;
rank=card[2].rank;
if (card[2].rank==rank&&card[3].rank==rank&&card[4].rank==rank&&card[5].rank==rank)
{
std::swap(card[1],card[5]);
return 1;
}
return 0;
}
int hand_7(Card*card)
{
// std::sort(card+1,card+1+5);
int rank1,rank2;
rank1=card[1].rank,rank2=card[4].rank;
if (card[1].rank==rank1&&card[2].rank==rank1&&card[3].rank==rank1&&card[4].rank==rank2&&card[5].rank==rank2) return 1;
For(i,0,4) card[i]=card[i+1];card[5]=card[0];
rank1=card[1].rank,rank2=card[4].rank;
if (card[1].rank==rank1&&card[2].rank==rank1&&card[3].rank==rank1&&card[4].rank==rank2&&card[5].rank==rank2) return 1;
For(i,0,4) card[i]=card[i+1];card[5]=card[0];
rank1=card[1].rank,rank2=card[4].rank;
if (card[1].rank==rank1&&card[2].rank==rank1&&card[3].rank==rank1&&card[4].rank==rank2&&card[5].rank==rank2) return 1;
return 0;
}
int hand_6(Card*card)
{
char suit=card[1].suit;
For(i,1,5) if (card[i].suit !=suit) return 0;
// std::sort(card+1,card+1+5);
return 1;
}
int hand_5(Card*card)//
{
int flag=1;For(i,1,4) if (card[i+1].rank!=card[i].rank-1) {flag=0;break;}
if (flag) return 1;
For(i,1,5) if (card[i].rank==14) card[i].rank=1;
std::sort(card+1,card+1+5);
For(i,1,4) if (card[i+1].rank!=card[i].rank-1) return 0;
return 1;
}
int hand_4(Card*card)
{
// std::sort(card+1,card+1+5);
int rank;
rank=card[1].rank;
if (card[1].rank==rank&&card[2].rank==rank&&card[3].rank==rank&&card[4].rank>card[5].rank) return 1;
rank=card[2].rank;
if (card[2].rank==rank&&card[3].rank==rank&&card[4].rank==rank&&card[1].rank>card[5].rank)
{
std::swap(card[1],card[4]);
return 1;
}
rank=card[3].rank;
if (card[3].rank==rank&&card[4].rank==rank&&card[5].rank==rank&&card[1].rank>card[2].rank)
{
std::swap(card[1],card[4]);std::swap(card[2],card[5]);
return 1;
}
return 0;
}
int hand_3(Card*card)
{
if (card[1].rank==card[2].rank&&card[2].rank>card[3].rank&&card[3].rank==card[4].rank) return 1;
if (card[1].rank==card[2].rank&&card[2].rank>card[3].rank&&card[3].rank==card[5].rank)
{
std::swap(card[4],card[5]);
return 1;
}
if (card[1].rank==card[2].rank&&card[2].rank>card[4].rank&&card[4].rank==card[5].rank)
{
Card temp=card[3];For(i,3,4) card[i]=card[i+1];card[5]=temp;
return 1;
}
if (card[1].rank==card[3].rank&&card[3].rank>card[4].rank&&card[4].rank==card[5].rank)
{
Card temp=card[2];For(i,2,4) card[i]=card[i+1];card[5]=temp;
return 1;
}
if (card[2].rank==card[3].rank&&card[3].rank>card[4].rank&&card[4].rank==card[5].rank)
{
Card temp=card[1];For(i,1,4) card[i]=card[i+1];card[5]=temp;
return 1;
}
return 0;
}
int hand_2(Card*card)
{
if (card[1].rank==card[2].rank&&card[3].rank>card[4].rank&&card[4].rank>card[5].rank) return 1;
if (card[2].rank==card[3].rank&&card[1].rank>card[4].rank&&card[4].rank>card[5].rank)
{
For(i,0,2) card[i]=card[i+1];card[3]=card[0];
return 1;
}
if (card[3].rank==card[4].rank&&card[1].rank>card[2].rank&&card[2].rank>card[5].rank)
{
std::swap(card[1],card[3]);
std::swap(card[2],card[4]);
return 1;
}
if (card[4].rank==card[5].rank&&card[1].rank>card[2].rank&&card[2].rank>card[3].rank)
{
Card temp1=card[4],temp2=card[5];
Frd(i,5,3) card[i]=card[i-2];card[1]=temp1;card[2]=temp2;
return 1;
}
return 0;
}
int hand_1(Card*card)
{
For(i,1,4) if (card[i].rank==card[i+1].rank) return 0;
return 1;
}
int (*(hand_x[15]))(Card*card)={hand_1,hand_1,hand_2,hand_3,hand_4,hand_5,hand_6,hand_7,hand_8,hand_9,hand_10};
int hand_rank(Card *card)
{
std::sort(card+1,card+1+5);
Card temp[10];
Frd(i,10,1)
{
For(j,1,5) temp[j]=card[j];
if (hand_x[i](temp)) return hand_x[i](card),i;
}
return 0;
}
int vis[10];Card common[10];
Card card[2][10];
int judge[100];
int dfs(int people,int state0,int state1)
{
if (state0+state1==(1<<6)-1) return judge[state0];
int ans=2;
For(i,1,6) if (!vis[i])
{
vis[i]=1;
if (people==0) ans=std::min(ans,dfs(1,state0|(1<<(i-1)),state1));
else ans=std::min(ans,dfs(0,state0,state1|(1<<(i-1))));
vis[i]=0;
}
return -ans;
}
int main()
{
int T;scanf("%d",&T);
while (T--)
{
For(i,0,1) For(j,1,2) read(card[i][j]);
For(i,1,6) read(common[i]);
For(state,1,(1<<6)-1)
{
int cnt=0;For(i,1,6) cnt+=!!(state&(1<<i-1));
if (cnt!=3) continue;
int cnt0=2,cnt1=2;
For(i,1,6) if (state&(1<<i-1)) card[0][++cnt0]=common[i];else card[1][++cnt1]=common[i];
Card temp[2][10];
For(i,0,1) For(j,1,5) temp[i][j]=card[i][j];
int rk0=hand_rank(temp[0]);
int rk1=hand_rank(temp[1]);
judge[state]=0;
if (rk0!=rk1) {judge[state]=rk0>rk1?1:-1;continue;}
For(i,1,5) if (temp[0][i].rank!=temp[1][i].rank) {judge[state]=temp[0][i].rank>temp[1][i].rank?1:-1;break;}
}
int ans=dfs(0,0,0);
if (ans==-1) printf("Bob\n");
else if (ans==1) printf("Alice\n");
else printf("Draw\n");
}
return 0;
}
G Lexicographic Comparison
题目难度:medium-hard
题目大意:有两个排列a和p,一开始ai=i,pi=i。A是排列的序列,A1=a,Ai,j=Ai-1,pj (1<i,1≤j≤n)
维护三种操作:swap_a x y 交换ax和ay (1≤x,y≤n)
swap_p x y 交换px和py(1≤x,y≤n)
cmp x y 询问Ax和Ay的字典序谁更小(1≤x,y≤1e18)
解题分析:
手动模拟一下会发现Ai相对于Ai-1就是让某些环轮转一圈
如pa=b,pb=c,pc=a
那么一开始:A1=a,b,c
转一圈以后:A2=b,c,a
再转一圈:A3=c,a,b
然后再模拟一下会发现a数组其实是没什么用的,p数组直接决定了Ax的每一位取的是a数组的哪个位置
再模拟一下会发现swap_p时如果x,y属于一个环就会断环;不属于一个环就会并环。
那么swap_a直接模拟即可,swap_p需要用某种数据结构维护断环和并环。
然后发现cmp x,y实际上是比较第一个大小不能被x-y整除的环的第一位。
那么考虑用线段树在每个环的第一个位置维护这个位置所属的环的size,并对每个区间维护lcm。注意到lcm可能会远远超过long long,而xy的范围是1e18,那么可以认为如果lcm>1e18就直接认为永远不可能被y-x整除,记为-1即可。
至于一个环中的其他位置,让其置为0即可,然后线段树合并和查询的时候跳过0就好了。
再考虑如何维护每个环?我需要快速知道某一位属于哪个环,需要快速知道一个环中的最小位置,需要快速知道一个环的某一位,那么考虑用fhqtreap来维护。
需要注意的是我的每个root本身表示的是原数组下标,那么一个环的首位置实际上就是这棵treap中的的最小root。
比赛的时候为了快速知道某个位置属于某个环,潜意识直接把root和原数组下标一一对应,但是却没有思考清楚我的root的实际意义,结果导致没想到必须得维护min。。。
这个代码遇到的所有bug:
①没有维护min,root1这棵树split以后的b这棵树的第一个点不能保证是最小值 。。。
②忘记维护pa
③在维护pa的时候忘记考虑一个点的pa变成0了的情况
④split_min的时候k必须是整个Treap的min,而非这个子树的min
⑤线段树中的l,mid写成l,r
真是没想到在检查了一遍代码之后仍然会出现5个bug,对拍了2.5h才找出所有bug,害。。。还是经验不足
以后写很长的题的时候一定要注意写注释,一定要理清每个变量的意义,同时要时刻保证每个变量合法,想的周全一点。
参考代码:
查看代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#define ABS(x) ((x)<0?-(x):(x))
#define For(i,a,b) for(int i=a;i<=b;i++)
#define Frd(i,a,b) for(int i=a;i>=b;i--)
const int N=1e5+1000;
int n,q,a[N];
const long long MAX=1e18;
namespace Treap
{
struct Node
{
int lc,rc,pa,weight,size,min;
} T[N];
void Init()
{
For(i,1,n) T[i]=(Node){0,0,0,rand()*rand(),1,i};
}
inline void update(int root)
{
T[root].size =1+T[T[root].lc].size+ T[T[root].rc].size;
T[root].min=std::min(root,std::min(T[T[root].lc].min ,T[T[root].rc].min ));//必须维护min,才能拆环
}
int merge(int x,int y)
{
if (!x||!y) return x+y;
if (T[x].weight <T[y].weight )
{
if (T[x].rc) T[T[x].rc].pa=0;//维护pa:断开旧联系
T[x].rc=merge(T[x].rc,y);
if (T[x].rc) T[T[x].rc].pa=x;//维护pa:构建新联系
update(x);return x;
}
else
{
if (T[y].lc) T[T[y].lc].pa=0;
T[y].lc=merge(x,T[y].lc);
if (T[y].lc) T[T[y].lc].pa=y;
update(y);return y;
}
}
void split(int root,int &x,int &y,int k)// <=k >k
{
if (!root) x=y=0;
else
{
int lc=T[root].lc,rc=T[root].rc;
if (T[lc].size+1<=k)
{
x=root;if (T[x].rc) T[T[x].rc].pa=0;//维护pa:断开旧联系
split(rc,T[x].rc,y,k-T[lc].size-1);//注意是 k-T[lc].size-1
if (T[x].rc) T[T[x].rc].pa=x;//维护pa:构建新联系
}
else
{
y=root;if (T[y].lc) T[T[y].lc].pa=0;
split(lc,x,T[y].lc,k);
if (T[y].lc) T[T[y].lc].pa=y;
}
update(root);
}
}
void split_min(int root,int &x,int &y,int k)// [] [min,...],必须维护min才能拆环
{
if (!root) x=y=0;
else
{
int lc=T[root].lc,rc=T[root].rc;
if (std::min(T[lc].min,root)!=k) //k必须是整个Treap的min,而非这个root的min
{
x=root;if (T[x].rc) T[T[x].rc].pa=0;
split_min(rc,T[x].rc,y,k);
if (T[x].rc) T[T[x].rc].pa=x;
}
else
{
y=root;if (T[y].lc) T[T[y].lc].pa=0;
split_min(lc,x,T[y].lc,k);
if (T[y].lc) T[T[y].lc].pa=y;
}
update(root);
}
}
int find(int x,int &root)//查询某位置所属的平衡树,及其在环中的位置
{
int size=T[T[x].lc].size+1;
int pa=0;
while (pa=T[x].pa)
{
if (T[pa].rc==x) size+=T[T[pa].lc].size+1;
x=pa;
}
root=x;
return size;
}
int first(int root)//找到环的首位置 的原序列下标
{
return T[root].min ;//一直向左儿子跑,答案不一定对。一个Treap去掉第一个点后的最小值不一定是第二个点,要重新split
}
int getval(int root,int k)//找到平衡树第k个位置的 原序列下标
{
while (root)
{
if (T[T[root].lc].size+1==k) return root;
else if (T[T[root].lc].size+1>k) root=T[root].lc;
else k-=T[T[root].lc].size+1,root=T[root].rc;
}
return 0;
}
void print(int root)
{
if (!root) return ;
print(T[root].lc);
printf("(%d,%d,%d,%d) ",root,T[root].lc,T[root].rc,T[root].size);
print(T[root].rc);
}
}
long long lcm[4*N];
long long gcd(long long x,long long y){return y==0?x:gcd(y,x%y);}//
void Build(int root,int l,int r)//
{
if (l==r)
{
lcm[root]=1;
return ;
}
int mid=(l+r)>>1;
Build(root<<1,l,mid);Build(root<<1|1,mid+1,r);
lcm[root]=1;
}
void update(int root,int l,int r,int x,long long v)//
{
if (l==r)
{
lcm[root]=v;
return ;
}
int mid=(l+r)>>1;
if (x<=mid) update(root<<1,l,mid,x,v);
else update(root<<1|1,mid+1,r,x,v);
if (lcm[root<<1]==-1||lcm[root<<1|1]==-1) lcm[root]=-1;
else if (lcm[root<<1]==0||lcm[root<<1|1]==0) lcm[root]=lcm[root<<1]+lcm[root<<1|1];
else
{
long long LCM1=lcm[root<<1],LCM2=lcm[root<<1|1];
long long GCD=gcd(LCM1,LCM2);
LCM1/=GCD;
if (LCM1>(MAX+LCM2-1)/LCM2) lcm[root]=-1;
else lcm[root]=LCM1*LCM2;
}
}
int query(int root,int l,int r,long long x)//查询第一个产生字典序不同的环 的首位位置
{
if (l==r) return l;
int mid=(l+r)>>1;
if (lcm[root<<1]==-1||(lcm[root<<1]!=0&&x%lcm[root<<1]!=0)) return query(root<<1,l,mid,x);//写成了root<<1,l,r,x ...
else return query(root<<1|1,mid+1,r,x);
}
void Solve(int x,int y)
{
int root1=0;int pos1=Treap::find(x,root1);//查询某位置所属的平衡树,及其在环中的位置
int root2=0;int pos2=Treap::find(y,root2);//查询某位置所属的平衡树,及其在环中的位置
if (root1==root2)
{
if (pos1>pos2) std::swap(pos1,pos2),std::swap(root1,root2);
int a,b,c,d;
Treap::split(root1,a,b,pos1);
Treap::split(b,b,c,pos2-pos1-1);
Treap::split(c,c,d,1);
root1=Treap::merge(a,d);//root1的最小是肯定还是 merge(a,d)的最小值
b=Treap::merge(b,c);Treap::split_min(b,b,c,Treap::T[b].min);root2=Treap::merge(c,b);
//必须通过min来求出最小值, root1这棵树split以后的b这棵树的第一个点不能保证是最小值 。。。
int first1=Treap::first(root1);//找到环的首位置 的原序列下标
int first2=Treap::first(root2);//找到环的首位置 的原序列下标
update(1,1,n,first1,Treap::T[root1].size);
update(1,1,n,first2,Treap::T[root2].size);
}
else
{
int first1=Treap::first(root1);//找到环的首位置 的原序列下标
int first2=Treap::first(root2);//找到环的首位置 的原序列下标
if (first1>first2) std::swap(first1,first2),std::swap(root1,root2),std::swap(pos1,pos2);//漏了swap(pos1,pos2)。。。
int a,b,c,d,e,f;
Treap::split(root1,a,b,pos1-1);
Treap::split(b,b,c,1);
Treap::split(root2,d,e,pos2-1);
Treap::split(e,e,f,1);
//a, b,f,d,e, c
int root=Treap::merge(Treap::merge(Treap::merge(Treap::merge(Treap::merge(a,b),f),d),e),c);
update(1,1,n,first1,Treap::T[root].size);
update(1,1,n,first2,0);
}
}
int main()
{
int T;scanf("%d",&T);
while (T--)
{
Treap::T[0].min=0x3f3f3f3f;
scanf("%d%d",&n,&q);
For(i,1,n) a[i]=i;Treap::Init();Build(1,1,n);
For(i,1,q)
{
char opt[10];long long x,y;
scanf("%s%lld%lld",opt,&x,&y);
if (strcmp(opt,"swap_a")==0)
{
if (x==y) continue;
std::swap(a[x],a[y]);
}
else if (strcmp(opt,"swap_p")==0)
{
if (x==y) continue;
Solve(x,y);
}
else
{
if (lcm[1]!=-1&&ABS(x-y)%lcm[1]==0) {printf("=\n");continue;}
int pos=query(1,1,n,ABS(x-y));//查询第一个产生字典序不同的环 的首位位置
int root=0;Treap::find(pos,root);//查询该位置所属的平衡树,及其在环中的位置
int pos1=Treap::getval(root,1+(x-1)%Treap::T[root].size);//找到平衡树第k个位置的 原序列下标
int pos2=Treap::getval(root,1+(y-1)%Treap::T[root].size);//找到平衡树第k个位置的 原序列下标
if (a[pos1]<a[pos2]) printf("<\n");
else printf(">\n");
}
}
}
return 0;
}
I Equivalence in Connectivity
题目难度:medium-hard
题目大意:
两个无向图是连通性等价的,当前仅当它们的可达性矩阵完全相同。(即u和v在第一个图联通,在第二个图也联通)
给定一个n个点m条边初始图G1,另外给定k-1个图G2...Gk ,每个图会从某个Gp加边/删边得来。
询问根据连通性能将这k个图分成几个等价类?每个等价类具体有哪些图?
解题思路:
首先所有的图会构成一棵树,树上每个点的加边和删边都会影响整个子树
通过dfs序将这棵树变成一个序列,则加边和删边影响的范围都是一个区间
将加边和删边的操作记录在线段树的各个区间上,那么从线段树根到线段树叶子的路径上记录的所有加边操作即可通过并查集模拟
从某个叶子转到另一个叶子时需要撤销并查集的合并操作,用可撤销并查集即
需要注意的是从线段树根到线段树叶子的删边如何模拟?
由于图构成的树上某个点删的边一定是在某个祖先加的边,那么让这个祖先在加边的时候不要给这个点所对应子树加边即可
换言之,作为祖先我先不急着加边,先遍历所有子嗣,如果某个子嗣要删边了,那先让前面的子嗣加边,然后跳过当前子嗣,再考虑之后的子嗣
由于增删操作一共1e5次,每次增删都会修改线段树的Log的区间,实际复杂度是nlogn
至于最后在线段树上的模拟操作,一个nlogn个并查集增删,每次增删划分logn,则总复杂度为nlog2n
另外需要注意图的连通性可以用并查集哈希表示,但必须写成双哈希,单哈希会WA
参考代码:
查看代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<map>
#include<vector>
#include<queue>
#include<stack>
#define For(i,a,b) for(int i=a;i<=b;i++)
const int N=1e5+1000;
const int K=1e5+1000;
const int M=1e5+1000;
int k,n,m;
struct Operation
{
int x,y;
int operator < (const Operation&other)const {return x==other.x?y<other.y:x<other.x;}
};
namespace UnionFind //可撤销并查集
{
Operation stack[N];int top;
int pa[N],size[N];
unsigned long long hash[N],hash_sum;//hash_sum代表当前图的连通性 hash[i]表示并查集中i这课子树的所有hash的异或
std::map<unsigned long long,std::vector<int> > ans;//答案
void Init()
{
hash_sum=0;top=0;ans.clear();
For(i,1,n)
{
pa[i]=-1,size[i]=1;
// hash[i]=((long long)rand()<<32)+rand();//必须得这样写???
// hash_sum+=hash[i];
hash[i]=((long long)rand()<<32)+rand();//换了种哈希法,但初值也必须设成这样
//这种写法相当于是双哈希了,因为rand的最大值是32767,rand*n最大值为3,276,700,000,但2^32=4,294,967,296,所以不会爆
hash_sum^=hash[i];
}
}
int find(int x)
{
if (pa[x]==-1) return x;
return find(pa[x]);
}
void merge(int x,int y)
{
x=find(x);y=find(y);if (x==y) return ;
if (size[x]>size[y]) std::swap(x,y);
pa[x]=y;
size[y]+=size[x];
// hash_sum-=hash[x]+hash[y];
// hash[y]^=hash[x];
// hash_sum+=hash[y];
hash_sum^=hash[x]^hash[y];
hash[y]+=hash[x];
hash_sum^=hash[y];
stack[++top]=(Operation){x,y};
}
void back()
{
int x=stack[top].x,y=stack[top].y;
pa[x]=-1;
size[y]-=size[x];
// hash_sum-=hash[y];
// hash[y]^=hash[x];
// hash_sum+=hash[x]+hash[y];
hash_sum^=hash[y];
hash[y]-=hash[x];
hash_sum^=hash[x]^hash[y];
top--;
}
}
namespace SegTree //线段树
{
std::vector<Operation> operation[4*K];//n个operation,每个operation分配到logn个区间
void Build(int root,int l,int r)
{
operation[root].clear();
if (l==r) return ;
int mid=(l+r)>>1;
Build(root<<1,l,mid);Build(root<<1|1,mid+1,r);
}
void update(int root,int l,int r,int a,int b,Operation v)
{
if (l==a&&r==b) {operation[root].push_back(v);return ;}//
int mid=(l+r)>>1;
if (b<=mid) update(root<<1,l,mid,a,b,v);
else if (a>mid) update(root<<1|1,mid+1,r,a,b,v);
else update(root<<1,l,mid,a,mid,v),update(root<<1|1,mid+1,r,mid+1,b,v);
}
void Solve(int root,int l,int r)
{
int pre_top=UnionFind::top;
For(i,0,(int)operation[root].size()-1) UnionFind::merge(operation[root][i].x,operation[root][i].y);
if (l==r)
{
UnionFind::ans[UnionFind::hash_sum].push_back(l);
}
else
{
int mid=(l+r)>>1;
Solve(root<<1,l,mid);Solve(root<<1|1,mid+1,r);
}
while (UnionFind::top>pre_top) UnionFind::back();
}
}
namespace GraphTree//由图组成的树
{
std::map<Operation,std::stack<std::pair<int,int> > > NowAddEdge;//从根图到当前图的加边,每一层对应stack中的连续若干个
//保证删边时一定删的最靠近当前图的那个图的加边,即stack的栈顶
Operation operation[K],init_operation[M];
std::vector<int> To[K];
int L[K],R[K],match[N];int cnt;
void get_LR(int now)
{
L[now]=++cnt;match[cnt]=now;
For(i,0,(int)To[now].size()-1) get_LR(To[now][i]);
R[now]=cnt;
}
void dfs(int now)//本题重点:将删边转为加边
{
int pre_size=0;
if (now==1)
{
For(i,1,m) NowAddEdge[init_operation[i]].push(std::make_pair(1,k));
}
else if (operation[now].x>0)
{
pre_size=NowAddEdge[operation[now]].size();
NowAddEdge[operation[now]].push(std::make_pair(L[now],R[now]));
}
else
{
operation[now].x*=-1;
std::stack<std::pair<int,int> >&stack=NowAddEdge[operation[now]];
std::pair<int,int> last=stack.top();stack.pop();
//[last.first,last.second] ->[last.first,L[now]-1] [R[now]+1,last.second]
if (last.first<=L[now]-1) stack.push(std::make_pair(last.first,L[now]-1));
if (R[now]+1<=last.second) stack.push(std::make_pair(R[now]+1,last.second));
operation[now].x*=-1;
}
For(i,0,(int)To[now].size()-1) dfs(To[now][i]);
if (now==1)
{
for(std::map<Operation,std::stack<std::pair<int,int> > >::iterator it=NowAddEdge.begin();it!=NowAddEdge.end();it++)
{
std::stack<std::pair<int,int> >&stack=it->second;
while (!stack.empty())
{
std::pair<int,int> last=stack.top();stack.pop();
SegTree::update(1,1,k,last.first,last.second,it->first);
}
}
}
else if (operation[now].x>0)
{
std::stack<std::pair<int,int> >&stack=NowAddEdge[operation[now]];
while (pre_size!=-1&&(int)stack.size()>pre_size)
{
std::pair<int,int> last=stack.top();stack.pop();
SegTree::update(1,1,k,last.first,last.second,operation[now]);
}
}
}
void Solve()
{
scanf("%d%d%d",&k,&n,&m);
NowAddEdge.clear();
For(i,1,m)
{
Operation opt;scanf("%d%d",&opt.x,&opt.y);if (opt.x>opt.y) std::swap(opt.x,opt.y);
init_operation[i]=opt;
}
For(i,1,k) To[i].clear();
For(i,2,k)
{
int p;char s[10];
scanf("%d%s%d%d",&p,s,&operation[i].x,&operation[i].y);
To[p].push_back(i);
if (operation[i].x>operation[i].y) std::swap(operation[i].x,operation[i].y);
if (s[0]=='r') operation[i].x*=-1;
}
cnt=0;get_LR(1);
SegTree::Build(1,1,k);dfs(1);
}
}
int main()
{
srand((int)time(0));
int T;scanf("%d",&T);
while (T--)
{
GraphTree::Solve();
UnionFind::Init();SegTree::Solve(1,1,k);
printf("%d\n",UnionFind::ans.size());
for(std::map<unsigned long long,std::vector<int> >::iterator it=UnionFind::ans.begin();it!=UnionFind::ans.end();it++)
{
std::vector<int> &vector =it->second;
printf("%d",vector.size());
For(i,0,(int)vector.size()-1) printf(" %d",GraphTree::match[vector[i]]);putchar('\n');
}
}
return 0;
}