神秘做题记录3(含追忆)
P11831
rerererererecall
题解
发现图的形态一点都不会变,所以可达性很好做,直接用bitset做一个传递闭包。
然后考虑加上A的限制,你需要维护一个新的bitset同时满足可达性和A的区间。
区间?可以分块了,每块开bitset,散点暴力即可,然后可以修改一下意义,块维护后缀块的和,然后查询l,r+1异或起来。
这样你就再一个合理的时间内得到了合法的点,但是这些合法的点B最大值为多少呢?设想一下可以二分,做一个后缀和,但是无法修改。
那就对B也分块,维护根号个bitset,和A一模一样的一点区别都没有,然后也不用二分了直接扫做完了。
一个细节,你怎么扫呢?可以先枚举bitset的位,但是不是bool位,是unsigned long long位,这就相当于每一个点更新一下,最后就是n/w+sqrt(n)的,否则两者要乘起来,过不了。
而且你要访问ull,就得手写bitset,好在写这个比写树状数组还简单,随便做。
多测记得清空。
代码
#include<bits/stdc++.h>
#define ull unsigned long long
using namespace std;
const int N = 100010,M = 1570,len = 320;
int C,T;
int n,m,q,cnt;
int a[N],b[N];
int ia[N],ib[N];//a,b的反映射
int ans;
vector<int> e[N];
struct bitset_{//reset set flip
ull num[M];
void reset(){
memset(num,0,sizeof(num));
}
void set(int p){
num[p>>6]|=(1ull<<(p&63));
}
void flip(int p){
num[p>>6]^=(1ull<<(p&63));
}
void operator &=(const bitset_ &tmp){
for(int i = 0;i<M;i++)num[i]&=tmp.num[i];
}
void operator |=(const bitset_ &tmp){
for(int i = 0;i<M;i++)num[i]|=tmp.num[i];
}
void operator ^=(const bitset_ &tmp){
for(int i = 0;i<M;i++)num[i]^=tmp.num[i];
}
int get(int p){
return (1ull&(num[p>>6]>>(p&63)));
}
};
bitset_ g[N];//传递闭包
bitset_ bl_a[len+10];//对A_i值域进行分块,大于等于idx*len的i,存到bitset里
bitset_ bl_b[len+10];//B_i也分块,可行性bitset和b值域的bitset与一下,sqrt(n)查询
bitset_ c,res1,res2;
void solve(){
cin>>n>>m>>q;
for(int i = 1;i<=n;i++)e[i].clear();
for(int i = 1;i<=m;i++){
int u,v;
cin>>u>>v;
e[u].push_back(v);
}
for(int i = n;i>=1;i--){
g[i].reset();
g[i].set(i);
for(int j = 0;j<e[i].size();j++){
g[i]|=g[e[i][j]];
}
}
cnt = 0;
while(cnt*len<=n)cnt++;
for(int i = 0;i<=cnt;i++){//根据分块的意义下标从0开始
bl_a[i].reset();
bl_b[i].reset();
}
for(int i = 1;i<=n;i++){
cin>>a[i];
ia[a[i]] = i;
}
for(int i = 1;i<=n;i++){
cin>>b[i];
ib[b[i]] = i;
}
for(int i = 1;i<=n;i++){
bl_a[a[i]/len].set(i);
bl_b[b[i]/len].set(i);
}
for(int i = cnt-1;i>=0;i--){
bl_a[i]|=bl_a[i+1];
bl_b[i]|=bl_b[i+1];
}
for(int iq = 1;iq<=q;iq++){
int t,x,y,l,r;
cin>>t;
if(t==1){//a_x a_y
cin>>x>>y;
int ll = a[x]/len,rr = a[y]/len;
if(ll>rr)swap(ll,rr);
for(int i = ll+1;i<=rr;i++){//这里很重要,改的是整块的!
bl_a[i].flip(x);
bl_a[i].flip(y);
}
swap(ia[a[x]],ia[a[y]]);
swap(a[x],a[y]);
}else if(t==2){//b_x b_y
cin>>x>>y;
int ll = b[x]/len,rr = b[y]/len;
if(ll>rr)swap(ll,rr);
for(int i = ll+1;i<=rr;i++){
bl_b[i].flip(x);
bl_b[i].flip(y);
}
swap(ib[b[x]],ib[b[y]]);
swap(b[x],b[y]);
}else{//query
cin>>x>>l>>r;
r++;
ans = 0;
c = g[x];
res1.reset(),res2.reset();
int h = (l+len-1)/len;//右边的块
res1 = bl_a[h];
for(int i = l;i<min(n+1,h*len);i++){//
res1.set(ia[i]);
}
h = (r+len-1)/len;
res2 = bl_a[h];
for(int i = r;i<min(n+1,h*len);i++){
res2.set(ia[i]);
}
res1^=res2;
c&=res1;
int now = 0;
for(int i = 0;i<M&&now<cnt-1;i++){
while(now<cnt-1&&(c.num[i]&bl_b[now+1].num[i])){
now++;
}
}
int ll = now*len;
if(ll==0)ll = 1;
int rr = min(n,(now+1)*len-1);
for(int i = rr;i>=ll;i--){
if(c.get(ib[i])){
ans = i;
break;
}
}
cout<<ans<<"\n";
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>C>>T;
while(T--){
solve();
}
return 0;
}
反思
感觉追忆不咋难啊,不降紫我还不敢做呢,但是我本来就不擅长数据结构,还是写了挺长时间的。
希望到省选的时候我能有场切追忆的水平。
其实这个目标不算很难实现?
P5419
思考过程
看到了题里给的提示,然后想那个转化,然后我就想模拟这个过程,每次操作n/2条边,使得它们没有交。
感觉比较乱,瞎做了一下就得6分。
其实这个构出来这题就切了。
题解
写出邻接矩阵。
对角线表示自环,我们没有自环,设为0。
然后发现邻接矩阵必然对称,对于每条边,我们只关注它是哪个集合的(这里的划分方式不变,n/2),编号1 to n-1。
对于一行,是一个点到所有点的连边,不能有相同的数,否则有交,列同理。
所以这是个拉丁方,并且满足对称且主对角线相等。
这个也太难构了!先不考虑对角线,对称就行,你可以形如
1 3 2
3 2 1
2 1 3
这么斜着放。
然后发现直接把对角线填上,因为是123,所以数的个数没变
0 3 2
3 0 1
2 1 0
数多了一种,扩大看看。
0 3 2 x
3 0 1 x
2 1 0 x
x x x 0
因为我们已经满足了拉丁方的性质,每行缺了对角线上那个,直接补。
0 3 2 1
3 0 1 2
2 1 0 3
1 2 3 0
做完了!
把每组的边填进来输出即可。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 510;
int n;
int e[N][N];
int get(int x,int mx){
if(x>mx)x-=mx;
if(x<1)x+=mx;
return x;
}
int cnt = 0;
int cur[N];
int main(){
cin>>n;
int now = 1;
for(int i = 1;i<=n;i++){
cur[i] = 1;
}
for(int p = 1;p<n;p++){
for(int i = now,j = 1;j<n;i--,j++){
i = get(i,n-1);
e[i][j] = p;
}
now = get(now+2,n-1);
}
for(int i = 1;i<n;i++){
e[i][n] = i;
}
for(int i = 1;i<=n;i++){
for(int j = i+1;j<=n;j++){
cout<<(e[i][j]-1)*(n/2)+cur[e[i][j]]<<" ";
cur[e[i][j]]++;
}
cout<<endl;
}
return 0;
}
反思
构造太神秘了。
总之把这个记住吧可能有用。
P5441
思考过程
完全图,看成一个圆。
我试着左手边连正边,右手边连反边,以为任取四个点都可以。
然后发现四个点在优弧上就死了,考虑双向边放哪里。
肯定放在优弧的最后最有了,这样C的上指标比较大,覆盖的情况多,推个式子证明了在有向边这样取的情况下这么做最优。
那么有向边这么取最优吗?我感性理解一下,边没有浪费的,所以这样取可以。
写了,获得99pts,发现n = 1需要特判,判完通过了。
是的,又切紫了。
题解
wwh给出了证明,先取一个点,假设它对取的另外三个点都向外连或向里连,就无了。
那么怎么使得这个最少呢,这个个数也是组合数,我们让两个组合数上指标接近均值,就最优了。
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 110;
int n;
int ans = 0;
int c[N][N];
int e[N][N];
inline int get(int x){
if(x>n)x-=n;
if(x<1)x+=n;
return x;
}
int main(){
cin>>n;
if(n==1){
cout<<"0\n0\n";
return 0;
}
c[0][0] = 1;
for(int i = 1;i<=n;i++){
c[i][0] = 1;
for(int j = 1;j<=min(i,4);j++){
c[i][j] = c[i-1][j]+c[i-1][j-1];
}
}
ans = c[n][4];
int res = 0;
for(int i = 2;i<=n/2-2;i++){
res+=n*c[i][2];
}
ans-=res;
for(int i = 1;i<=n;i++){
for(int j = 1,k = i+1;j<=n/2+1;j++,k++){
k = get(k);
e[i][k] = 1;
}
for(int j = n/2+1,k = i+n/2+1;j<n;j++,k++){
k = get(k);
e[k][i] = 1;
}
}
cout<<ans<<"\n";
for(int i = 1;i<=n;i++){
for(int j = 1;j<=n;j++){
cout<<e[i][j]<<" ";
}
cout<<"\n";
}
return 0;
}
反思
不太会证但是切了。
构造题是这样的。
P5811
思考过程
树会做了,发现考虑每条边就行。
这是40分,然后就想图能不能归约成树。
然后就是想点双,边双,园方树,最后想了一个看起来很真的边双算法,但是还是假的。
题解
图转化为树是对的,用dfs树即可。
然后,发现我们满足a,b即可(设a<b<c)
把重心搜出来,如果出了一个n/2左右的子树就通关了。
而且对于重心,如果任何一个子树都不能满足a,那么两个加起来不会超三分之二n,可以满足b。
子树能加吗?考虑到根是给b的,不好加,但是原图中还有边,我们凑一凑,凑出来大于a的就通关了。
一定要先判单个子树能不能符合a的要求!
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
int n,m;
int a,b,c;
pair<int,int> v[5];
vector<int> e[N];
bool intr[N];
vector<int> g[N];//dfs树
void get_dfs_tree(int x){
for(int i = 0;i<e[x].size();i++){
int h = e[x][i];
if(!intr[h]){
g[h].push_back(x);
g[x].push_back(h);
intr[h] = 1;
get_dfs_tree(h);
}
}
}
int siz[N],G;
void get_G(int x,int fa){
siz[x] = 1;
int mx = 1;
for(int i = 0;i<g[x].size();i++){
int h = g[x][i];
if(h!=fa){
get_G(h,x);
siz[x]+=siz[h];
mx = max(mx,siz[h]);
}
}
mx = max(mx,n-siz[x]);
if(mx<=n/2)G = x;
}
int bel[N],rt[N],idx = 0;
int g_siz[N];
void find_sons(int x,int fa){
g_siz[x] = 1;
if(fa==G){
bel[x] = ++idx;
rt[idx] = x;
}else if(x!=G){
bel[x] = bel[fa];
}
for(int i = 0;i<g[x].size();i++){
int h = g[x][i];
if(h!=fa){
find_sons(h,x);
g_siz[x]+=g_siz[h];
}
}
}
int cnt[N];
int in[N];
bool vis[N];
int k = 0,scc_cnt = 0;
void build(int x){
if(!in[bel[x]]){
in[bel[x]] = scc_cnt;
cnt[scc_cnt]+=g_siz[rt[bel[x]]];
}
if(cnt[scc_cnt]>=v[1].first)return;
for(int i = 0;i<e[x].size();i++){
int h = e[x][i];
if(!vis[h]){
vis[h] = 1;
build(h);
if(cnt[scc_cnt]>=v[1].first)return;
}
}
}
int ans[N];
int num = 0;
void color(int x,int fa,int typ,int col,int aim){
if(num==aim)return;
ans[x] = col;
num++;
for(int i = 0;i<e[x].size();i++){
int h = e[x][i];
if(h!=fa&&(in[bel[h]]^typ)&&ans[h]==0){//
color(h,x,typ,col,aim);
}
}
}
void recolor(){
for(int i = 1;i<=n;i++){
if(ans[i]==1)ans[i] = v[1].second;
else if(ans[i]==2)ans[i] = v[2].second;
else ans[i] = v[3].second;
}
}
int main(){
cin>>n>>m;
cin>>a>>b>>c;
v[1] = {a,1},v[2] = {b,2},v[3] = {c,3};
sort(v+1,v+4);
for(int i = 1;i<=m;i++){
int u,v;
cin>>u>>v;
u++,v++;
e[u].push_back(v);
e[v].push_back(u);
}
intr[1] = 1;
get_dfs_tree(1);
get_G(1,0);
find_sons(G,0);
for(int i = 1;i<=idx;i++){
if(g_siz[rt[i]]>=v[1].first){
scc_cnt = 1;
in[i] = 1;
cnt[scc_cnt] = g_siz[rt[i]];
k = i;
break;
}
}
vis[G] = 1;
if(k==0){
for(int i = 1;i<=idx;i++){
if(!in[i]){
scc_cnt++;
build(rt[i]);
if(cnt[scc_cnt]>=v[1].first){
k = i;
break;
}
}
}
}
if(k==0){
for(int i = 1;i<=n;i++)cout<<"0 ";
return 0;
}
for(int i = 1;i<=idx;i++){
if(in[i]==scc_cnt){
in[i] = 1;
}else{
in[i] = 0;
}
}
color(rt[k],G,0,1,v[1].first);
num = 0;
color(G,0,1,2,v[2].first);
recolor();
for(int i = 1;i<=n;i++){
cout<<ans[i]<<" ";
}
return 0;
}
反思
所以说黑题都这么神秘吗?
没敢想dfs树,挂着很多边感觉不好做,重心的性质更是不敢想。
代码也不怎么好写,但是也不算太难,和追忆难度差不多吧。
P11993
思考过程
看到是数据结构。
好吧我知道标签了,线段树合并,没想到怎么合。
然后发现给树分层很重要,于是想能不能分块,不能,然后想nq/w,追忆做多了是这样的。
大概糊了一下,发现应该可以,但是时间复杂度不行。
题解
看书一下看懂了,每层开一棵线段树,对dfn序开,然后合并直接合。
查询怎么查?查子树就完事了,这个点的权值要么是本身的要么是子树上来的。
所以说这么水我为什么想不出来?
代码
#include<bits/stdc++.h>
using namespace std;
const int N = 2000010,M = 64000010;
int n,q;
vector<int> e[N];
int a[N];
struct segment_tree{//change merge query
int num[M],ls[M],rs[M];
int rt[N],idx;
void push_up(int p){
num[p] = num[ls[p]]+num[rs[p]];
}
void change(int &p,int l,int r,int x,int y){
if(!p)p = ++idx;
if(l==r){
num[p]+=y;
return;
}
int mid = (l+r)>>1;
if(mid>=x)change(ls[p],l,mid,x,y);
else change(rs[p],mid+1,r,x,y);
push_up(p);
}
void merge(int &p,int q,int l,int r){
if(!q)return;
if(!p){
p = q;
return;
}
if(l==r){
num[p]+=num[q];
return;
}
int mid = (l+r)>>1;
merge(ls[p],ls[q],l,mid);
merge(rs[p],rs[q],mid+1,r);
push_up(p);
}
int query(int p,int l,int r,int ll,int rr){
if(p==0)return 0;
if(l>=ll&&r<=rr){
return num[p];
}
int mid = (l+r)>>1,res = 0;
if(mid>=ll)res+=query(ls[p],l,mid,ll,rr);
if(mid+1<=rr)res+=query(rs[p],mid+1,r,ll,rr);
return res;
}
}tr;
int siz[N],dfn[N],dep[N],cnt = 0,max_dep = 1;
void dfs(int x,int fa){
dep[x] = dep[fa]+1;
max_dep = max(max_dep,dep[x]);
dfn[x] = ++cnt;
siz[x] = 1;
for(int i = 0;i<e[x].size();i++){
int h = e[x][i];
if(h!=fa){
dfs(h,x);
siz[x]+=siz[h];
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i = 2;i<=n;i++){
int t;
cin>>t;
e[i].push_back(t);
e[t].push_back(i);
}
for(int i = 1;i<=n;i++){
cin>>a[i];
}
dfs(1,0);
tr.idx = max_dep;
for(int i = 1;i<=max_dep;i++){
tr.rt[i] = i;
}
for(int i = 1;i<=n;i++){
tr.change(tr.rt[dep[i]],1,n,dfn[i],a[i]);
}
cin>>q;
for(int i = 1;i<=q;i++){
int t,x,y;
cin>>t;
if(t==1){
cin>>x>>y;
if(x+1>max_dep)continue;
tr.merge(tr.rt[y+1],tr.rt[x+1],1,n);
tr.rt[x+1] = ++tr.idx;
}else if(t==2){
cin>>x>>y;
tr.change(tr.rt[dep[x]],1,n,dfn[x],y);
}else{
cin>>x;
cout<<tr.query(tr.rt[dep[x]],1,n,dfn[x],dfn[x]+siz[x]-1)<<'\n';
}
}
return 0;
}
反思
加训数据结构,发现了性质不会维护就完蛋了。

浙公网安备 33010602011771号