好耶

题单

我单推这些题(

1|0P2824 [HEOI2016/TJOI2016]排序

两种做法应该都是很好的方法。

1|1二分

时间复杂度 O(nlog2n)

二分答案后赋权值为 0/1,有很优美的性质。

二分答案 mid,我们把原排列中大于等于 mid 的数都标记为 1,小于 mid 的都标记为 0。然后对于每个操作我们就将 01 序列排个序。最后如果第 p 个位子仍是 1 的话就是可行的。

太懒了并没有写

1|2DS

时间复杂度 O(nlogn)

考虑 ODT 的维护,可以把区间排序操作类比为区间染色。

然后对于区间分裂合并直接开权值线段树分裂合并即可。

时间复杂度证明即线段树分裂合并复杂度(

很好写 这是好的(

1|3Code

const int N=4e5+5,logN=20; int n,m,rt[N],nw=1,a[N]; struct range{ int l,r,v; range(int L,int R=-1,int V=0):l(L),r(R),v(V){} bool operator < (range x) const{return l<x.l;} }; set<range> qaq; #define IT set<range>::iterator struct SGT{ #define mid ((l+r)>>1) #define xl (ls[x]) #define xr (rs[x]) int sum[N*logN],ls[N*logN],rs[N*logN],st[N],tp,cnt; inline int newnode(){ if(tp) return st[tp--]; return ++cnt; } inline void pushup(int x){sum[x]=sum[xl]+sum[xr];} inline void del(int x){sum[x]=xl=xr=0;st[++tp]=x;} inline int insert(int x,int l,int r,int k,int v){ if(!x) x=newnode(); if(l==r){sum[x]+=v;return x;} if(k<=mid) xl=insert(xl,l,mid,k,v); else xr=insert(xr,mid+1,r,k,v); pushup(x);return x; } inline int merge(int x,int y,int l,int r){ if(!x||!y) return x|y; if(l==r){sum[x]+=sum[y];del(y);return x;} xl=merge(xl,ls[y],l,mid);xr=merge(xr,rs[y],mid+1,r); pushup(x);return x; } inline void split(int x,int &y,int l,int r,int k){ if(!x) return ;y=newnode(); if(k>sum[xl]) split(xr,rs[y],mid+1,r,k-sum[xl]); else xr^=rs[y]^=xr^=rs[y]; if(k<sum[xl]) split(xl,ls[y],l,mid,k); pushup(x);pushup(y);return ; } inline int kth(int x,int l,int r,int k){ if(l==r) return l; if(sum[xl]>=k) return kth(xl,l,mid,k); return kth(xr,mid+1,r,k-sum[xl]); } inline int query(int x,int l,int r,int L,int R){ if(L<=l&&r<=R) return sum[x]; ll res=0; if(mid>=L) res+=query(xl,l,mid,L,R); if(mid<R) res+=query(xr,mid+1,r,L,R); return res; } }T; // ---------- SGT ---------- // inline IT split(int pos){ IT tmp=qaq.lower_bound(range(pos)); if(tmp!=qaq.end()&&tmp->l==pos) return tmp; --tmp;int l=tmp->l,r=tmp->r,v=tmp->v; if(!v) T.split(rt[l],rt[pos],1,n,pos-l); else T.split(rt[l],rt[pos],1,n,r-pos+1),rt[l]^=rt[pos]^=rt[l]^=rt[pos]; qaq.erase(tmp);qaq.insert(range(l,pos-1,v)); return qaq.insert(range(pos,r,v)).first; } inline void assign(int l,int r,int op){ IT R=split(r+1),L=split(l),nw=L;++nw; while(nw!=R) T.merge(rt[l],rt[nw->l],1,n),++nw; qaq.erase(L,R);qaq.insert(range(l,r,op)); } int main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); rd(n);rd(m); for(re i=1;i<=n;++i){ rd(a[i]);rt[i]=T.insert(rt[i],1,n,a[i],1);qaq.insert(range(i,i,0)); } qaq.insert(range(n+1,n+1,0)); for(re i=1;i<=m;++i){ int op,l,r;rd(op);rd(l);rd(r); assign(l,r,op); } rd(m); for(IT nw=qaq.begin();nw!=qaq.end();++nw){ if(nw->r-nw->l+1<m) m-=nw->r-nw->l+1; else{ wr(T.kth(rt[nw->l],1,n,nw->v?nw->r-nw->l+2-m:m));puts("");break; } } return 0; } // ---------- Main ---------- //

2|0CF1559D Mocha and Diana

很好的贪心题。

A 图连通块个数不小于 B 图。

我们证明 B 图最后连通块个数为 1

A 连通块数量大于 1,若此时无法操作,考虑到 A 图中连通块 a, b

对于 ab 中的点,他们在 B 图中一定连通。

那么考虑到 A 中所有连通块,可发现 B 全连通,即连通块数量为 1

由此贪心分析可知,随意加可行边即可。

因此 D1 就可以直接暴力枚举点对加边即可,时间复杂度 O(n2α(n))

优化贪心。

考虑一个中心点 s

我们先让所有点与 s 尝试连边。

然后连完后令 A 图中与 s 不连通的点集为 LB 图中与 s 不连通的点集为 R

显然 LR=

考虑 lLrR

由定义有 A 图中 ls 不连通,rs 连通,B 图相反。

那么任意 lr 都可连边。

然后只要随意配对完 LR 就行了,此时一幅图变成一个连通块。

时间复杂度 O(nα(n))

2|1Code

const int N=1e5+5; int n,m1,m2,fa1[N],fa2[N],ans,qaq[N][2],l[N],r[N],cnt1,cnt2; inline int find1(int x){return fa1[x]==x?x:fa1[x]=find1(fa1[x]);} inline void merge1(int x,int y){fa1[find1(x)]=find1(y);} inline int find2(int x){return fa2[x]==x?x:fa2[x]=find2(fa2[x]);} inline void merge2(int x,int y){fa2[find2(x)]=find2(y);} // ---------- ---------- // int main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); // ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); rd(n);rd(m1);rd(m2); for(re i=1;i<=n;++i) fa1[i]=fa2[i]=i; for(re i=1;i<=m1;++i){ int x,y;rd(x);rd(y);merge1(x,y); } for(re i=1;i<=m2;++i){ int x,y;rd(x);rd(y);merge2(x,y); } for(re i=2;i<=n;++i) if(find1(i)!=find1(1)&&find2(i)!=find2(1)) qaq[++ans][0]=i,qaq[ans][1]=1,merge1(i,1),merge2(i,1); for(re i=2;i<=n;++i) if(find1(i)!=find1(1)) l[++cnt1]=i,merge1(i,1); else if(find2(i)!=find2(1)) r[++cnt2]=i,merge2(i,1); wr(ans+min(cnt1,cnt2));puts(""); for(re i=1;i<=ans;++i) wr(qaq[i][0]),putchar(' '),wr(qaq[i][1]),puts(""); for(re i=1;i<=min(cnt1,cnt2);++i) wr(l[i]),putchar(' '),wr(r[i]),puts(""); return 0; } // ---------- Main ---------- //

3|0P7824 「RdOI R3」毒水

很好的构造题。

观察到有 29<n<210

大概一看就是考虑二进制分组了。

1n 标号不太方便,我们改成 0n1

先看 maxk=30 的限制。

我们先选 10 只鼠编号为 09,让它们分别喝对应的水,编号为 i 的鼠喝的水的编号转化为二进制 2i 这一项系数为 1

考虑到变异鼠,我们每个鼠复制一下成三个鼠,这样就能辨别出变异鼠了。

然后考虑去掉变异鼠每个鼠存活情况,编号为 i 的鼠存活当且仅当毒水编号转化为二进制后 2i 这一项系数为 0

然后算出来即可。

然后考虑 maxk=15

显然不是 maxk2 的关系,因为这没啥实际意义。

很容易想到 log2n+log2log2n=14,盲猜是 log2n+log2log2n+1=15

log2log2n 这个东西容易想到是对上面我们鼠的编号再二进制分组。

考虑一些水的集合 S 和一些鼠的集合 M,保证对于 sSsM 中偶数个鼠喝了,且对于 mMm 喝的水都在 S 中。

有结论若 M 中死了偶数个则无变异鼠,若 M 中死了奇数个则有变异鼠。由定义和恰有一瓶毒水一只变异鼠易证。

那么考虑对 09 这些鼠再进行类似地二进制分组,编号为 10+i 的鼠喝的水为编号转化为二进制后 2i 系数为 1喝的水的异或。(异或有很好的性质能保证每瓶水被喝偶数次。)

为防止这 1013 四只鼠里有内鬼,再拿一只鼠喝他们喝的水的异或即可。

然后先考虑 1014 这里有无内鬼,若有则表明 09 里无内鬼,直接用上面 maxk=30 的方法算即可。

1014 无内鬼,则考虑 1013 和它们各自支配的鼠,分别考虑是否有内鬼,若 10+i 和支配的鼠里有内鬼,则表明内鬼鼠编号转化为二进制后 2i 系数为 1。把内鬼算出来然后直接把它存活状态取反即可。然后还像上面一样算出毒水即可。

记得 n=1/2 拿出来特判。

记得 cout 不知道为啥之前不用 cout 基本全 T 了。

3|1Code

const int N=1005,M=20; int n,q,sum1,sum2,s[M],qaq;bool op; bitset<N> a[M],ans; // ---------- ---------- // int main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); // ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); cin>>n>>q;ans.set(); if(n==1){cout<<2<<endl<<1<<endl;return 0;} if(n==2){cout<<"1 1 1"<<endl<<2<<endl;cin>>q;cout<<2-q<<endl;return 0;} sum1=log2(n-1)+1;sum2=log2(sum1-1)+1; for(re i=0;i<sum1;++i){ for(re j=0;j<n;++j) a[i][j]=(j>>i)&1;cout<<"1 "<<a[i].count()<<' '; for(re j=0;j<n;++j) if((j>>i)&1) cout<<j+1<<' ';cout<<endl; } for(re i=0;i<sum2;++i){ for(re j=0;j<sum1;++j) if((j>>i)&1) a[sum1+i]^=a[j];cout<<"1 "<<a[sum1+i].count()<<' '; for(re j=0;j<n;++j) if(a[sum1+i][j]) cout<<j+1<<' ';cout<<endl;a[sum1+sum2]^=a[sum1+i]; } cout<<"1 "<<a[sum1+sum2].count()<<' '; for(re j=0;j<n;++j) if(a[sum1+sum2][j]) cout<<j+1<<' ';cout<<endl<<2<<endl; for(re i=0;i<=sum1+sum2;++i) cin>>s[i],s[i]^=1,op^=i>=sum1?s[i]:0; if(!op){ for(re i=0;i<sum2;++i){ q=s[sum1+i];for(re j=0;j<sum1;++j) q^=((j>>i)&1)?s[j]:0;qaq+=q<<i; } s[qaq]^=1; } for(re i=0;i<sum1;++i) if(s[i]) ans&=a[i];cout<<ans._Find_first()+1<<endl; return 0; } // ---------- Main ---------- //

4|0P7825 「RdOI R3」VSQ

先咕

5|0AT2389 [AGC016E] Poor Turkeys

时光倒流 和那道 JOI 的断层思想差不多。

考虑倒推,即从最后一个人开始向前进行。

我们考虑 (i,j) 最后都没被扔掉,则在前面某人为 (x,i) 时,我们扔掉的一定是 x

那么此时我们就需要保证再往前 x 不能被扔掉。

若前面出现 (x,y) 且都不能被扔掉,那么 (i,j) 一定不是一组解。

注意到这里每次拓展实际是一个的拓展,而非一对的拓展。

我们可以将枚举 (i,j) 换成枚举 i,记录下如果要留下 i 前面一定不能被扔掉的数集 Si

此时若 SiSj=,则 (i,j) 满足题意。模型意义下即表示 ij 前面不能被扔掉的东西不重复,因为在 Si 中所有除 i 的东西都会被扔掉,而东西不能重复扔掉。

考虑到 SiSj 实际上只要对位完成与运算,用 bitset 存储即可。

时间复杂度 O(nm+n3ω)

5|1Code

const int N=405,M=1e5+5; int n,m,a[M],b[M],ans; bool tag[N]; bitset<N> s[N]; // ---------- ---------- // int main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); // ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); rd(n);rd(m); for(re i=1;i<=m;++i) rd(a[i]),rd(b[i]); for(re i=1;i<=n;++i) s[i][i]=1; for(re i=1;i<=n;++i) for(re j=m;j>0;--j){ if(s[i][a[j]]&&s[i][b[j]]){tag[i]=1;break;} if(s[i][a[j]]) s[i][b[j]]=1; if(s[i][b[j]]) s[i][a[j]]=1; } for(re i=1;i<=n;++i) if(!tag[i]){ for(re j=i+1;j<=n;++j) if(!tag[j]){ if((s[i]&s[j]).count()==0) ++ans; } } wr(ans);puts(""); return 0; } // ---------- Main ---------- //

6|0CF446B DZY Loves Modification

贪心合并 证伪真的挺难(

一开始有个很简单的贪心想法,就是每次取行列中总和最大的那个。

然后你冲了一发,发现你挂了。

WA on #4

然后其实你发现行列是对互相有后效影响的。

比如当你最大值又有行又有列,你是选哪个呢?

那么考虑到行或者列自己是不影响的。

那么考虑行列分开贪心,贪心策略同上。

li 为行选了 i 个最大答案,ri 为列。

显然有 ans=maxi=0k{li+rii×(ki)×p},这时你把行列拆开就可以直接算出行列之间相互影响,保证了贪心的正确性。

记得开 long long。

贪心随便拿个东西存一下就行了,我整了个 multiset,时间复杂度 O(klogmax(n,m))

6|1Code

const int N=1e3+5,K=1e6+5; int n,m,k,p,a[N][N],l[K],r[K],sl[N],sr[N],ans=-1e18; multiset<int> L,R; #define IT multiset<int>::iterator // ---------- ---------- // signed main(){ // freopen(".in","r",stdin); // freopen(".out","w",stdout); // ios::sync_with_stdio(0);cin.tie(0);cout.tie(0); rd(n);rd(m);rd(k);rd(p); for(re i=1;i<=n;++i) for(re j=1;j<=m;++j) rd(a[i][j]),sl[i]+=a[i][j],sr[j]+=a[i][j]; for(re i=1;i<=n;++i) L.insert(sl[i]); for(re i=1;i<=m;++i) R.insert(sr[i]); for(re i=1;i<=k;++i){ IT it=L.end();--it;int res=*it;l[i]=l[i-1]+res;L.erase(it);L.insert(res-p*m); it=R.end();--it;res=*it;r[i]=r[i-1]+res;R.erase(it);R.insert(res-p*n); } for(re i=0;i<=k;++i) ans=max(ans,l[i]+r[k-i]-i*(k-i)*p); wr(ans);puts(""); return 0; } // ---------- Main ---------- //

__EOF__

本文作者Daniel Jiang
本文链接https://www.cnblogs.com/danieljiang/p/goooooooooood.html
关于博主:JSOIer 高一萌新 求带/kel
版权声明:awa
声援博主:求赞/kel
posted @   Demoe  阅读(111)  评论(1编辑  收藏  举报
编辑推荐:
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· .NET Core 中如何实现缓存的预热?
· 三行代码完成国际化适配,妙~啊~
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
点击右上角即可分享
微信分享提示