模拟106 题解

A. 合并集合

显然的区间dp。

断环成链,预处理出每个连续区间集合的元素个数。

然后直接dp就完了。

B. climb

想了一些简单的贪心,然后都伪了。

所以考虑如何暴力$O(n^2)$来做这个题。

枚举最终用来跳最后一步的药丸,显然前面的药丸可以按$a_i-b_i$排序。

然后考虑如何优化这个过程,问题在于如何判断溺水的情况。

溺水的情况只出现在$a_i-b_i$的前缀和小于等于$c_i$的前缀和。

然而对于单个元素的删除添加,很多元素的下标会出现移位,然后就很难搞。实际上并没有很多,是我弱智了

一个方法是线段树分治,首先对所有的药丸按$a_i-b_i$从大到小排序。

$solve(l,r)$表示用来跳最后一步的药丸在$(l,r)$范围内,已经考虑其它状态。

这个做法的好处在于,当$(l,r)$确定,除掉这个区间内每个药丸的排名也是确定的。

维护两棵线段树,下标为排名,

其中第一棵维护$a_i-b_i-c_i$前缀和的最小值,第二棵维护$a_i-b_i$前缀和的最大值。

递归$solve(mid+1,r)$之前,将$[l,mid]$之间的贡献统计,暴力求出范围内的前缀和,之后区间加$l-1$前缀和的值。

同理递归$solve(l,mid)$之前,将$[mid+1,r]$之间的贡献统计,暴力求出范围内的前缀和,更新$[r+1,n]$之间元素的值。

当递归到$l=r$,可以现在第一棵线段树上二分确定不会溺水的边界,再在第二棵线段树上二分确定最早可以跳出的时间,更新最终答案就好了。

虽然分治套线段树,但是这个做法的复杂度是$O(nlogn)$的。

因为$solve$函数显然只会调用$n$次,对于每次调用:

在线段树上确定区间是$log$复杂度的,因为线段树集合划分的性质,之后暴力递归到叶子节点是与长度线性相关的。

对于最终$l=r$,两次二分都在线段树上实现,所以复杂度也是正确的。

考场上打了$4K$代码,一遍过对拍。

起初有两处导致复杂度$O(nlog^2)$的地方,最终一步步改到了$O(nlogn)$,打起来确实很爽。

然而实际上我的做法并没有什么必要,因为邻项的修改只会更改一位上的值,普通线段树也可以实现。

  1 #include<algorithm>
  2 #include<iostream>
  3 #include<cstdio>
  4 #define ll long long
  5 #define cri const register 
  6 #define lch p<<1
  7 #define rch p<<1|1
  8 using namespace std;
  9 const int N=1e5+7;
 10 const int inf=0x3f3f3f3f;
 11 struct YaoWan{
 12     int a,b;
 13 }med[N];
 14 int n,ans=inf;
 15 long long lim,pre[N],w[N];
 16 struct node{
 17     ll val[2],lzy[2];//0表示最小值 减pre之后的 
 18 }s[N<<2];//1表示最大值 不减pre的
 19 inline void down(cri int p){
 20     if(s[p].lzy[0]){
 21         s[lch].val[0]+=s[p].lzy[0];
 22         s[rch].val[0]+=s[p].lzy[0];
 23         s[lch].lzy[0]+=s[p].lzy[0];
 24         s[rch].lzy[0]+=s[p].lzy[0];
 25         s[p].lzy[0]=0;
 26     }
 27     if(s[p].lzy[1]){
 28         s[lch].val[1]+=s[p].lzy[1];
 29         s[rch].val[1]+=s[p].lzy[1];
 30         s[lch].lzy[1]+=s[p].lzy[1];
 31         s[rch].lzy[1]+=s[p].lzy[1];
 32         s[p].lzy[1]=0;
 33     }
 34 }
 35 inline void up(cri int p){
 36     s[p].val[0]=min(s[lch].val[0],s[rch].val[0]);
 37     s[p].val[1]=max(s[lch].val[1],s[rch].val[1]);
 38 }
 39 void build(cri int p,cri int l,cri int r){
 40     if(l==r) return s[p].val[0]=-pre[l],void();
 41     int mid=l+r>>1;
 42     build(lch,l,mid);
 43     build(rch,mid+1,r);
 44     up(p);
 45 }
 46 void toadd(cri int p,cri int fl,cri int l,cri int r,cri int L,cri int R,cri int f){
 47     if(L==R) return s[p].val[0]+=w[L-l+fl]*f,s[p].val[1]+=w[L-l+fl]*f,void();
 48     down(p);
 49     int mid=L+R>>1;
 50     if(l<=mid) toadd(lch,fl,l,r,L,mid,f);
 51     if(r>mid) toadd(rch,fl,l,r,mid+1,R,f);
 52     up(p);
 53 }
 54 void modify(cri int p,cri int l,cri int r,cri ll val,cri int L,cri int R){
 55     if(L>=l&&R<=r) return s[p].lzy[0]+=val,s[p].lzy[1]+=val,s[p].val[0]+=val,s[p].val[1]+=val,void();
 56     down(p);
 57     int mid=L+R>>1;
 58     if(l<=mid) modify(lch,l,r,val,L,mid);
 59     if(r>mid) modify(rch,l,r,val,mid+1,R);
 60     up(p);
 61 }
 62 ll query(cri int p,cri int l,cri int r,cri int L,cri int R){
 63     if(L>=l&&R<=r) return s[p].val[1];
 64     down(p);
 65     int mid=L+R>>1;
 66     if(r<=mid) return query(lch,l,r,L,mid);
 67     if(l>mid) return query(rch,l,r,mid+1,R);
 68     return max(query(lch,l,r,L,mid),query(rch,l,r,mid+1,R));
 69 }
 70 int ask(cri int p,cri ll add,cri int L,cri int R){
 71     if(L==R) return L;
 72     down(p);
 73     int mid=L+R>>1;
 74     if(s[lch].val[1]+add>=lim) return ask(lch,add,L,mid);
 75     else return ask(rch,add,mid+1,R);
 76 }
 77 int find(cri int p,cri int L,cri int R){
 78     if(L==R) return L;
 79     down(p);
 80     int mid=L+R>>1;
 81     if(s[lch].val[0]<=0) return find(lch,L,mid);
 82     else return find(rch,mid+1,R);
 83 }
 84 void solve(cri int l,cri int r){
 85     ll val;
 86     if(l==r){
 87         if(l!=1&&l!=n){
 88             val=query(1,l-1,l-1,1,n-1);
 89             modify(1,l,n-1,val,1,n-1);
 90         }
 91         register int fr=n-1;
 92         if(s[1].val[0]<=0) fr=find(1,1,n-1)-1;
 93         if(fr&&query(1,1,fr,1,n-1)+med[l].a>=lim) ans=min(ans,ask(1,med[l].a,1,n-1)+1);
 94         if(l!=1&&l!=n) modify(1,l,n-1,-val,1,n-1);
 95         return ;
 96     }
 97     cri int mid=l+r>>1;
 98     w[mid]=0;
 99     for(register int i=mid+1;i<=r;++i) w[i]=w[i-1]+med[i].a-med[i].b;
100     toadd(1,mid+1,mid,r-1,1,n-1,1);
101     if(r!=n) modify(1,r,n-1,w[r],1,n-1);
102     solve(l,mid);
103     w[mid]=0;
104     for(register int i=mid+1;i<=r;++i) w[i]=w[i-1]+med[i].a-med[i].b;
105     toadd(1,mid+1,mid,r-1,1,n-1,-1);
106     if(r!=n) modify(1,r,n-1,-w[r],1,n-1);
107     
108     w[l-1]=0;
109     for(register int i=l;i<=mid;++i) w[i]=w[i-1]+med[i].a-med[i].b;
110     toadd(1,l,l,mid,1,n-1,1);
111     if(l!=1){
112         val=query(1,l-1,l-1,1,n-1);
113         modify(1,l,mid,val,1,n-1);
114     }
115     solve(mid+1,r);
116     w[l-1]=0;
117     for(register int i=l;i<=mid;++i) w[i]=w[i-1]+med[i].a-med[i].b;
118     toadd(1,l,l,mid,1,n-1,-1);
119     if(l!=1) modify(1,l,mid,-val,1,n-1);
120 }
121 inline bool cmp(const YaoWan &x,const YaoWan &y){
122     return x.a-x.b>y.a-y.b;
123 }
124 inline int read(register int x=0,register char ch=getchar()){
125     for(;!isdigit(ch);ch=getchar()) ;
126     for(; isdigit(ch);ch=getchar()) x=(x<<1)+(x<<3)+(ch^48);
127     return x;
128 }
129 int main(){
130     freopen("climb.in","r",stdin);
131     freopen("climb.out","w",stdout);
132     n=read(); scanf("%lld",&lim);
133     for(int i=1;i<=n;++i){
134         med[i].a=read(); med[i].b=read();
135         if(med[i].a>=lim) return printf("%d\n",1),0;
136     }
137     for(int i=1;i<=n;++i) pre[i]=pre[i-1]+read();
138     if(n==1) return puts("-1"),0;
139     sort(med+1,med+n+1,cmp);
140     build(1,1,n-1);
141     solve(1,n);
142     printf("%d\n",ans==inf?-1:ans);
143     return 0;
144 }
T2

C. coin

当存在一种方案,使得二者共同努力可以达到全部硬币正面朝上,答案显然不会低于2。

否则答案只与行数+列数的奇偶性有关。

所以首先判局面是否可以转化到全部硬币正面朝上:

对于每一个硬币,

如果它是正面,那么行翻转对应列翻转,行不翻转对应列不翻转。

如果它是反面,那么行翻转对应列不翻转,行不翻转对应列翻转。

发现这个东西可以用拓展域并查集简单维护,

如果最终态存在矛盾,那么无解,否则存在至少一组解。

之后考虑先手是否必胜。

对于每一个集合,如果该集合对应着偶数个翻转,其对立集合对应着偶数个翻转,那么显然可以忽略这个集合。

其余的两个情况是:

该集合对应着奇数个翻转,其对立集合对应着偶数个翻转。

该集合对应着奇数个翻转,其对立集合对应着奇数个翻转。

所以只要考虑后两种情况,设$dp(i,j)$表示前一种状态有$i$个,后一种状态有$j$个。

显然$dp(0,0)=0$,对应先手必败。

有转移

$dp(i,j)|=!dp(i,j-1)$,表示简单选择一个两面都是奇数的集合,将其转化为没用的偶数集合。

$dp(i,j)|=!dp(i-1,j)$,表示选择一个奇偶面集合的奇面,将其转化为没用的偶数集合。

$dp(i,j)|=!dp(i-1,j+1)$,表示选择一个奇偶面集合的偶面,那么多一个奇数集合。

猜你喜欢

转载自www.cnblogs.com/skyh/p/11824295.html