好耶
我单推这些题(
P2824 [HEOI2016/TJOI2016]排序
两种做法应该都是很好的方法。
二分
时间复杂度 \(O(n\log^2 n)\)。
二分答案后赋权值为 \(0/1\),有很优美的性质。
二分答案 \(mid\),我们把原排列中大于等于 \(mid\) 的数都标记为 \(1\),小于 \(mid\) 的都标记为 \(0\)。然后对于每个操作我们就将 \(01\) 序列排个序。最后如果第 \(p\) 个位子仍是 \(1\) 的话就是可行的。
太懒了并没有写
DS
时间复杂度 \(O(n\log n)\)。
考虑 ODT 的维护,可以把区间排序操作类比为区间染色。
然后对于区间分裂合并直接开权值线段树分裂合并即可。
时间复杂度证明即线段树分裂合并复杂度(
很好写 这是好的(
Code
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 ---------- //
CF1559D Mocha and Diana
很好的贪心题。
令 \(A\) 图连通块个数不小于 \(B\) 图。
我们证明 \(B\) 图最后连通块个数为 \(1\)。
若 \(A\) 连通块数量大于 \(1\),若此时无法操作,考虑到 \(A\) 图中连通块 \(a\), \(b\)。
对于 \(a\),\(b\) 中的点,他们在 \(B\) 图中一定连通。
那么考虑到 \(A\) 中所有连通块,可发现 \(B\) 全连通,即连通块数量为 \(1\)。
由此贪心分析可知,随意加可行边即可。
因此 D1 就可以直接暴力枚举点对加边即可,时间复杂度 \(O(n^2\alpha(n))\)。
优化贪心。
考虑一个中心点 \(s\)。
我们先让所有点与 \(s\) 尝试连边。
然后连完后令 \(A\) 图中与 \(s\) 不连通的点集为 \(L\),\(B\) 图中与 \(s\) 不连通的点集为 \(R\)。
显然 \(L\cap R=\varnothing\)。
考虑 \(l\in L\) 和 \(r\in R\)。
由定义有 \(A\) 图中 \(l\) 与 \(s\) 不连通,\(r\) 与 \(s\) 连通,\(B\) 图相反。
那么任意 \(l\) 与 \(r\) 都可连边。
然后只要随意配对完 \(L\) 或 \(R\) 就行了,此时一幅图变成一个连通块。
时间复杂度 \(O(n\alpha(n))\)。
Code
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 ---------- //
P7824 「RdOI R3」毒水
很好的构造题。
观察到有 \(2^9<n<2^{10}\)。
大概一看就是考虑二进制分组了。
\(1\sim n\) 标号不太方便,我们改成 \(0\sim n-1\)
先看 \(maxk=30\) 的限制。
我们先选 \(10\) 只鼠编号为 \(0\sim 9\),让它们分别喝对应的水,编号为 \(i\) 的鼠喝的水的编号转化为二进制 \(2^i\) 这一项系数为 \(1\)。
考虑到变异鼠,我们每个鼠复制一下成三个鼠,这样就能辨别出变异鼠了。
然后考虑去掉变异鼠每个鼠存活情况,编号为 \(i\) 的鼠存活当且仅当毒水编号转化为二进制后 \(2^i\) 这一项系数为 \(0\)。
然后算出来即可。
然后考虑 \(maxk=15\)。
显然不是 \(\dfrac{maxk}{2}\) 的关系,因为这没啥实际意义。
很容易想到 \(\left\lceil \log_2n \right\rceil+\left\lceil \log_2\left\lceil \log_2n \right\rceil \right\rceil=14\),盲猜是 \(\left\lceil \log_2n \right\rceil+\left\lceil \log_2\left\lceil \log_2n \right\rceil \right\rceil+1=15\)。
\(\left\lceil \log_2\left\lceil \log_2n \right\rceil \right\rceil\) 这个东西容易想到是对上面我们鼠的编号再二进制分组。
考虑一些水的集合 \(S\) 和一些鼠的集合 \(M\),保证对于 \(\forall s\in S\) 水 \(s\) 被 \(M\) 中偶数个鼠喝了,且对于 \(\forall m\in M\) 鼠 \(m\) 喝的水都在 \(S\) 中。
有结论若 \(M\) 中死了偶数个则无变异鼠,若 \(M\) 中死了奇数个则有变异鼠。由定义和恰有一瓶毒水一只变异鼠易证。
那么考虑对 \(0\sim 9\) 这些鼠再进行类似地二进制分组,编号为 \(10+i\) 的鼠喝的水为编号转化为二进制后 \(2^i\) 系数为 \(1\) 的鼠喝的水的异或。(异或有很好的性质能保证每瓶水被喝偶数次。)
为防止这 \(10\sim 13\) 四只鼠里有内鬼,再拿一只鼠喝他们喝的水的异或即可。
然后先考虑 \(10\sim 14\) 这里有无内鬼,若有则表明 \(0\sim 9\) 里无内鬼,直接用上面 \(maxk=30\) 的方法算即可。
若 \(10\sim 14\) 无内鬼,则考虑 \(10\sim 13\) 和它们各自支配的鼠,分别考虑是否有内鬼,若 \(10+i\) 和支配的鼠里有内鬼,则表明内鬼鼠编号转化为二进制后 \(2^i\) 系数为 \(1\)。把内鬼算出来然后直接把它存活状态取反即可。然后还像上面一样算出毒水即可。
记得 \(n=1/2\) 拿出来特判。
记得 cout
不知道为啥之前不用 cout
基本全 T 了。
Code
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 ---------- //
P7825 「RdOI R3」VSQ
先咕
AT2389 [AGC016E] Poor Turkeys
时光倒流 和那道 JOI 的断层思想差不多。
考虑倒推,即从最后一个人开始向前进行。
我们考虑 \((i,j)\) 最后都没被扔掉,则在前面某人为 \((x,i)\) 时,我们扔掉的一定是 \(x\)。
那么此时我们就需要保证再往前 \(x\) 不能被扔掉。
若前面出现 \((x,y)\) 且都不能被扔掉,那么 \((i,j)\) 一定不是一组解。
注意到这里每次拓展实际是一个的拓展,而非一对的拓展。
我们可以将枚举 \((i,j)\) 换成枚举 \(i\),记录下如果要留下 \(i\) 前面一定不能被扔掉的数集 \(S_i\)。
此时若 \(S_i \cap S_j=\varnothing\),则 \((i,j)\) 满足题意。模型意义下即表示 \(i\) 和 \(j\) 前面不能被扔掉的东西不重复,因为在 \(S_i\) 中所有除 \(i\) 的东西都会被扔掉,而东西不能重复扔掉。
考虑到 \(S_i\) 和 \(S_j\) 实际上只要对位完成与运算,用 bitset 存储即可。
时间复杂度 \(O(nm+\dfrac{n^3}{\omega})\)。
Code
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 ---------- //
CF446B DZY Loves Modification
贪心合并 证伪真的挺难(
一开始有个很简单的贪心想法,就是每次取行列中总和最大的那个。
然后你冲了一发,发现你挂了。
然后其实你发现行列是对互相有后效影响的。
比如当你最大值又有行又有列,你是选哪个呢?
那么考虑到行或者列自己是不影响的。
那么考虑行列分开贪心,贪心策略同上。
令 \(l_i\) 为行选了 \(i\) 个最大答案,\(r_i\) 为列。
显然有 \(ans=\max_{i=0}^k\{l_i+r_i-i\times(k-i)\times p\}\),这时你把行列拆开就可以直接算出行列之间相互影响,保证了贪心的正确性。
记得开 long long。
贪心随便拿个东西存一下就行了,我整了个 multiset,时间复杂度 \(O(k\log\max(n,m))\)。
Code
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 ---------- //