NOIP 模拟 17
A 镜的绮想
直接做,不过如果 \(n=2e5\) 咋做?
B 万物有灵
倒着选一定最优,然后每 \(K\) 层是一个周期,为了避免分讨,使 \(K\gets 2K\),写完贡献的式子是等比数列,但是这题卡逆元,所以用矩阵加速或者倍增求和即可。
C 白石溪
\(n^2\) DP 容易想到,但是无论如何都需要石子数量的状态,整个是 2D/0D,没有可以优化的地方。
发现这个东西推一下贡献的式子很有前途,先考虑一个红石子的贡献,\(f(i)=a_i+d(i-pre_i)\),其中 \(pre_i\) 表示 \([1,i]\) 中与 \(i\) 同色的数量,对于蓝石子同理,简单化简一下石子,所有红石子的贡献就是 \(\sum a_i+d(i-pre_i)=\sum a_i+d\sum i-\sum pre_i\),发现后面这一项已经跟 \(i\) 没关系了,只需要考虑数量即可,所以可以先全涂成蓝色,然后把一些位置改成蓝色,改 \(i\) 位置的贡献是 \(a_i-b_i+di-ci-num_{red}+num_{blue}\),\(num\) 是实时更改的,与前面没关系,所以直接贪心的按 \(a_i-b_i+di-ci\) 排序即可。
D 上山岗
容易得出数量,考虑每一位的答案,肯定是在保证数量的时候尽可能大,但是越大对后面的影响就越大,所以讨论一下这个能不能匹配上后二分答案即可,时间复杂度为 \(\mathcal{O}(n^2\log n)\)。
发现上面的东西还是很有前途的,不过每次二分后需要 \(\mathcal{O}(n)\) 的 check
很不好,所以思考怎样快速得出田忌赛马问题的答案。
田忌赛马问题本质上是二分图匹配,求最大匹配数自然可以想到 \(\text{Hall}\) 定理,根据 \(\text{Hall}\) 的推广得出:
- 将山看做 \(1\),人看做 \(-1\),将山和人一起排序后最大匹配数为 \(n\) 加上最小前缀和。
最小前缀和可以使用线段树维护,这时二分能做到 \(\mathcal{O}(n\log^2n)\),直接在线段树上二分就能做到 \(\mathcal{O}(n\log n)\)。
#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
#define fi first
#define se second
#define pii std::pair<int,int>
#define eb emplace_back
#define pb push_back
typedef long long ll;
typedef unsigned long long ull;
std::mt19937 myrand(std::chrono::high_resolution_clock::now().time_since_epoch().count());
inline int R(int n){return myrand()%n+1;}
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=1e6+10,inf=1e9;
inline void Min(int &x,int y){if(x>y)x=y;}
inline void Max(int &x,int y){if(x<y)x=y;}
int n,LEN,v[N],h[N],a[N],pos[2][N];
pii s[N];
struct NODE{int tag,min,num;}t[N<<2];
inline void pushdown(int p){int &x=t[p].tag;t[ls].tag+=x;t[ls].min+=x;t[rs].tag+=x;t[rs].min+=x;x=0;}
inline void update(int p){t[p].min=std::min(t[ls].min,t[rs].min);t[p].num=t[ls].num+t[rs].num;}
inline void build(int p,int l,int r){
if(l==r){t[p].min=v[l],t[p].num=s[l].se>n;return;}
int mid=l+r>>1;build(ls,l,mid);build(rs,mid+1,r);update(p);
}
inline void change(int p,int l,int r,int L,int R,int x){
if(l>=L&&r<=R){t[p].tag+=x,t[p].min+=x;return ;}int mid=l+r>>1;if(t[p].tag)pushdown(p);
if(L<=mid)change(ls,l,mid,L,R,x);if(R>mid)change(rs,mid+1,r,L,R,x);update(p);
}
inline void _change(int p,int l,int r,int pos){
if(l==r)return t[p].num=0,void();if(t[p].tag)pushdown(p);int mid=l+r>>1;
if(pos<=mid)_change(ls,l,mid,pos);else _change(rs,mid+1,r,pos);update(p);
}
inline int query(int p,int l,int r,int lim){
if(!t[p].num)return -1;if(l==r)return t[p].min>=lim-1?l:-1;
if(t[p].tag)pushdown(p);int mid=l+r>>1;
if(t[ls].min<lim)return query(ls,l,mid,lim);
int res=query(rs,mid+1,r,lim);
return res>0?res:query(ls,l,mid,lim);
}
signed main(){
freopen("uphill.in","r",stdin);freopen("uphill.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read();for(int i=1;i<=n;++i)h[i]=read()+1,s[i]={h[i],i};LEN=2*n;
for(int i=1;i<=n;++i)a[i]=read(),s[i+n]={a[i],i+n};
std::sort(s+1,s+LEN+1);
for(int i=1;i<=LEN;++i)
if(s[i].se<=n)v[i]=v[i-1]+1,pos[0][s[i].se]=i;
else v[i]=v[i-1]-1,pos[1][s[i].se-n]=i;
build(1,1,LEN);
for(int i=1,tot=n+t[1].min;i<=n;++i){
change(1,1,LEN,pos[0][i],LEN,-1);
int p=query(1,1,LEN,tot-1-(n-i));
if(p<pos[0][i])p=query(1,1,LEN,tot-(n-i));else tot--;
change(1,1,LEN,p,LEN,1);_change(1,1,LEN,p);std::cout<<s[p].fi<<' ';
}std::cout<<'\n';
}
总结
打的不行,T3 想到推下贡献的式子但是没去做,T4 二分都没想到,菜,T2 的暴力没有赋初值,都什么时候了还在这种地方挂分。