3.15省选模拟
$T1$
主要问题是如何还原正方体,除了我之外的所有人都是用的还原三维坐标,只有我写了个奇奇怪怪的判断所在面的操作,常数很大,不开$O2$过不了,开了$O2$卡时限过
写的很麻烦...
#include<bits/stdc++.h> #define INF 0x3f3f3f3f #define MAXM 2500000 #define MAXN 350000 using namespace std; struct node { int bet,num; }Ti[MAXN]; int bc[10],Min_ans=INF,Max_ans,cnt,n; int head[MAXN],dis1[MAXN],Lai[MAXN],nxt[MAXM],pre[MAXN],In[MAXN][7],shu[MAXN],to[MAXM],Mian,tot; int dis[9][MAXN],poi[MAXN],poi_num; map<int,int>mp,ding; vector<int>Now; vector<int>rd[10][10]; void add(int u,int v) { tot++; to[tot]=v; nxt[tot]=head[u]; head[u]=tot; } void sol(int id) { scanf("%d",&Ti[id].bet); if(Ti[id].bet==0) mp[id]=++cnt,bc[cnt]=id; int num=0; char ch; int res=0; getchar(); ch=getchar(); do { if(ch>='0'&&ch<='9') res=(res*10+ch-'0'); else { num++; add(id,res); res=0; } }while((ch=getchar())!='\n'); add(id,res); ++num; Ti[id].num=num; if(num==3) poi[++poi_num]=id,ding[id]=poi_num; } string s; void bfs(int id,int st) { queue<int>q; memset(dis[id],0x3f,sizeof(dis[id])); dis[id][st]=0; q.push(st); while(!q.empty()) { int now=q.front(); q.pop(); for(int i=head[now];i;i=nxt[i]) { int y=to[i]; if(dis[id][y]==INF) { dis[id][y]=dis[id][now]+1; q.push(y); } } } } void check(int st) { for(int i=0;i<Now.size();i++) { for(int j=i+1;j<Now.size();j++) { ++Mian; for(int k=1;k<=n*n*n;k++) { if(dis[ding[Now[i]]][k]+dis[ding[Now[j]]][k]==2*(n-1)) { In[k][++shu[k]]=Mian; } } } } } void bfs_vis(int st) { queue<int>q; memset(dis1,0x3f,sizeof(dis1)); dis1[st]=0; q.push(st); while(!q.empty()) { int now=q.front(); q.pop(); for(int i=head[now];i;i=nxt[i]) { int y=to[i]; if(dis1[y]==INF) { dis1[y]=dis1[now]+1; if(dis1[y]==n-1) { if(ding[y]) { Now.push_back(y); } continue; } q.push(y); } } } } void bfs_Min(int id,int st) { queue<int>q; memset(dis1,0x3f,sizeof(dis1)); memset(pre,0,sizeof(pre)); memset(Lai,0,sizeof(Lai)); q.push(st); dis1[st]=0; while(q.size()) { int now=q.front(); q.pop(); for(int i=1;i<=shu[now];i++) { if(!Lai[In[now][i]]) { Lai[In[now][i]]=now; } } for(int i=head[now];i;i=nxt[i]) { int y=to[i]; if(dis1[y]==INF) { dis1[y]=dis1[now]+1; pre[y]=now; q.push(y); } } } for(int i=1;i<=6;i++) { int now=Lai[i]; while(now!=st) { rd[id][i].push_back(now); now=pre[now]; } } } int zong=0,X[10],Num[MAXN]; void dfs_ans(int now) { if(now==cnt+1) { int res=0; for(int i=1;i<=cnt;i++) { for(int j=0;j<rd[i][X[i]].size();j++) { if(!Num[rd[i][X[i]][j]]) res+=Ti[rd[i][X[i]][j]].bet,Num[rd[i][X[i]][j]]++; zong++; } } for(int i=1;i<=cnt;i++) { for(int j=0;j<rd[i][X[i]].size();j++) { Num[rd[i][X[i]][j]]=0; zong++; } } Max_ans=max(Max_ans,res); Min_ans=min(Min_ans,res); return ; } int k[7]={0,1,2,3,4,5,6}; random_shuffle(k+1,k+1+6); for(int i=6;i>=1;i--) { X[now]=k[i]; dfs_ans(now+1); } } int main() { srand(time(0)); scanf("%d",&n); for(int i=1;i<=n*n*n;i++) { sol(i); } for(int i=1;i<=8;i++) { bfs(i,poi[i]); } bfs_vis(poi[1]); check(poi[1]); for(int i=2;i<=8;i++) { if(dis[1][poi[i]]!=3*(n-1)) continue; Now.clear(); bfs_vis(poi[i]); check(poi[i]); break; } for(int i=1;i<=cnt;i++) { bfs_Min(i,bc[i]); } dfs_ans(1); cout<<Min_ans<<" "<<Max_ans<<endl; }
莫名其妙心态就有点崩.调整了一会再写
$T2$
是一个图上博弈问题
两人分别能占领图上的节点,最后$B$完全占领$T$集合就胜利.问最小需要提前$k$步就能取胜
感觉原来做过类似的树上博弈问题
先考虑树上的情况,显然的,$A$只要有点脑子就不会走叉,最后一定是一条链
那么记录数组$f_i$表示目前$A$占领了$i$号节点,在$i$号节点子树内,$B$至少要占领多少节点
$g_i$表示$i$号节点子树内距离$i$号店最近的被$B$占领的位置
$i\in T,f_i=g_i$必染
否则$f_i=min(g_i,\sum_{j\in son_i}max(f_j-dis(i,j),0))$
这个式子就是在几个里面取$min$,后面那个式子就是你需要在每个子树内必染的数目减去距离就是提供决策
那么转移显然了
那么树上的轻松解决了,对于环上而言
如果环上存在$B$占据的节点,那么往两侧的答案是独立的,那么变为虚点统计即可,由于肯定走的是一条链,那么直接对于两部分分别跑一遍就好了
具体还是要更麻烦些
可以考虑拆坏为点,不存在跨树时候只需要求出每个树的答案,还需要保证顺逆时针的前缀和都大于等于$0$,确保能够,由于新的转移没有$max(0,val)$,如果小于$0$的话,$A$必然会直接走,那么这样下去增加到等于$0$,这样就可以保证优
跨树$(B)$的时候,由于有用的最多两个,那么分成两部分跑一遍答案相加就好了
那么$1$不在环上,可以提前处理好答案,当做一个节点挂到树上就好了,至于会覆盖到$1$的其他子树,那么加一个虚点更新一下$g$就好了
$T3$
可持久化线段树
观察$4$个操作,把最左边隔板小于等于$h$和右边隔板小于等于$h$的一段范围赋值
大概就是下面一堆隔板中间水池维护的过程
感觉这题是一模拟...
考虑几个操作
加水操作,考虑这个水能漫到哪里为止,肯定是左右第一个大于$h$的位置,当然要特殊处理已经大的情况
防水操作,首先判断哪一部分会受到影响,显然是目前水的高度$h$,左右第一个大于$h$的位置之间都会受到影响,那么最后的高度是目前位置到防水节点中间最高的位置
其余的操作就是简单单点修改,单点查询就好了
考虑维护什么,显然需要维护隔板最高值,还需要维护水的高度就好了
#include<bits/stdc++.h> #define INF 2147483647 #define MAXN 200005 using namespace std; int h[MAXN],rt[MAXN]; int n,m,cnt; struct Tree { int ls,rs; int tag,val,Maxl,Maxr; }tr[MAXN<<6]; void upd(int now) { tr[now].Maxl=max(tr[tr[now].ls].Maxl,tr[tr[now].rs].Maxl); tr[now].Maxr=max(tr[tr[now].ls].Maxr,tr[tr[now].rs].Maxr); } int New(int lst) { cnt++; tr[cnt].ls=tr[lst].ls; tr[cnt].rs=tr[lst].rs; tr[cnt].val=tr[lst].val; tr[cnt].tag=tr[lst].tag; tr[cnt].Maxl=tr[lst].Maxl; tr[cnt].Maxr=tr[lst].Maxr; return cnt; } void pd(int now) { //至于什么时候新开节点 //这个由于区间修改时候先把上一个的左右儿子附上去 //其余的跟着改就好了 if(tr[now].tag) { tr[now].ls=New(tr[now].ls); tr[now].rs=New(tr[now].rs); if(tr[now].tag==1) { int ls=tr[now].ls; int rs=tr[now].rs; tr[ls].tag=1; tr[ls].val=tr[now].val; tr[rs].tag=1; tr[rs].val=tr[now].val; } if(tr[now].tag==2) { //先释放右边,后释放左边 int ls=tr[now].ls; int rs=tr[now].rs; tr[ls].tag=tr[rs].tag=2; tr[rs].val=tr[now].val; // cout<<"vzl: "<<tr[now].val<<endl; //这个val就是lz和高度,艹 //我就说为啥一开始build没看见呢... tr[ls].val=max(tr[now].val,tr[tr[now].rs].Maxl); } if(tr[now].tag==3) { int ls=tr[now].ls; int rs=tr[now].rs; tr[ls].tag=tr[rs].tag=3; tr[ls].val=tr[now].val; tr[rs].val=max(tr[now].val,tr[tr[now].ls].Maxr); } tr[now].tag=0; } } void build(int &now,int l,int r) { //线段树维护一个区间最大值就好了 //维护一个向左的最大值和一个向右的最大值 if(!now) now=++cnt; if(l==r) { tr[now].Maxl=h[l-1]; tr[now].Maxr=h[l]; return ; } int mid=(l+r)>>1; build(tr[now].ls,l,mid); build(tr[now].rs,mid+1,r); upd(now); } int query(int now,int l,int r,int poz) { // cout<<"q: "<<l<<" "<<r<<" "<<tr[now].val<<endl; if(l==poz&&r==poz) { return tr[now].val; } pd(now); int mid=(l+r)>>1; if(poz<=mid) return query(tr[now].ls,l,mid,poz); else return query(tr[now].rs,mid+1,r,poz); } int GetL(int now,int L,int R,int x,int h) { //首先确定位置 //如果这个时候最左边只能往左 //否则优先向右 if(tr[now].Maxl<h) return -1; if(L==R) return L; int mid=(L+R)>>1; pd(now); int ls=tr[now].ls,rs=tr[now].rs; if(x<=mid) return GetL(ls,L,mid,x,h); else { int res=GetL(rs,mid+1,R,x,h); if(res==-1) res=GetL(ls,L,mid,x,h); return res; } } int GetR(int now,int L,int R,int x,int h) { if(tr[now].Maxr<h) return -1; if(L==R) return L; int mid=(L+R)>>1; pd(now); int ls=tr[now].ls,rs=tr[now].rs; if(x>mid) return GetR(rs,mid+1,R,x,h); else { int res=GetR(ls,L,mid,x,h); if(res==-1) res=GetR(rs,mid+1,R,x,h); return res; } } void change(int &now,int L,int R,int l,int r,int val) { //这个也需要打lz标记 now=New(now); if(L>=l&&R<=r) { tr[now].tag=1; tr[now].val=val; return ; } pd(now); int mid=(L+R)>>1; if(l<=mid) change(tr[now].ls,L,mid,l,r,val); if(r>mid) change(tr[now].rs,mid+1,R,l,r,val); } void changel(int &now,int L,int R,int l,int r,int &val) { //大概是区间赋右边最大值操作了 now=New(now); if(L>=l&&R<=r) { tr[now].tag=2; // cout<<"vaz: "<<val<<" "<<endl; tr[now].val=val; val=max(val,tr[now].Maxl); // cout<<"tm[]: "<<val<<endl; return ; } //显然的,这个东西直接赋值右边最大值就好了 //下放的时候一样下放就好了 pd(now); int mid=(L+R)>>1; if(r>mid) changel(tr[now].rs,mid+1,R,l,r,val); if(l<=mid) changel(tr[now].ls,L,mid,l,r,val); } void changer(int &now,int L,int R,int l,int r,int &val) { now=New(now); if(L>=l&&R<=r) { tr[now].tag=3; tr[now].val=val; val=max(val,tr[now].Maxr); return ; } pd(now); int mid=(L+R)>>1; if(l<=mid) changer(tr[now].ls,L,mid,l,r,val); if(r>mid) changer(tr[now].rs,mid+1,R,l,r,val); } void changeh(int &now,int L,int R,int poz,int k,int opt) { now=New(now); // cout<<"ragL "<<tr[now].val<<endl; if(L==R) { if(opt) { tr[now].Maxr=k; } else tr[now].Maxl=k; return ; } pd(now); int mid=(L+R)>>1; if(poz<=mid) changeh(tr[now].ls,L,mid,poz,k,opt); else changeh(tr[now].rs,mid+1,R,poz,k,opt); upd(now); } int opt,ls,x,h1; int main() { scanf("%d%d",&n,&m); h[0]=h[n]=INF; for(int i=1;i<n;i++) { scanf("%d",&h[i]); } build(rt[0],1,n); for(int i=1;i<=m;i++) { scanf("%d",&opt); scanf("%d",&ls); rt[i]=rt[ls]; if(opt==0) { scanf("%d%d",&x,&h1); if(query(rt[i],1,n,x)<h1) { int L=GetL(rt[i],1,n,x,h1); int R=GetR(rt[i],1,n,x,h1); change(rt[i],1,n,L,R,h1); // cout<<"change: "<<L<<" "<<R<<endl; } } if(opt==1) { scanf("%d",&x); h1=query(rt[i],1,n,x); // cout<<"h1: "<<h1<<endl; int L=GetL(rt[i],1,n,x,h1); int R=GetR(rt[i],1,n,x,h1); // cout<<"opt1: "<<L<<" "<<R<<endl; int Midval=0; changel(rt[i],1,n,L,x,Midval); Midval=0; changer(rt[i],1,n,x,R,Midval); //分别找到左右第一个大的 //我觉得向左找的话 //先看右儿子大就往右,否则往左 //其余同理 //现在才知道主席树只是一种思想罢了 } if(opt==2) { scanf("%d",&x); scanf("%d",&h1); //还是先找左右区间 changeh(rt[i],1,n,x+1,h1,0); changeh(rt[i],1,n,x,h1,1); //这个就是区间更改了 //首先这个区间每一部分都会改变 //那么改变后的值就是之间较大的一部分了 } if(opt==3) { //直接单点改就好了 //更新一下往左/往右最大值就好了 //这个需要分开维护 scanf("%d",&x); cout<<query(rt[i],1,n,x)<<endl; } // cout<<"Now: "<<i<<endl; // for(int j=1;j<=n;j++) // { // query(rt[i],1,n,j); // } // cout<<endl<<endl; // cout<<"now: "<<i<<" "<<rt[i]<<endl; } }