IOI 2026 国家集训队作业整理
题目整理
1. Cells Blocking
首先如果 \((1,1)\) 和 \((n,m)\) 不连通,那么方案数就是 \(\dbinom c2\),其中 \(c\) 是空地个数。
如果 \((1,1)\) 和 \((n,m)\) 本就联通,那么两个选取的点必然至少有一个在最左下路径上。考虑枚举这个点,设为 \((x,y)\),那么另一个点就是新图中 \((1,1)\) 到 \((n,m)\) 的必经点,而从 \((1,1)\) 到 \((n,m)\) 必经的点一定是最左下的路径和最右上的路径的交点。假设图仍联通,那么最右上路径不变,考虑如何找到新的最左下路径。由于每条路径 \(x+y=c\) 上只会恰好选一个点,因此找到最小的 \(k\) 使得 \((x-k,y+k)\) 仍能到达 \((1,1)\) 和 \((n,m)\),找出其到 \((1,1)\) 和 \((n,m)\) 的最左下路径并拼接起来就是新的最左下路径。和最右上路径归并即可找出所有交点。时间复杂度 \(O((n+m)^2)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=3e3+9;
int ban[N][N],n,m;
char a[N][N],lu[N][N],rd[N][N];
inline bool Valid(char c[N][N],int i,int j){return c[i][j]!='*'&&c[i][j];}
template<class T> inline int Merge(vector<T> u,vector<T> v){
int cnt=0;
while(u.size()){
while(v.size()&&v.back()>u.back()) v.pop_back();
if(v.size()&&v.back()==u.back()) cnt+=!ban[v.back()[0]][v.back()[1]];
u.pop_back();
}
return cnt;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) cin>>a[i][j];
}
int cnt=0;
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cnt+=(a[i][j]=='.');
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i][j]=='*') lu[i][j]='*';
else if(i==1&&j==1) lu[i][j]='.';
else if(Valid(lu,i,j-1)||Valid(lu,i-1,j)) lu[i][j]='~';
else lu[i][j]='*';
}
}
for(int i=n;i>=1;i--){
for(int j=m;j>=1;j--){
if(a[i][j]=='*') ;
else if(i==n&&j==m) rd[i][j]='.';
else if(Valid(rd,i,j+1)||Valid(rd,i+1,j)) rd[i][j]='~';
else rd[i][j]='*';
}
}
if(!Valid(rd,1,1)){
cout<<1ll*cnt*(cnt-1)/2<<endl;
return 0;
}
vector<array<int,2>> rp;
for(int i=1,j=1;;){
rp.push_back({i,j});
if(rd[i][j]=='.') break ;
else if(Valid(rd,i,j+1)) j++;
else if(Valid(rd,i+1,j)) i++;
}
ll ans=0,c=0;
for(int i=1,j=1;;){
int k=1;
while(i-k>=1&&j+k<=m&&(!Valid(lu,i-k,j+k)||!Valid(rd,i-k,j+k))) k++;
if(i-k<1||j+k>m) ans+=cnt-c-1;
else{
vector<array<int,2>> lp;
for(int p=i-k,q=j+k;;){
lp.push_back({p,q});
if(lu[p][q]=='.') break ;
else if(Valid(lu,p,q-1)) q--;
else if(Valid(lu,p-1,q)) p--;
}
reverse(lp.begin(),lp.end());
lp.pop_back();
for(int p=i-k,q=j+k;;){
lp.push_back({p,q});
if(rd[p][q]=='.') break ;
else if(Valid(rd,p+1,q)) p++;
else if(Valid(rd,p,q+1)) q++;
}
ans+=Merge(lp,rp);
}
c++;
ban[i][j]=1;
if(rd[i][j]=='.') break ;
else if(Valid(rd,i+1,j)) i++;
else if(Valid(rd,i,j+1)) j++;
}
cout<<ans<<endl;
return 0;
}
2. Giant Penguin
考虑建出点分树,那么一条路径要么跨过分治中心,要么跨过一个跨过分治中心的横叉边。由于这种横叉边可以和分支中心构成一个简单环,因此这种边不会超过 \(k\) 条。考虑用 BFS 预处理子树内各个节点到分治中心以及各个横叉边之间的距离,那么剩下的信息则可以动态维护,时间复杂度 \(O((n+q)k\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=2e5+9;
const int K=1e1+9;
const int lgN=2e1;
int typ[N],n,m,k,q;
vector<array<int,2>> e[N];
int vis[N];
inline void GetTE(int x){
vis[x]=1;
for(auto p:e[x]){
if(!vis[p[0]]){
typ[p[1]]=1;
GetTE(p[0]);
}
}
}
int mrk[N],siz[N];
inline void GetGrv(int x,int fa,int tot,int &grv){
bool err=0;
siz[x]=1;
for(auto p:e[x]){
if(p[0]==fa) continue ;
if(mrk[p[0]]) continue ;
if(typ[p[1]]!=1) continue ;
GetGrv(p[0],x,tot,grv);
siz[x]+=siz[p[0]];
err|=(siz[p[0]]>tot/2);
}
err|=(tot-siz[x]>tot/2);
if(!err) grv=x;
}
int col[N],cp[N];
inline void Color(int x,int fa,int c,int rt,vector<int> &v){
col[x]=c,cp[x]=rt;
v.push_back(x);
for(auto p:e[x]){
if(p[0]==fa) continue ;
if(mrk[p[0]]) continue ;
if(typ[p[1]]!=1) continue ;
Color(p[0],x,c,rt,v);
}
}
int dep[lgN][K][N],mn[N][K];
inline void BFS(int s,int *dep){
dep[s]=0;
queue<int> q;
q.push(s);
while(q.size()){
int x=q.front();
q.pop();
for(auto p:e[x]){
if(mrk[p[0]]) continue ;
if(!~typ[p[1]]) continue ;
if(dep[x]+1<dep[p[0]]){
dep[p[0]]=dep[x]+1;
q.push(p[0]);
}
}
}
}
int d[N],up[N],ec[N];
inline void Conquer(int x,int tot,int u){
GetGrv(x,-1,tot,x);
GetGrv(x,-1,tot,x);
up[x]=u,d[x]=d[u]+1;
int ccnt=0;
vector<int> sv;
col[x]=0,cp[x]=x;
for(auto p:e[x]){
if(mrk[p[0]]) continue ;
if(typ[p[1]]!=1) continue ;
Color(p[0],x,++ccnt,x,sv);
}
for(int y:sv){
for(auto p:e[y]){
if(mrk[p[0]]) continue ;
if(typ[p[1]]) continue ;
if(cp[p[0]]!=x) continue ;
if(col[p[0]]<=col[y]) continue ;
BFS(y,dep[d[x]][++ec[x]]);
typ[p[1]]=-1;
}
}
BFS(x,dep[d[x]][++ec[x]]);
mrk[x]=1;
for(auto p:e[x]){
if(mrk[p[0]]) continue ;
if(typ[p[1]]!=1) continue ;
Conquer(p[0],siz[p[0]],x);
}
}
inline void Update(int x){
for(int y=x;y;y=up[y]){
for(int i=1;i<=ec[y];i++) mn[y][i]=min(mn[y][i],dep[d[y]][i][x]);
}
}
inline int Calc(int x){
int ans=1e9;
for(int y=x;y;y=up[y]){
for(int i=1;i<=ec[y];i++) ans=min(ans,mn[y][i]+dep[d[y]][i][x]);
}
return ans;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>m>>k;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
e[u].push_back({v,i});
e[v].push_back({u,i});
}
memset(mn,0x3f,sizeof mn);
memset(dep,0x3f,sizeof dep);
GetTE(1);
Conquer(1,n,0);
cin>>q;
while(q--){
int op,x;
cin>>op>>x;
if(op==1) Update(x);
else cout<<Calc(x)<<endl;
}
return 0;
}
3. Edit Distance Yet Again
首先把 \(O(n^2)\) 的 DP 过程放到平面上考虑,那么答案就变成了 \(S=(1,1)\) 到 \(T=(n+1,m+1)\) 的最短路。
有一个关键性质是,对于 \(d(S\rightarrow(i,i+\Delta))=d(S\rightarrow(j,j+\Delta))\) 的 \(i<j\) 和 \(\Delta\),从 \((i,i+\Delta)\) 转移一定不优。
证明考虑 \((i,i+\Delta)\) 路径上的点 \((j,k)\),其中 \(k\geq j+\Delta\),那么 \(d((j,j+\Delta)\rightarrow(j,k))\leq d((i,i+\Delta)\rightarrow(j,k))\),所以 \((j,j+\Delta)\) 更优。如果不存在 \((j,k)\) 则可以用 \((k,j+\Delta)\) 类似地证明,因为 \((j,k)\) 和 \((k,j+\Delta)\) 一定至少存在一个。
将所有这样的点缩在一起之后,有效状态数就是 \(O(k^2)\) 的,原理是 \(0 \leq d(S\rightarrow (i,i+\Delta)) \leq k\),而 \(-k \leq \Delta \leq k\)。找到一组 \((d,\Delta)\) 最有用的 \(i\) 可以直接查询后缀的 LCP,一层一层地转移即可,时间复杂度 \(O((n+k^2) \log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
using ull=unsigned long long;
using bint=__int128;
const int N=2e6+9;
const int K=1e3+9;
const int lgN=2e1;
const ll mod=114514191981000001ll;
const ll base=1000;
inline void AddAs(ll &x,ll y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(ll &x,ll y){if((x-=y)<0) x+=mod;}
inline void MulAs(ll &x,ll y){x=bint(x)*y%mod;}
inline ll Add(ll x,ll y){if((x+=y)>=mod) x-=mod;return x;}
inline ll Sub(ll x,ll y){if((x-=y)<0) x+=mod;return x;}
inline ll Mul(ll x,ll y){return bint(x)*y%mod;}
inline ll QPow(ll x,ll y){
ll res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline ll Inv(ll x){return QPow(x,mod-2);}
int n,m,k;
char s[N],t[N];
ll hs[N],ht[N],b[N],ib[N];
inline void Init(int lim){
const ll ibase=Inv(base);
ib[0]=b[0]=1;
for(int i=1;i<=lim;i++){
b[i]=Mul(b[i-1],base);
ib[i]=Mul(ib[i-1],ibase);
}
}
inline void CalcHash(char *s,ll *h,int n){
for(int i=1;i<=n;i++) h[i]=Add(h[i-1],Mul(s[i],b[i-1]));
}
inline ll H(ll *h,int l,int r){return Mul(Sub(h[r],h[l-1]),ib[l-1]);}
inline int LCP(int i,int j){
int l=0,r=min(n-i+1,m-j+1)+1;
while(l+1<r){
int mid=l+r>>1;
if(H(hs,i,i+mid-1)==H(ht,j,j+mid-1)) l=mid;
else r=mid;
}
return l;
}
int cnt=0;
const int dx[3]={1,0,1};
const int dy[3]={0,1,1};
int id[K][K<<1],pre[K][K<<1];
inline ull F(unsigned x,unsigned y){return (ull(x)<<23)^(ull(x)<<13)^(ull(x)<<3)^y;}
inline ull F(array<int,2> x){return F(x[0],x[1]);}
inline void Solve(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>s[i];
for(int i=1;i<=m;i++) cin>>t[i];
Init(max(n,m));
CalcHash(s,hs,n);
CalcHash(t,ht,m);
auto T=[](array<int,2> p)->array<int,2>{
int k=LCP(p[0],p[1]);
return {p[0]+k,p[1]+k};
};
for(int i=0;i<=k;i++){
for(int j=-k;j<=k;j++) id[i][K+j]=pre[i][K+j]=-1;
}
int p=-1;
id[0][K+0]=1+LCP(1,1);
for(int r=0;r<=k;r++){
for(int d=-k;d<=k;d++){
int i=id[r][K+d],j=i-d;
if(!~i) continue ;
if(i==n+1&&j==m+1){
p=r;
break ;
}
for(int o:{0,1,2}){
if(i+dx[o]>n+1||j+dy[o]>m+1) continue ;
array<int,2> nxt=T({i+dx[o],j+dy[o]});
int u=nxt[0],v=nxt[1];
if(u>id[r+1][K+u-v]){
id[r+1][K+u-v]=u;
pre[r+1][K+u-v]=i-j;
}
}
}
if(~p) break ;
}
if(~p){
cout<<"YES"<<endl;
cout<<p<<endl;
vector<tuple<string,int,char>> ans;
for(int r=p,i=n+1,j=m+1;r>=1;r--){
int u=id[r-1][K+pre[r][K+i-j]],v=u-pre[r][K+i-j];
if(!~u) break ;
int dlt=(i-u)-(j-v);
if(dlt==0) ans.push_back({"REPLACE",v,t[v]});
else if(dlt==1) ans.push_back({"DELETE",v,'#'});
else ans.push_back({"INSERT",v,t[v]});
i=u,j=v;
}
reverse(ans.begin(),ans.end());
for(auto t:ans){
if(get<0>(t)=="DELETE") cout<<get<0>(t)<<' '<<get<1>(t)<<endl;
else cout<<get<0>(t)<<' '<<get<1>(t)<<' '<<get<2>(t)<<endl;
}
}else cout<<"NO"<<endl;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T;
cin>>T;
while(T--) Solve();
return 0;
}
4. Cactus
🌵🌵?!
首先只有一个环是经典 DP 问题。
发现把仙人掌缩成树之后相当于对非根的环上最高点 ban 掉一种颜色,即代价乘上 \(\dfrac {k}{k-1}\)。
然后合起来就🌵完了,时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=4e5+9;
const int mod=1e9+7;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
vector<int> e[N],stk;
int dfn[N],low[N],ins[N],bel[N],len[N],f[N][2],g[N],n,m,k,dcnt,scnt;
inline void Tarjan(int x,int fa){
ins[x]=1;
stk.push_back(x);
dfn[x]=low[x]=++dcnt;
for(int y:e[x]){
if(y==fa) continue ;
if(!dfn[y]){
Tarjan(y,x);
low[x]=min(low[x],low[y]);
}else if(ins[y]) low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x]){
scnt++;
while(stk.size()){
int p=stk.back();
stk.pop_back();
bel[p]=scnt;
len[scnt]++;
ins[p]=0;
if(p==x) break ;
}
}
}
inline void Solve(){
cin>>n>>m>>k;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
e[u].push_back(v);
e[v].push_back(u);
}
Tarjan(1,0);
f[1][0]=0,f[1][1]=1;
for(int i=2;i<=n;i++){
f[i][0]=Add(Mul(f[i-1][0],k-2),Mul(f[i-1][1],k-1));
f[i][1]=f[i-1][0];
}
for(int i=1;i<=n;i++) g[i]=f[i][i==1];
int ans=1;
for(int i=1;i<=scnt;i++) MulAs(ans,Mul(k-1,g[len[i]]));
MulAs(ans,Mul(k,Inv(k-1)));
cout<<ans<<endl;
dcnt=scnt=0;
for(int i=1;i<=n;i++){
e[i].clear();
dfn[i]=low[i]=ins[i]=bel[i]=len[i]=0;
}
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T;
cin>>T;
while(T--) Solve();
return 0;
}
6. Social Justice
平均值几乎没有任何性质。
首先排完序二分一下就可以找出最多剩几个人,然后对于某个最大值,可以作为最小值的位置是一个区间,找出来求并再反选即可。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=2e5+9;
using ll=long long;
int p[N],c[N],d[N],n;
ll a[N],b[N],s[N],fp,fq;
inline void Solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
cin>>fp>>fq;
iota(p+1,p+n+1,1);
sort(p+1,p+n+1,[](int i,int j){return a[i]<a[j];});
for(int i=1;i<=n;i++) s[i]=s[i-1]+(b[i]=a[p[i]]);
for(int i=1;i<=n;i++){
auto Check=[&](int p){return fp*(s[i]-s[p-1])>=b[i]*(i-p+1)*fq;};
int l=0,r=i;
while(l+1<r){
int mid=l+r>>1;
if(Check(mid)) r=mid;
else l=mid;
}
c[i]=i-r+1;
}
int t=*max_element(c+1,c+n+1);
for(int i=1,j=1;i<=n;i++){
if(c[i]==t){
auto Check=[&](int p){return fp*(s[i]-s[i-t+1]+b[p])>=b[i]*t*fq;};
int l=0,r=i-t+1;
while(l+1<r){
int mid=l+r>>1;
if(Check(mid)) r=mid;
else l=mid;
}
d[r]++,d[i+1]--;
}
}
vector<int> ans;
for(int i=1;i<=n;i++) if(!(d[i]+=d[i-1])) ans.push_back(p[i]);
sort(ans.begin(),ans.end());
cout<<ans.size()<<endl;
for(int x:ans) cout<<x<<' ';cout<<endl;
for(int i=1;i<=n+1;i++) d[i]=0;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T;
cin>>T;
while(T--) Solve();
return 0;
}
8. Travel around China
由于 \((*,l)\rightarrow (*,r)\) 一定经过 \(l\leq p\leq r\) 的 \(p\),考虑分治。
从分治中心 \(p\) 出发,计算 \((o,p)\) 到 \([l,r]\) 内所有点的最短路 \(d_o(i,j)\)。那么 \(\displaystyle \min_{o=1}^n dis_o(i_1,j_1)+dis_o(i_2,j_2)\) 即为 \((i_1,j_1)\) 到 \((i_2,j_2)\) 的最短路。这个东西的和是经典问题,可以二维偏序解决。
最短路部分可以直接 DIjkstra,注意 \(l\) 处可以从 \((1,l)\) 绕出去再从 \((3,l)\) 绕回来,需要特判,\(r\) 处同理。
时间复杂度 \(O(n^2m\log^2 m)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int M=2e5+9;
const ll inf=1e18;
const int mod=1e9+7;
ll d[3][3][M],ans;
int a[3][M],vis[3][M],n,m;
const int dx[4]={-1,0,1,0};
const int dy[4]={0,-1,0,1};
inline void Dij(int si,int sj,int l,int r,ll ld,ll rd,ll dis[3][M]){
for(int i:{0,1,2}) for(int j=l;j<=r;j++) dis[i][j]=inf,vis[i][j]=0;
dis[si][sj]=a[si][sj];
priority_queue<array<ll,3>> q;
q.push({-dis[si][sj],si,sj});
while(q.size()){
int i=q.top()[1],j=q.top()[2];
q.pop();
if(vis[i][j]) continue ;
vis[i][j]=1;
if(j==l&&(~i&1)){
int k=i^2;
if(dis[i][j]+a[k][j]+ld<dis[k][j]){
dis[k][j]=dis[i][j]+a[k][j]+ld;
q.push({-dis[k][j],k,j});
}
}
if(j==r&&(~i&1)){
int k=i^2;
if(dis[i][j]+a[k][j]+rd<dis[k][j]){
dis[k][j]=dis[i][j]+a[k][j]+rd;
q.push({-dis[k][j],k,j});
}
}
for(int o:{0,1,2,3}){
int ii=i+dx[o],jj=j+dy[o];
if(ii<0||jj<l||ii>2||jj>r) continue ;
if(dis[i][j]+a[ii][jj]<dis[ii][jj]){
dis[ii][jj]=dis[i][j]+a[ii][jj];
q.push({-dis[ii][jj],ii,jj});
}
}
}
}
struct Fenw{
int lim;
ll tr[M<<2];
inline void Add(int x,ll k){while(x<=lim) tr[x]+=k,x+=x&-x;}
inline ll Ask(int x){ll sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
inline ll Ask(int l,int r){return Ask(r)-Ask(l-1);}
inline void Clear(){while(lim) tr[lim--]=0;}
}C,S;
inline void Conquer(int l,int r,ll ld,ll rd){
if(l>r) return ;
int mid=l+r>>1;
for(int i:{0,1,2}){
Dij(i,mid,l,r,ld,rd,d[i]);
for(int j:{0,1,2}){
for(int k=l;k<=r;k++){
if(k!=mid||i<j) ans=(ans+d[i][j][k])%mod;
}
}
}
for(int i:{0,1,2}) for(int j:{0,1,2}) for(int k=r;k>mid;k--) d[i][j][k]-=a[i][mid];
for(int i:{0,1,2}){
int x=(i+1)%3,y=(i+2)%3;
vector<ll> val;
vector<array<ll,3>> lv,rv;
for(int j:{0,1,2}){
for(int k=l;k<mid;k++){
lv.push_back({(d[i][j][k]-d[x][j][k])*3+i-x,(d[i][j][k]-d[y][j][k])*3+i-y,d[i][j][k]});
}
for(int k=r;k>mid;k--){
rv.push_back({(d[x][j][k]-d[i][j][k])*3,(d[y][j][k]-d[i][j][k])*3,d[i][j][k]});
}
}
val.push_back(-inf);
for(auto &t:lv) val.push_back(t[1]);
for(auto &t:rv) val.push_back(t[1]);
sort(lv.begin(),lv.end());
sort(rv.begin(),rv.end());
sort(val.begin(),val.end());
for(auto &t:lv) t[1]=lower_bound(val.begin(),val.end(),t[1])-val.begin();
for(auto &t:rv) t[1]=lower_bound(val.begin(),val.end(),t[1])-val.begin();
C.lim=S.lim=val.size()-1;
for(int j=0,k=0;k<rv.size();k++){
while(j<lv.size()&&lv[j][0]<=rv[k][0]){
C.Add(lv[j][1],1);
S.Add(lv[j][1],lv[j][2]);
j++;
}
ans=(ans+C.Ask(rv[k][1])*rv[k][2])%mod;
ans=(ans+S.Ask(rv[k][1]))%mod;
}
C.Clear();
S.Clear();
}
ll md=d[0][2][mid];
Conquer(l,mid-1,ld,md),Conquer(mid+1,r,md,rd);
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=0;i<n;i++){
for(int j=1;j<=m;j++) cin>>a[i][j];
}
Conquer(1,m,inf,inf);
ans=(ans<<1)%mod;
cout<<ans<<endl;
return 0;
}
9. Thanks to MikeMirzayanov
把操作看成段内 reverse 再 reverse 全部,先忽略全局的 reverse,最后如果操作次数为奇数再补一次。
考虑如何排序一个 01 序列,将相同颜色的缩在一起,然后把 0 10 1 01 0 10 1 调整成 0 01 1 10 0 01 1,这样每次段数可以除以 \(3\),重新合到原序列上操作次数就是 \(O(\log_2n\log_3n)\) 的,常数小于 \(1\),可以通过。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=2e4+9;
int a[N],n;
vector<vector<int>> ans;
inline void Solve(int l,int r,int s){
if(l==r) return ;
int mid=l+r>>1;
vector<array<int,2>> cur({{a[l]>mid,1}});
for(int i=l+1;i<=r;i++){
if((a[i]>mid)==(a[i-1]>mid)) cur.back()[1]++;
else cur.push_back({!cur.back()[0],1});
}
while(cur.size()>2||cur.front()[0]){
vector<int> tmp;
vector<array<int,2>> nxt;
int f=!cur.front()[0];
for(int i=0;i<cur.size();i++){
if(i%3==(f+2)%3){
tmp.push_back(cur[i][1]);
nxt.push_back(cur[i]);
}else if(i%3==f){
if(i+1<cur.size()){
tmp.push_back(cur[i][1]+cur[i+1][1]);
nxt.push_back(cur[i+1]);
nxt.push_back(cur[i]);
}else{
tmp.push_back(cur[i][1]);
nxt.push_back(cur[i]);
}
}
}
cur.clear();
for(auto p:nxt){
if(!cur.size()||cur.back()[0]!=p[0]) cur.push_back(p);
else cur.back()[1]+=p[1];
}
if(s>=ans.size()) ans.resize(ans.size()+1);
int T=l-1-accumulate(ans[s].begin(),ans[s].end(),0);
while(T--) ans[s].push_back(1);
ans[s].insert(ans[s].end(),tmp.begin(),tmp.end());
int st=l;
for(int x:tmp){
reverse(a+st,a+st+x);
st+=x;
}
s++;
}
Solve(l,mid,s);
Solve(mid+1,r,s);
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
Solve(1,n,0);
for(auto &v:ans){
int T=n-accumulate(v.begin(),v.end(),0);
while(T--) v.push_back(1);
}
if(ans.size()&1) ans.push_back(vector<int>(n,1));
for(int i=1;i<ans.size();i+=2) reverse(ans[i].begin(),ans[i].end());
vector<int> tmp;
for(int i=ans.size();i--;) if(ans[i].size()==1) tmp.push_back(i);
for(int i:tmp) ans.erase(ans.begin()+i);
cout<<ans.size()<<endl;
for(auto &v:ans){
cout<<v.size()<<' ';
for(int x:v) cout<<x<<' ';
cout<<endl;
}
return 0;
}
10. Excluded Min
不是我操了数据结构怎么这么难啊。
首先答案显然可以转化成 \(\displaystyle \max\{v|\sum_{x\in S} [x\lt v]\geq v\}\),其中可重集 \(S=a[l:r]\)。
考虑从大到小扫描 \(v\),那么有些 \(a_i\) 的权值会从 \(1\) 变成 \(0\),对应到平面上相当于对一个 2-side 矩形减 \(1\),维护成本很高。
如果只考虑一些不相交的线段,那么相当于对一个排序后的询问区间减 \(1\),而这题有性质被包含的区间的答案一定比包含的区间的小。那么当一个线段求得答案之后把它删除再添加新的入度为 \(0\) 的线段即可做到时刻线段不相互包含。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=5e5+9;
const int inf=1e9+7;
vector<int> b[N];
int a[N],ql[N],qr[N],ans[N],p[N],ip[N],n,q;
multiset<array<int,2>> ls,rs;
namespace Fenw{
int tr[N];
inline void Add(int x,int k){while(x<=n) tr[x]+=k,x+=x&-x;}
inline int Ask(int x){int sum=0;while(x) sum+=tr[x],x&=x-1;return sum;}
inline int Ask(int l,int r){return Ask(r)-Ask(l-1);}
}
namespace SgT{
struct Node{
int tag;
array<int,2> dat;
}tr[N<<2];
inline void PushUp(int x){tr[x].dat=max(tr[x<<1].dat,tr[x<<1|1].dat);}
inline void Push(int x,int k){tr[x].dat[0]+=k,tr[x].tag+=k;}
inline void PushDown(int x){
if(tr[x].tag){
Push(x<<1,tr[x].tag);
Push(x<<1|1,tr[x].tag);
tr[x].tag=0;
}
}
inline void Build(int x,int L,int R){
if(L==R) return tr[x].dat={-inf,L},void();
int mid=L+R>>1;
Build(x<<1,L,mid),Build(x<<1|1,mid+1,R);
PushUp(x);
}
inline void Modify(int x,int L,int R,int l,int r,int k){
if(l>r) return ;
if(l<=L&&R<=r) return Push(x,k);
PushDown(x);
int mid=L+R>>1;
if(l<=mid) Modify(x<<1,L,mid,l,r,k);
if(r>mid) Modify(x<<1|1,mid+1,R,l,r,k);
PushUp(x);
}
inline void Set(int x,int L,int R,int pos,array<int,2> k){
if(L==R) return tr[x].dat=k,void();
PushDown(x);
int mid=L+R>>1;
if(pos<=mid) Set(x<<1,L,mid,pos,k);
else Set(x<<1|1,mid+1,R,pos,k);
PushUp(x);
}
}
namespace TgS{
struct Data{
int a,b,p;
Data(){}
Data(int _a,int _b,int _p){a=_a,b=_b,p=_p;}
inline friend Data operator +(Data x,Data y){
if(y.b<=x.a) y.b=-inf,y.p=-inf;
if(x.b>y.b) return Data(max(x.a,y.a),x.b,x.p);
else return Data(max(x.a,y.a),y.b,y.p);
}
};
struct Node{
Data dat=Data(-inf,-inf,-inf);
}tr[N<<2];
inline void PushUp(int x){tr[x].dat=tr[x<<1].dat+tr[x<<1|1].dat;}
inline void Set(int x,int L,int R,int pos,Data k){
if(L==R) return tr[x].dat=k,void();
int mid=L+R>>1;
if(pos<=mid) Set(x<<1,L,mid,pos,k);
else Set(x<<1|1,mid+1,R,pos,k);
PushUp(x);
}
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i],a[i]=min(a[i],n);
for(int i=1;i<=q;i++) cin>>ql[i]>>qr[i];
iota(p+1,p+q+1,1);
sort(p+1,p+q+1,[](int i,int j){
return ql[i]^ql[j]?ql[i]<ql[j]:qr[i]>qr[j];
});
for(int i=1;i<=q;i++) ip[p[i]]=i;
for(int i=1;i<=n;i++) b[a[i]].push_back(i);
SgT::Build(1,1,q);
ls.insert({-inf,0});
rs.insert({inf,q+1});
for(int i=1;i<=n;i++) Fenw::Add(i,1);
for(int i=1,j=0;i<=q;i++){
if(qr[p[i]]<=qr[p[j]]) TgS::Set(1,1,q,i,TgS::Data(-inf,qr[p[i]],i));
else{
ls.insert({ql[p[i]],i});
rs.insert({qr[p[i]],i});
TgS::Set(1,1,q,i,TgS::Data(qr[p[i]],-inf,-inf));
SgT::Set(1,1,q,i,{Fenw::Ask(ql[p[i]],qr[p[i]]),i});
j=i;
}
}
for(int v=n;~v;v--){
for(int i:b[v]){
Fenw::Add(i,-1);
int lp=(*rs.lower_bound({i,0}))[1];
int rp=(*--ls.lower_bound({i+1,0}))[1];
SgT::Modify(1,1,q,lp,rp,-1);
}
while(SgT::tr[1].dat[0]>=v){
int i=SgT::tr[1].dat[1];
ans[p[i]]=v;
ls.erase({ql[p[i]],i});
rs.erase({qr[p[i]],i});
SgT::Set(1,1,q,i,{-inf,i});
TgS::Set(1,1,q,i,TgS::Data(-inf,-inf,-inf));
while(true){
int j=TgS::tr[1].dat.p;
if(j==-inf) break ;
TgS::Set(1,1,q,j,TgS::Data(qr[p[j]],-inf,-inf));
ls.insert({ql[p[j]],j});
rs.insert({qr[p[j]],j});
SgT::Set(1,1,q,j,{Fenw::Ask(ql[p[j]],qr[p[j]]),j});
}
}
}
for(int i=1;i<=q;i++) cout<<ans[i]<<endl;
return 0;
}
11./114. Best Subsequence
Easy Version (a.k.a. 114. ~)
先二分答案 \(w\),将数按是否大于 \(\lfloor\dfrac w2\rfloor\) 分为大数和小数。
那么小数肯定选,两个小数之间就尝试选最小的大数,如果合法就加入答案,这样就是 \(O(n\log V)\) 的。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=5e5+9;
int a[N],n,k;
inline int Count(int w){
int cnt=0;
for(int i=1;i<=n;i++) if(a[i]<=(w>>1)) cnt++;
if(!cnt) return 0;
int lp=n,rp=0;
for(int i=1;i<=n;i++){
if(a[i]>(w>>1)) continue ;
if(!rp) lp=rp=i;
else{
if(rp+1<i){
int k=*min_element(a+rp+1,a+i);
if(ll(max(a[i],a[rp]))+k<=w) cnt++;
}
rp=i;
}
}
int k=2e9;
if(lp!=1) k=min(k,*min_element(a+1,a+lp));
if(rp!=n) k=min(k,*min_element(a+rp+1,a+n+1));
if(ll(max(a[lp],a[rp]))+k<=w) cnt++;
return cnt;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
int L=-1,R=2e9+1;
while(L+1<R){
int mid=ll(L)+R>>1;
if(Count(mid)>=k) R=mid;
else L=mid;
}
cout<<R<<endl;
return 0;
}
Hard Version (a.k.a. 11. ~)
考虑有多次询问怎么做。
尝试找出所有小数-大数-小数有效组合 \((i,j,k)\),这会对 \(w\in[\max(a_i,a_k)+a_j,2a_j)\) 产生 \(1\) 的贡献。由于 \((i,k)\) 中不存在 \(a_p<a_j\) 的 \(p\),因此 \(a_i,a_k\) 分别为 \(j\) 左右第一个比 \(a_j\) 小的数,单调栈找出即可。
考虑对值域建出可持久化线段树,小数的贡献是容易维护的。\((i,j,k)\) 对的考虑全部记在 \(k\) 的位置,对于询问 \([l,r]\),分别找出其最左最右的小数 \(a_L,a_R\),那么由于 \(a_L\) 是小数,那么 \(k\in(L,R]\) 的 \((i,j,k)\) 必然满足 \(l\leq L\leq i,j,k\leq R\leq r\)。剩下断环为链丢失的贡献最后补上即可。
时间复杂度 \(O(n\log V+q\log^2 V)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=1e5+9;
const int lgN=2e1;
const int inf=2e9;
int a[N],mn[lgN][N],n,q;
inline void InitRMQ(){
for(int i=1;i<=n;i++) mn[0][i]=a[i];
for(int k=1;k<=__lg(n);k++){
for(int i=1;i+(1<<k)-1<=n;i++){
mn[k][i]=min(mn[k-1][i],mn[k-1][i+(1<<k-1)]);
}
}
}
inline int Min(int l,int r){
if(l>r) return inf;
int k=__lg(r-l+1);
return min(mn[k][l],mn[k][r-(1<<k)+1]);
}
struct Node{
int lc,rc,dat;
}tr[N<<7];
int root[N],cnt;
inline int Allc(){return ++cnt;}
inline int Clone(int x){int y=Allc();tr[y]=tr[x];return y;}
inline void PushUp(int x){tr[x].dat=tr[tr[x].lc].dat+tr[tr[x].rc].dat;}
inline void Modify(int &x,int L,int R,int pos,int k){
x=Clone(x);
if(L==R) return tr[x].dat+=k,void();
int mid=ll(L)+R>>1;
if(pos<=mid) Modify(tr[x].lc,L,mid,pos,k);
else Modify(tr[x].rc,mid+1,R,pos,k);
PushUp(x);
}
inline int Query(int x,int L,int R,int l,int r){
if(!x) return 0;
if(l<=L&&R<=r) return tr[x].dat;
int mid=ll(L)+R>>1,ans=0;
if(l<=mid) ans+=Query(tr[x].lc,L,mid,l,r);
if(r>mid) ans+=Query(tr[x].rc,mid+1,R,l,r);
return ans;
}
inline int LApr(int p,int k){
int l=p-1,r=n+1;
while(l+1<r){
int mid=l+r>>1;
if(Min(p,mid)<=k) r=mid;
else l=mid;
}
return r;
}
inline int RApr(int p,int k){
int l=0,r=p+1;
while(l+1<r){
int mid=l+r>>1;
if(Min(mid,p)<=k) l=mid;
else r=mid;
}
return l;
}
inline int Count(int l,int r,int w){
int lp=LApr(l,w>>1),rp=RApr(r,w>>1),ans=0;
if(lp>r||rp<l) return ans;
ans+=Query(root[rp],0,inf,0,w)-Query(root[lp],0,inf,0,w)+1;
ans+=(ll(min(Min(l,lp-1),Min(rp+1,r)))+max(a[lp],a[rp])<=w);
return ans;
}
inline int Calc(int l,int r,int k){
int L=-1,R=inf+1;
while(L+1<R){
int mid=ll(L)+R>>1;
if(Count(l,r,mid)>=k) R=mid;
else L=mid;
}
return R;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
InitRMQ();
vector<int> stk;
for(int i=1;i<=n;i++){
Modify(root[i]=root[i-1],0,inf,a[i]<<1,1);
while(stk.size()&&a[stk.back()]>=a[i]){
if(stk.size()>1){
int j=stk.end()[-1],k=stk.end()[-2];
Modify(root[i],0,inf,a[j]+max(a[i],a[k]),1);
Modify(root[i],0,inf,a[j]<<1,-1);
}
stk.pop_back();
}
stk.push_back(i);
}
while(q--){
int l,r,k;
cin>>l>>r>>k;
cout<<Calc(l,r,k)<<endl;
}
return 0;
}
12. Binary Search Tree
首先把数扔到值域上看就是以插入时间为 prio 的笛卡尔树。
而笛卡尔树上的左父亲和右父亲分别就是前缀/后缀最小值。
那么离线下来直接楼房重建维护即可,时间复杂度 \(O(n\log^2 n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=2e5+9;
const int inf=1e9+7;
ll ans[N];
int op[N],w[N],t[N],n,q,tot;
vector<array<int,2>> opr[N];
vector<int> qry[N],val;
struct SgT{
struct Node{
int l,r;
ll mn,sum;
}tr[N<<2];
inline ll Calc(int x,int k){
if(k<=tr[x].mn) return 0;
if(tr[x].l==tr[x].r) return w[tr[x].mn];
if(k<=tr[x<<1|1].mn) return Calc(x<<1,k);
else return Calc(x<<1|1,k)+tr[x].sum-tr[x<<1|1].sum;
}
inline void PushUp(int x){
tr[x].mn=min(tr[x<<1].mn,tr[x<<1|1].mn);
tr[x].sum=tr[x<<1|1].sum+Calc(x<<1,tr[x<<1|1].mn);
}
inline void Build(int x,int l,int r){
tr[x].l=l,tr[x].r=r;
if(tr[x].l==tr[x].r) return tr[x].mn=q+1,void();
int mid=tr[x].l+tr[x].r>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
PushUp(x);
}
inline void Set(int x,int pos,int k){
if(tr[x].l==tr[x].r) return tr[x].mn=k,tr[x].sum=w[k],void();
int mid=tr[x].l+tr[x].r>>1;
if(pos<=mid) Set(x<<1,pos,k);
else Set(x<<1|1,pos,k);
PushUp(x);
}
inline array<ll,2> Prefix(int x,int pos,ll k){
if(pos<tr[x].l) return {0,q+1};
if(tr[x].l==tr[x].r) return {Calc(x,k),min(k,tr[x].mn)};
int mid=tr[x].l+tr[x].r>>1;
if(pos<=mid) return Prefix(x<<1,pos,k);
else{
auto res=Prefix(x<<1|1,pos,k);
return {res[0]+Calc(x<<1,res[1]),min(res[1],tr[x<<1].mn)};
}
}
}T,R;
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>q;
for(int i=1,l,r,x;i<=q;i++){
cin>>op[i];
if(op[i]==1){
cin>>l>>r>>w[i];
opr[l].push_back({w[i],i});
opr[r+1].push_back({w[i],q+1});
}else{
cin>>x>>w[i];
qry[x].push_back(i);
}
val.push_back(w[i]);
}
val.push_back(0);
sort(val.begin(),val.end());
val.erase(unique(val.begin(),val.end()),val.end());
tot=val.size()-1;
w[q+1]=inf;
T.Build(1,1,tot);
R.Build(1,1,tot);
for(int i=1;i<=tot;i++) t[i]=q+1;
for(int i=1;i<=n;i++){
for(auto p:opr[i]){
p[0]=lower_bound(val.begin(),val.end(),p[0])-val.begin();
T.Set(1,p[0],p[1]);
R.Set(1,tot-p[0]+1,p[1]);
t[p[0]]=p[1]+1;
}
for(int j:qry[i]){
int p=lower_bound(val.begin(),val.end(),w[j])-val.begin();
ans[j]=T.Prefix(1,p,min(j,t[p]))[0]+R.Prefix(1,tot-p,min(j,t[p]))[0];
}
}
for(int i=1;i<=q;i++) if(op[i]==2) cout<<ans[i]<<endl;
return 0;
}
13. Game
选手注意力涣散咋办。
考虑 \((1,n)\) 中最大值的决策,设其位置为 \(x\),那么对于 \(x\) 来说,如果不在 \(x\) 停留,最后在 \((1,n)\) 停留肯定不优,因此只能赌跑到一个 \(1\) 或 \(n\) 处的较大值,那么此时的期望 \(E(x)=(1-P(x))A_1+P(x)A_n\),其中 \(P(x)\) 表示从 \(x\) 出发停留在 \(n\) 的概率。
可以证明的是,\(P(x)=\dfrac {x-1}{n-1}\)。考虑列出 \(P\) 的关系式,即 \(P(x)=\dfrac {P(x-1)+P(x+1)}2\),经过简单变形即可发现 \(P\) 是等差数列,带入边界 \(P(1)=0,P(n)=1\) 即可得出 \(P(x)=\dfrac{x-1}{n-1}\)。因此,若 \(A_x\gt \dfrac {n-x}{n-1} A_1+\dfrac {x-1}{n-1} A_n\),则 \(x\) 也是必选点,那么可以对 \([1,x]\) 和 \([x,n]\) 做相同的事,否则答案就是 \(\displaystyle \frac 1n\sum_{x=1}^nE(x)\)。
不难观察到,上面的答案等同于 \(\dfrac 1n\) 倍的凸包面积,直接求出即可,时间复杂度 \(O(n)\)。
当然也可以直接钦定必选点 \(1=x_1\lt x_2\lt \ldots \lt x_m=n\),观察答案 \(\displaystyle \frac 1{2n}(A_1+\sum_{i=1}^{m-1}(A_{x_i}+A_{x_{i+1}})(x_{i+1}-x_i)+A_n)\) 得出结论。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=5e5+9;
const int mod=998244353;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
int n;
ll a[N];
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
vector<int> stk;
for(int i=1;i<=n;i++){
while(stk.size()>1){
int j=stk.end()[-1],k=stk.end()[-2];
if((a[i]-a[j])*(j-k)>=(a[j]-a[k])*(i-j)) stk.pop_back();
else break ;
}
stk.push_back(i);
}
int ans=0;
for(int i=0;i+1<stk.size();i++) AddAs(ans,Mul((a[stk[i]]+a[stk[i+1]])%mod,stk[i+1]-stk[i]));
AddAs(ans,(a[stk.front()]+a[stk.back()])%mod);
MulAs(ans,Inv(Mul(2,n)));
cout<<ans<<endl;
return 0;
}
14. Local Maxima
考虑从大到小填规避掉限制,那么相当于每次只能填和之前填的同行或者同列的。
设 \(f_{i,j}\) 表示已经填了 \(i\) 行 \(j\) 列共 \(ij\) 个交叉点,有转移:
- \(\displaystyle f_{i+1,j} \leftarrow (n-i)\binom{nm-ij-1}{j-1}j!f_{i,j}\)。
- \(\displaystyle f_{i,j+1}\leftarrow (m-j)\binom{nm-ij-1}{i-1}i!f_{i,j}\)。
初值有 \(f_{1,1}=1\),答案就是 \(f_{n,m}\)。
事实上有更加高明的做法, 考虑二项式反演,钦定有 \(i\) 个最大值,令恰有 \(k\) 个局部最大值的答案为 \(f_k\),则:
-
设 \(\displaystyle a_i=n+m-2i,s_i=\sum_{j=1}^ia_j\)。
-
\(\displaystyle f_k=\sum_{i=k}^{\min(n,m)}(-1)^{i-k}\binom{i}{k}\binom{nm}{s_i+i}(nm-s_i-i)!\binom ni\binom mi(i!)^2\prod_{j=1}^i\binom{s_j+j-1}{a_j}a_j!\)。
\(f_1\) 即为所求。
时间复杂度均为 \(O(nm)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=3e3+9;
int mod;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
int fac[N*N],ifac[N*N];
inline void Init(int lim){
fac[0]=1;
for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
ifac[lim]=Inv(fac[lim]);
for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
if(m<0||m>n) return 0;
else return Mul(fac[n],Mul(ifac[m],ifac[n-m]));
}
int f[N][N],n,m;
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>m>>mod;
Init(n*m);
f[1][1]=n*m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
AddAs(f[i+1][j],Mul(f[i][j],Mul(n-i,Mul(C(n*m-i*j-1,j-1),fac[j]))));
AddAs(f[i][j+1],Mul(f[i][j],Mul(m-j,Mul(C(n*m-i*j-1,i-1),fac[i]))));
}
}
cout<<f[n][m]<<endl;
return 0;
}
17. Knowledge Is...
悔悔!?
相当于尽量匹配无交的区间。把所有区间按 \(L_i\) 排序,维护待定集合和已匹配集合,因为左端点有序,每次匹配待定的 \(R_i\) 最小的。如果没有则匹配以匹配的靠右的区间右端点最小的,换出来一个右端点更小的。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=3e5+9;
int l[N],r[N],b[N],c[N],n,m;
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>l[i]>>r[i];
vector<int> p(n);
iota(p.begin(),p.end(),1);
sort(p.begin(),p.end(),[](int i,int j){return l[i]<l[j];});
priority_queue<array<int,2>> q1,q2;
for(int i:p){
if(q1.size()&&l[i]>-q1.top()[0]){
int j=q1.top()[1];
q1.pop();
b[i]=j,b[j]=i;
q2.push({-r[i],i});
}else if(q2.size()&&r[i]>-q2.top()[0]){
int j=q2.top()[1],k=b[j];
q2.pop();
b[i]=k,b[k]=i,b[j]=0;
q2.push({-r[i],i});
q1.push({-r[j],j});
}else q1.push({-r[i],i});
}
for(int i=1;i<=n;i++) if(!c[i]&&b[i]&&m) c[i]=c[b[i]]=m--;
for(int i=1;i<=n;i++) if(!c[i]&&m) c[i]=m--;
for(int i=1;i<=n;i++) cout<<c[i]<<' ';cout<<endl;
return 0;
}
27. AND Permutation
要深刻反思了。
考虑从低到高归纳构造,先将原集合 \(S\) 按当前位分成 \(S_0\) 和 \(S_1\) 并消去当前位,递归下去构造。还原 \(S_1\) 时会出现当前位均为 \(1\) 而造成有交的情况,考虑到 \(S_1\) 在 \(S_0\) 中一定有一个对应的值,还原后交换匹配对象即可。
时间复杂度 \(O(n\log n)\),比较懒写了 map,时间复杂度 \(O(n\log^2 n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=(1<<18)+9;
int n;
ll a[N];
map<ll,ll> b;
inline void Construct(vector<ll> v,int d){
if(d<0||!v.size()) return ;
vector<ll> s[2];
for(ll x:v) s[x>>d&1].push_back(x);
Construct(s[0],d-1);
Construct(s[1],d-1);
for(ll x:s[1]){
swap(b[x],b[x^(1ll<<d)]);
b[x^(1ll<<d)]|=1ll<<d;
}
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i],b[a[i]]=0;
Construct(vector<ll>(a+1,a+n+1),60);
for(int i=1;i<=n;i++) cout<<b[a[i]]<<endl;
return 0;
}
31. Joke
首先对 \(p,q\) 做 \(p^{-1}\) 的置换使得 \(p=e\),其次不难得出 \(s\) 的充要条件是 \(\forall i<j,q_i>q_j,s_i=1\implies s_j=0\)。
考察最大值,设序列由 \(L,M,R\) 三部分组成,其中 \(M\) 是最大值,设 \(f(A)\) 表示序列 \(A\) 的答案。
那么如果最大值填 \(1\),后面的都得填 \(0\),否则无事发生,因此可以得出 \(f(LMR)=f(L)+f(LR)\)。
而 \(q\) 的上升子序列个数 \(g(q)\) 转移式也恰为 \(g(LMR)=g(L)+g(LR)\)。
因此答案就是 \(q\) 的上升子序列个数,设 \(f_{i,j,k}\) 表示考虑前 \(i\) 个,选了 \(j\) 个 \(q_i=0\),结尾为 \(k\) 的方案数,前缀和优化 DP 可以做到 \(O(n^3)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=1e2+9;
const int mod=998244353;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int ans=1;
while(y){
if(y&1) MulAs(ans,x);
MulAs(x,x);
y>>=1;
}
return ans;
}
inline int Inv(int x){return QPow(x,mod-2);}
int fac[N],ifac[N];
inline void Init(int lim){
fac[0]=1;
for(int i=1;i<=lim;i++) fac[i]=Mul(fac[i-1],i);
ifac[lim]=Inv(fac[lim]);
for(int i=lim-1;~i;i--) ifac[i]=Mul(ifac[i+1],i+1);
}
inline int C(int n,int m){
if(m<0||m>n) return 0;
else return Mul(fac[n],Mul(fac[m],fac[n-m]));
}
int p[N],q[N],f[N][N][N],g[N],vis[N],n;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i];
for(int i=1;i<=n;i++) cin>>q[i];
for(int i=1;i<=n;i++){
while(p[i]!=i) swap(q[i],q[p[i]]),swap(p[i],p[p[i]]);
}
Init(n);
f[0][0][0]=1;
for(int i=1;i<=n;i++) if(q[i]) vis[q[i]]=1;
for(int i=0;i<=n;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<=n;k++){
if(q[i+1]){
AddAs(f[i+1][j][k],f[i][j][k]);
if(q[i+1]>k) AddAs(f[i+1][j][q[i+1]],f[i][j][k]);
}else{
AddAs(f[i+1][j][k],f[i][j][k]);
AddAs(g[k+1],f[i][j][k]);
}
}
for(int k=0;k<=n;k++){
if(k+1<=n) AddAs(g[k+1],g[k]);
if(!vis[k]) AddAs(f[i+1][j+1][k],g[k]);
g[k]=0;
}
}
}
int m=n-accumulate(vis+1,vis+n+1,0),ans=0;
for(int j=0;j<=n;j++){
for(int k=0;k<=n;k++) AddAs(ans,Mul(f[n][j][k],fac[m-j]));
}
cout<<ans<<endl;
return 0;
}
36. Nein
首先直接计算答案看起来不是很好做,考虑求解第 \(n\) 大的不包含 \(9\) 的 \(10^k-1\) 的倍数。
由于 \(10^k-1\) 有循环溢出的性质,所以 \(10^k-1\) 的倍数按每 \(k\) 个数分一段加起来的和一定也是 \(10^k-1\) 的倍数,而且假设共有 \(B\) 段,那么这个数位和一定不会大于 \(B(10^k-1)\)。
考虑从高到低依次确定答案每一位,那么需要计算确定一个前缀之后的合法方案数。令前面已经填出来的数位和是 \(-r\pmod{10^k-1}\),则后面填出来的数位和就是 \(r+t(10^k-1)\),其中 \(t\in[0,B]\)。因为 \(t\) 很小,所以直接枚举 \(r+t(10^k-1)\) 是什么,然后就可以直接数位 DP。具体地,设 \(g_{i,j}\) 表示目前填到数位和的第 \(i\) 位,并从下面接受了 \(j\) 次进位的方案数。那么有转移 \(\displaystyle g_{i,j}=\sum_{k=0}^B g_{i-1,k}f_{c_{i-1},10j+s_{i-1}-k}\),其中 \(\displaystyle f_{i,j}=(\sum_{t=0}^8x^t)^i[x^j]\),\(c_i\) 表示后缀中下标模 \(k\) 与 \(i\) 同余的位的个数,\(s_i\) 则是 \(r+t(10^k-1)\) 的第 \(i\) 位,方案数即为 \(\displaystyle g_{k,\lfloor \frac {r+t(10^k-1)}{10^k}\rfloor}\)。
时间复杂度 \(O(9kT^4)\),其中 \(T=37\)。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
using bint=__int128;
const int D=4e2;
const bint T=1e18;
ll k,n;
bint f[D][D<<3],g[D][D],p10[D];
inline bint Count(int t,bint lft,vector<int> &cnt){
if(!t) return !lft;
vector<int> trg(k+1);
for(int i=0;i<k;i++) trg[i]=lft%10,lft/=10;
g[0][0]=1;
for(int i=0;i<k;i++){
for(int p=0;p<=t;p++){
for(int q=0;q<=t;q++){
if(p*10+trg[i]-q>=0) g[i+1][p]+=g[i][q]*f[cnt[i]][p*10+trg[i]-q];
}
}
}
bint ans=g[k][lft];
for(int i=0;i<=k;i++){
for(int p=0;p<=t;p++) g[i][p]=0;
}
return ans;
}
signed main(){
cin>>k>>n;
f[0][0]=1,p10[0]=1;
for(int i=1;i<=37;i++){
for(int j=0;j<=i*8;j++){
for(int k=j;k>=max(j-8,0);k--) f[i][j]+=f[i-1][k];
}
}
for(int i=1;i<=37;i++) p10[i]=p10[i-1]*10;
bint ans=0,s9=0;
for(int i=0;i<k;i++) s9=s9*10+9;
for(int i=37;~i;i--){
int t=(i+k-1)/k;
vector<int> cnt(k,0);
for(int j=0;j<i;j++) cnt[j%k]++;
for(int x=0;x<9;x++){
bint sum=-!ans;
for(int c=0;c<=t;c++){
bint lft=c*s9+(s9-ans%s9)%s9;
sum+=Count(t,lft,cnt);
}
if(n<=sum) break ;
n-=sum,ans+=p10[i];
}
}
ans/=s9;
if(ans<T) cout<<ll(ans)<<endl;
else cout<<ll(ans/T)<<setw(18)<<setfill('0')<<ll(ans%T)<<endl;
return 0;
}
41. Link Cut Digraph
经典永流传。
考虑整体二分第 \(i\) 条边能够加入到某个 SCC 的时间 \(t_i\),原先全部暂定 \(t_i=i\)。那么二分 \([l,r]\) 的时候,\(t_i\in(r,+\infty]\) 的边肯定不会起到任何作用,而 \([1,l)\) 的边则已经被缩完了,于是可以被表达成 SCC 内部的边,即缩完点之后全部在 SCC 内部,也不会起到作用。因此二分时仅需加入 \(t_i\in[l,mid]\) 的边,若某条边没能成功加入一个 SCC,则 \(t_i\leftarrow mid+1\)。继续整体二分即可。在跑右边之前需要把同属一个 SCC 的点缩在一起,顺带维护一下 SCC 大小。
时间复杂度 \(O(m\log m)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=3e5+9;
ll ans[N];
int ep[N],eq[N],t[N],n,m;
ll cur;
int fa[N],siz[N];
inline int Find(int x){return x==fa[x]?x:fa[x]=Find(fa[x]);}
inline void Merge(int x,int y){
x=Find(x),y=Find(y);
if(x==y) return ;
if(siz[x]<siz[y]) swap(x,y);
cur-=1ll*siz[x]*(siz[x]-1)/2;
cur-=1ll*siz[y]*(siz[y]-1)/2;
fa[y]=x;
siz[x]+=siz[y];
cur+=1ll*siz[x]*(siz[x]-1)/2;
}
vector<int> e[N],stk;
int dfn[N],low[N],ins[N],bel[N],dcnt;
inline void Tarjan(int x){
ins[x]=1,stk.push_back(x);
dfn[x]=low[x]=++dcnt;
for(int y:e[x]){
if(!dfn[y]){
Tarjan(y);
low[x]=min(low[x],low[y]);
}else if(ins[y]) low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x]){
while(stk.size()){
int p=stk.back();
stk.pop_back();
ins[p]=0;
bel[p]=x;
if(p==x) break ;
}
}
}
inline void BinSch(int l,int r,vector<int> &v){
int mid=l+r>>1;
vector<int> node;
for(int i:v){
if(t[i]>mid) continue ;
e[Find(ep[i])].push_back(Find(eq[i]));
node.push_back(Find(ep[i]));
node.push_back(Find(eq[i]));
}
for(int x:node) if(!dfn[x]) Tarjan(x);
auto Clear=[&](){
for(int x:node){
dfn[x]=low[x]=ins[x]=bel[x]=0;
e[x].clear();
}
dcnt=0;
};
if(l==r){
for(int x:node) Merge(x,bel[x]);
ans[l]=cur;
Clear();
}else{
vector<int> lv,rv;
for(int i:v){
if(t[i]>mid) rv.push_back(i);
else{
if(bel[Find(ep[i])]==bel[Find(eq[i])]) lv.push_back(i);
else t[i]=mid+1,rv.push_back(i);
}
}
Clear();
BinSch(l,mid,lv);
BinSch(mid+1,r,rv);
}
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>ep[i]>>eq[i];
for(int i=1;i<=n;i++) fa[i]=i,siz[i]=1;
for(int i=1;i<=m;i++) t[i]=i;
vector<int> v(m);
iota(v.begin(),v.end(),1);
BinSch(1,m,v);
for(int i=1;i<=m;i++) cout<<ans[i]<<endl;
return 0;
}
44. AND PLUS OR
不是我跟你们这群高智商人群拼了。
考虑把答案的 \(S\) 和 \(T\) 调整成比较好看的形式。
结论:如果 \(A\) 有解,那么一定存在一组 \(S,T\) 使得 \((S,T)\) 是合法答案且 \(|S\oplus T|=2\)。
证明:
显然 \(|S\oplus T|\lt 2\) 肯定不合法。
设 \((S_0,T_0)\) 为 \(A\) 的一组 \(|S_0\oplus T_0|\) 最小的解,并假设 \(|S\setminus T|<|T\setminus S|\neq 1\)。
令 \(P=S\cap T,Q=S\setminus T,R=T\setminus S,x\in R,R'=R/\{x\}\)。
那么 \((P\cup Q\cup R',P \cup R' \cup \{x\})\) 和 \((P\cup Q,P \cup R')\) 不合法,但 \((P\cup Q,P\cup R'\cup \{x\})\) 合法,即:
- \(A(P\cup Q)+A(P\cup R'\cup \{x\}) \lt A(P)+A(P\cup Q\cup R'\cup \{x\})\)。
- \(A(P\cup Q\cup R')+A(P \cup R' \cup \{x\}) \geq A(P\cup R')+A(P\cup Q\cup R'\cup \{x\})\)。
- \(A(P\cup Q)+A(P \cup R')\geq A(P)+A(P\cup Q\cup R')\)。
容易发现二式加三式与一式矛盾。
因此可以说明 \((P\cup Q\cup R',P \cup R' \cup \{x\})\) 和 \((P\cup Q,P \cup R')\) 至少有一组合法,从而可以不断把 \(|T\setminus S|\) 调整到 \(1\)。
对 \(|S\setminus T|\) 做同样的操作即可令 \(|S\oplus T|=|S\setminus T|+|T\setminus S|=2\)。
\(|S\oplus T|=2\) 的 \((S,T)\) 只有 \(O(n^22^n)\) 对,枚举找出答案即可。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=(1<<20)+9;
int a[N],n;
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;
for(int i=0;i<(1<<n);i++) cin>>a[i];
for(int sta=0;sta<(1<<n);sta++){
for(int i=0;i<n;i++){
for(int j=i+1;j<n;j++){
int tta=sta^(1<<i)^(1<<j);
if(a[sta]+a[tta]<a[sta&tta]+a[sta|tta]){
cout<<sta<<' '<<tta<<endl;
return 0;
}
}
}
}
cout<<-1<<endl;
return 0;
}
53. Good Coloring
给每条边按颜色大小从小到大定向,跑最长路,按最长路染色即可。
证明显然,最长路显然不会大于 \(k\),如果两个相邻的点相同显然不是最长路。
由于是 DAG 可以直接拓扑排序,时间复杂度 \(O(n+m)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=6e5+9;
int c[N],n,m,k;
vector<int> e[N];
int dis[N],pre[N],in[N];
inline void LPFA(){
queue<int> q;
for(int i=1;i<=n;i++){
dis[i]=pre[i]=0;
if(!in[i]) q.push(i);
}
while(q.size()){
int x=q.front();
q.pop();
for(int y:e[x]){
if(dis[x]+1>dis[y]){
pre[y]=x;
dis[y]=dis[x]+1;
}
if(!--in[y]) q.push(y);
}
}
}
inline void Solve(){
cin>>n>>m>>k;
for(int i=1;i<=n;i++) cin>>c[i];
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
if(c[u]>c[v]) e[u].push_back(v),in[v]++;
else e[v].push_back(u),in[u]++;
}
LPFA();
int p=max_element(dis+1,dis+n+1)-dis;
cout<<dis[p]+1<<' ';
for(int i=1;i<=n;i++) cout<<dis[i]+1<<' ';cout<<endl;
while(p) cout<<p<<' ',p=pre[p];cout<<endl;
for(int i=1;i<=n;i++) e[i].clear();
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T;
cin>>T;
while(T--) Solve();
return 0;
}
61. Easy Problem
呃呃呃啊啊啊。
首先考虑 Hall 定理,然后这个东西怎么就变成矩形加全局 max 了,我咋不会。
设当前取到的位置是 \(x\),发现所有区间全部交于 \(x\) 处,所以考虑分成左半右半刻画。
对于槽 \((l_i,r_i,c_i)\) ,将其额度按 \(\lt x\) 和 \(\geq x\) 分成 \(L_i,R_i\),有 \(L_i+R_i=c_i\)。\(x\) 从左向右扫时,\(L_i\) 一定递增,原因显然。
根据 Hall 定理,可以得出答案就是 \(\displaystyle \min_{p\leq x} (\sum_{i=p}^{x-1} a_i+\sum_{l_i\lt p} L_i)+\min_{p\geq x-1} (\sum_{i=x}^p a_i+\sum_{r_i\gt p} R_i)\),考虑贪心地调整 \(L_i,R_i\),每次找到 \(_i\) 最小且不会影响到右侧取值的 \(i\) 调整其 \(L_i,R_i\)。由于这是匹配,因此如果右侧可以取到某个上界则一定存在最优方案使得右侧取到上界。调整每次要么将 \(R_i\) 置为 \(0\),要么将右侧最小值向左移,设势能为线段树上左儿子最小值大于右儿子最小值的节点数,每次线段树操作至多增加 \(O(\log n)\) 的是能,最小值左移减少至少 \(1\) 的势能,总复杂度 \(O(n\log ^2 n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=1e5+9;
const int inf=1e9+7;
struct RARMQ{
struct Node{
int l,r;
ll tag;
pair<ll,int> dat;
}tr[N<<2];
inline void PushUp(int x){tr[x].dat=min(tr[x<<1].dat,tr[x<<1|1].dat);}
inline void Push(int x,ll k){tr[x].dat.first+=k,tr[x].tag+=k;}
inline void PushDown(int x){
if(tr[x].tag){
Push(x<<1,tr[x].tag);
Push(x<<1|1,tr[x].tag);
tr[x].tag=0;
}
}
inline void Build(int x,int l,int r){
tr[x].l=l,tr[x].r=r,tr[x].tag=0;
if(tr[x].l==tr[x].r) return tr[x].dat={0,l},void();
int mid=l+r>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
PushUp(x);
}
inline void Modify(int x,int l,int r,ll k){
if(l>r) return ;
if(l<=tr[x].l&&tr[x].r<=r) return Push(x,k);
PushDown(x);
int mid=tr[x].l+tr[x].r>>1;
if(l<=mid) Modify(x<<1,l,r,k);
if(r>mid) Modify(x<<1|1,l,r,k);
PushUp(x);
}
inline pair<ll,int> Query(int x,int l,int r){
if(l>r) return {inf,0};
if(l<=tr[x].l&&tr[x].r<=r) return tr[x].dat;
PushDown(x);
int mid=tr[x].l+tr[x].r>>1;
if(r<=mid) return Query(x<<1,l,r);
else if(l>mid) return Query(x<<1|1,l,r);
else return min(Query(x<<1,l,r),Query(x<<1|1,l,r));
}
}L,R;
struct PSRMQ{
struct DelHeap{
priority_queue<array<int,2>> p,q;
inline void Insert(array<int,2> x){p.push(x);}
inline void Erase(array<int,2> x){q.push(x);}
inline array<int,2> Top(){
while(q.size()&&p.top()==q.top()) p.pop(),q.pop();
return p.top();
}
inline void Clear(){
while(p.size()) p.pop();
while(q.size()) q.pop();
}
}h[N];
struct Node{
int l,r;
array<int,2> dat;
}tr[N<<2];
inline void PushUp(int x){tr[x].dat=min(tr[x<<1].dat,tr[x<<1|1].dat);}
inline void Build(int x,int l,int r){
tr[x].l=l,tr[x].r=r;
if(tr[x].l==tr[x].r){
h[l].Clear();
h[l].Insert({-inf,0});
tr[x].dat={inf,0};
return ;
}
int mid=l+r>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
PushUp(x);
}
inline void Update(int x,int pos){
if(tr[x].l==tr[x].r){
tr[x].dat={-h[pos].Top()[0],h[pos].Top()[1]};
return ;
}
int mid=tr[x].l+tr[x].r>>1;
if(pos<=mid) Update(x<<1,pos);
else Update(x<<1|1,pos);
PushUp(x);
}
inline array<int,2> Query(int x,int l,int r){
if(l<=tr[x].l&&tr[x].r<=r) return tr[x].dat;
int mid=tr[x].l+tr[x].r>>1;
if(r<=mid) return Query(x<<1,l,r);
else if(l>mid) return Query(x<<1|1,l,r);
else return min(Query(x<<1,l,r),Query(x<<1|1,l,r));
}
}P;
ll ans[N];
vector<int> st[N],ed[N];
int a[N],l[N],r[N],c[N],lc[N],rc[N],n,m;
inline void Solve(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=m;i++){
cin>>l[i]>>r[i]>>c[i];
if(!c[i]) continue ;
st[l[i]].push_back(i);
ed[r[i]].push_back(i);
}
L.Build(1,0,n+1),R.Build(1,0,n+1),P.Build(1,0,n+1);
for(int i=1;i<=n;i++) R.Modify(1,i,n,a[i]);
for(int x=1;x<=n;x++){
for(int i:st[x]){
lc[i]=0,rc[i]=c[i];
P.h[r[i]].Insert({-l[i],i});
P.Update(1,r[i]);
R.Modify(1,0,r[i]-1,c[i]);
}
while(true){
auto p=R.Query(1,x-1,n);
auto q=P.Query(1,x-1,p.second);
if(!q[1]) break ;
int i=q[1];
ll tmp=min(ll(rc[i]),R.Query(1,x-1,r[i]-1).first-p.first);
lc[i]+=tmp,rc[i]-=tmp;
L.Modify(1,l[i]+1,n+1,tmp);
R.Modify(1,0,r[i]-1,-tmp);
if(!rc[i]){
P.h[r[i]].Erase({-l[i],i});
P.Update(1,r[i]);
}
}
ans[x]=L.Query(1,1,x).first+R.Query(1,x-1,n).first;
for(int i:ed[x]){
if(rc[i]){
P.h[r[i]].Erase({-l[i],i});
P.Update(1,r[i]);
}
L.Modify(1,l[i]+1,n+1,-lc[i]);
R.Modify(1,0,r[i]-1,-rc[i]);
}
R.Modify(1,x,n,-a[x]);
L.Modify(1,1,x,a[x]);
}
for(int i=1;i<=n;i++) cout<<ans[i]<<' ';cout<<endl;
for(int i=1;i<=n;i++){
st[i].clear();
ed[i].clear();
}
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T;
cin>>T;
while(T--) Solve();
return 0;
}
65. Building Bombing
\(L\) 之前的点没啥用,对于 \(L\) 后面的点设 \(f_{i,j}\) 表示使 \(i\) 成为 \(L\) 后面第 \(j\) 小的要操作几次,有转移:
- \(\displaystyle f_{i,j}=\min_{L\leq p<i} (f_{p,j-1}+\sum_{q=p+1}^i[h_q>h_p])\)。
这显然是方便用区间加区间最小值值域线段树维护的,时间复杂度 \(O(nk\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=1e5+9;
const int inf=1e9+7;
struct Node{
int l,r,dat,tag;
}tr[N<<2];
inline void PushUp(int x){tr[x].dat=min(tr[x<<1].dat,tr[x<<1|1].dat);}
inline void Push(int x,int k){tr[x].dat+=k,tr[x].tag+=k;}
inline void PushDown(int x){
if(tr[x].tag){
Push(x<<1,tr[x].tag);
Push(x<<1|1,tr[x].tag);
tr[x].tag=0;
}
}
inline void Build(int x,int l,int r){
tr[x].l=l,tr[x].r=r,tr[x].tag=0;
if(tr[x].l==tr[x].r) return tr[x].dat=inf,void();
int mid=tr[x].l+tr[x].r>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
PushUp(x);
}
inline void Set(int x,int pos,int k){
if(tr[x].l==tr[x].r) return tr[x].dat=min(tr[x].dat,k),void();
PushDown(x);
int mid=tr[x].l+tr[x].r>>1;
if(pos<=mid) Set(x<<1,pos,k);
else Set(x<<1|1,pos,k);
PushUp(x);
}
inline void Modify(int x,int l,int r,int k){
if(l>r) return ;
if(l<=tr[x].l&&tr[x].r<=r) return Push(x,k);
PushDown(x);
int mid=tr[x].l+tr[x].r>>1;
if(l<=mid) Modify(x<<1,l,r,k);
if(r>mid) Modify(x<<1|1,l,r,k);
PushUp(x);
}
inline int Query(int x,int l,int r){
if(l>r) return inf;
if(l<=tr[x].l&&tr[x].r<=r) return tr[x].dat;
PushDown(x);
int mid=tr[x].l+tr[x].r>>1,ans=inf;
if(l<=mid) ans=min(ans,Query(x<<1,l,r));
if(r>mid) ans=min(ans,Query(x<<1|1,l,r));
return ans;
}
int a[N],f[N],n,p,k;
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>p>>k;
for(int i=1;i<=n;i++) cin>>a[i];
a[++n]=inf;
int ans=0;
for(int i=1;i<p;i++) if(a[i]>=a[p]) ans++;
vector<int> val(a,a+n+1);
sort(val.begin(),val.end());
val.erase(unique(val.begin(),val.end()));
for(int i=1;i<=n;i++) a[i]=lower_bound(val.begin(),val.end(),a[i])-val.begin();
f[p]=0;
for(int i=p+1;i<=n;i++) f[i]=inf;
for(int o=1;o<=k;o++){
Build(1,1,n);
for(int i=p;i<=n;i++){
int tmp=f[i];
f[i]=Query(1,1,a[i]-1);
Modify(1,1,a[i]-1,1);
Set(1,a[i],tmp);
}
}
ans+=f[n];
cout<<(ans<inf/2?ans:-1)<<endl;
return 0;
}
66. Routes
我操我怎么花了整整两天时间意识到自己是个傻逼。
以下规定 \(d(u,v),f(p,q),g(u,p)\) 分别表示点 \(u\) 到点 \(v\),颜色 \(p\) 到颜色 \(q\),点 \(u\) 到颜色 \(p\) 的最短路,\(c_u\) 表示点 \(u\) 的颜色。
首先显然有 \(f(c_u,p)\leq g(u,p)\leq f(c_u,p)+1\),从而有 \(f(c_u,c_v)\leq d(u,v)\leq f(c_u,c_v)+2\)。
先假设所有点对都经过飞机转移过之上一次,后面可以再花 \(O(nk)\) 的时间修正,因为单组答案显然存在上界 \(2k-1\)。
考虑枚举 \(u\rightarrow v\) 中经过的中转颜色,即有 \(\displaystyle d(u,v)=\min_p g(u,p)+g(v,p)+1=0/1/2+\min_pf(c_u,p)+f(p,c_v)+1\)。
由于颜色数是极少的,考虑枚举 \(c_u,c_v\),对 \(d(u,v)=0/1/2+f(c_u,c_v)\) 分别计数。
设掩码 \(\displaystyle s_u=\{p|g(u,p)=f(c_u,p)\},m_\Delta=\{p|f(c_u,p)+f(p,c_v)+\Delta=f(c_v,c_v)\}\):
- 那么 \(d(u,v)=f(c_u,c_v)\) 的条件就是 \(\displaystyle s_u \cap s_v\cap m_0\neq \varnothing\)。考虑枚举 \(c_u\) 和 \(v\),以及预处理出 \(s_u\) 的高维前缀和,则满足条件的 \((u,v)\) 对数总体可以在 \(O(k^22^k+nk)\) 的时间计算。
- 类似地 \(d(u,v)=f(c_u,c_v)+1\) 的条件等价于 \(\displaystyle (s_u \oplus s_v)\cap m_0\neq \varnothing\) 或 \(\displaystyle s_u \cap s_v\cap m_1\neq \varnothing\)。这个不是很好求,考虑从 \(d(u,v)=f(c_u,c_v)+0/2\) 容斥得出。
- 而 \(d(u,v)=f(c_u,c_v)+2\) 的条件是 \(\displaystyle (s_u \cup s_v)\cap m_0 = \varnothing\) 且 \(\displaystyle s_u \cap s_v\cap m_1 = \varnothing\)。这个可以和 \(d(u,v)=f(c_u,c_v)\) 类似的通过位运算技巧在相同的时间复杂度内计算得到。
时间复杂度 \(O(k^3+nk+k^22^k)\),代码看起来因为内存访问的原因常数很大。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=1e6+9;
const int K=2e1+9;
const int S=(1<<16)+9;
const int inf=1e9+7;
string s[N];
int st[N],n,m,k;
vector<int> p[K];
vector<array<int,2>> e[N+K];
int bel[N],d[K][N+K],c[K][K],v[K][S],sta[N],m1[K][K],m2[K][K];
inline void BFS(int s,int *dis){
deque<int> q;
vector<int> vis(n+k+1,0);
for(int i=1;i<=n+k;i++) dis[i]=inf;
dis[s]=0;
q.push_back(s);
while(q.size()){
int x=q.front();
q.pop_front();
if(vis[x]) continue ;
vis[x]=1;
for(auto p:e[x]){
if(dis[x]+p[1]<dis[p[0]]){
dis[p[0]]=dis[x]+p[1];
if(!p[1]) q.push_front(p[0]);
else q.push_back(p[0]);
}
}
}
}
inline int Dist(int x,int y){
if(bel[x]==bel[y]) return x!=y;
if(bel[x]>bel[y]) swap(x,y);
if(m1[bel[x]][bel[y]]&(sta[x]&sta[y])) return c[bel[x]][bel[y]];
else if(m1[bel[x]][bel[y]]&(sta[x]|sta[y])) return c[bel[x]][bel[y]]+1;
else if(m2[bel[x]][bel[y]]&(sta[x]&sta[y])) return c[bel[x]][bel[y]]+1;
else return c[bel[x]][bel[y]]+2;
}
inline ll Solve(){
cin>>n>>m>>k;
st[0]=1;
for(int i=1;i<=m;i++){
cin>>s[i];
st[i]=st[i-1]+s[i-1].size();
for(int j=0;j<s[i].size();j++){
if(j){
e[st[i]+j].push_back({st[i]+j-1,1});
e[st[i]+j-1].push_back({st[i]+j,1});
}
bel[st[i]+j]=s[i][j]-'a'+1;
e[st[i]+j].push_back({n+bel[st[i]+j],1});
e[n+bel[st[i]+j]].push_back({st[i]+j,0});
}
}
for(int i=1;i<=k;i++) BFS(n+i,d[i]);
for(int i=1;i<=k;i++){
for(int j=1;j<=k;j++) c[i][j]=d[i][n+j]-(i!=j);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
sta[i]|=(d[j][i]==c[j][bel[i]])<<j-1;
}
v[bel[i]][sta[i]]++;
p[bel[i]].push_back(i);
}
ll ans=0;
for(int i=1;i<=k;i++){
ans+=1ll*p[i].size()*(p[i].size()-1)/2;
for(int j=0;j<k;j++){
for(int t=0;t<(1<<k);t++) if(~t>>j&1) v[i][t|(1<<j)]+=v[i][t];
}
for(int j=i+1;j<=k;j++){
for(int t=1;t<=k;t++){
m1[i][j]|=(c[i][j]==c[i][t]+c[t][j]+1)<<t-1;
m2[i][j]|=(c[i][j]+1==c[i][t]+c[t][j]+1)<<t-1;
}
for(int x:p[j]){
ll c0=p[i].size()-v[i][(1<<k)-1^m1[i][j]&sta[x]];
ll c2=v[i][(1<<k)-1^m1[i][j]^m2[i][j]&sta[x]];
ll c1=p[i].size()-c0-c2;
ans+=c0*c[i][j]+c1*(c[i][j]+1)+c2*(c[i][j]+2);
}
}
}
for(int i=1;i<=m;i++){
for(int j=0;j<s[i].size();j++){
for(int t=max(0,j-2*k);t<j;t++){
ans-=Dist(st[i]+t,st[i]+j);
ans+=min(Dist(st[i]+t,st[i]+j),j-t);
}
}
}
for(int i=1;i<=k;i++){
for(int j=1;j<=n+k;j++) d[i][j]=0;
for(int j=0;j<(1<<k);j++) v[i][j]=0;
for(int j=1;j<=k;j++) c[i][j]=m1[i][j]=m2[i][j]=0;
p[i].clear(),p[i].shrink_to_fit();
}
for(int i=1;i<=n+k;i++) e[i].clear(),e[i].shrink_to_fit();
for(int i=1;i<=n;i++) bel[i]=sta[i]=0;
for(int i=1;i<=m;i++) s[i].clear(),s[i].shrink_to_fit();
return ans;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T;
cin>>T;
for(int o=1;o<=T;o++) cout<<"Case #"<<o<<": "<<Solve()<<endl;
return 0;
}
67. One, Two, Three
呃呃首先因为是 \((1,2,3)\) 和 \((3,2,1)\),所以肯定 \(2\) 是特殊的。
考虑先让 \((1,3),(3,1)\) 匹配成区间,最后再往里面放 \(2\)。假设选了 \(x\) 对 \((1,3)\) 以及 \(y\) 对 \((3,1)\),那么处于贪心的目的,用于匹配的理应是前 \(x\) 个 \(1\) 前 \(y\) 个 \(3\),以及后 \(y\) 个 \(1\) 后 \(x\) 个 \(3\)。同时我们也可以通过调整使得 \((1,3)\) 对之间,\((3,1)\) 对之间互相不构成包含关系,而答案不变。
令 \(\displaystyle c_{k}(l,r)=\sum_{i=l}^r[a_i=k],s_k(p)=c_k(1,p)\)。
由于要对 \(2\) 匹配,考虑 Hall 定理,设 \(f(l,r),g(l,r)\) 分别表示 \([l,r]\) 内 \((1,3)\) 对和 \((3,1)\) 对的数量:
- \(f(l,r)+g(l,r)\leq c_2(l,r)\)。
- \(f(l,r)=\max(x-c_1(1,l-1)-c_3(r+1,n),0)\)。
- \(g(l,r)=\max(y-c_3(1,l-1)-c_1(r+1,n),0)\)。
二三式代入一式拆 max 可以得到 \(4\) 条限制:
- \(0 \leq c_2(l,r)\)。
- \(x \leq c_2(l,r)+c_1(1,l-1)+c_3(r+1,n)\)。
- \(y\leq c_2(l,r)+c_3(1,l-1)+c_1(r+1,n)\)。
- \(x+y\leq c_2(l,r)+c_1(1,l-1)+c_3(r+1,n)+c_3(1,l-1)+c_1(r+1,n)\)。
将 \(c_k\) 拆成 \(s_k\) 之后可以得到 \(l,r\) 对 \(x,y,x+y\) 的限制,对有关 \(l\) 的式子求前缀最值可以得到 \(x,y,x+y\) 的上界,根据 Hall 定理,随便找一组 \((x_0,y_0)\) 就是对的。
构造解考虑将所有 \((1,3)\) 和 \((3,1)\) 对抽出来,对所有 \(2\) 从左到右优先匹配右端点最左的即可。
时间复杂度 \(O(n\log n)\),瓶颈在于构造解。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=6e5+9;
const int inf=1e9+7;
int a[N],rk[N],cnt[4][N],p[4][N],n;
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int x=inf,y=inf,xy=inf,lx=inf,ly=inf,lxy=inf;
for(int i=1;i<=n;i++){
for(int j:{1,2,3}) cnt[j][i]=cnt[j][i-1];
p[a[i]][rk[i]=++cnt[a[i]][i]]=i;
}
x=y=xy=min(cnt[1][n],min(cnt[2][n],cnt[3][n]));
for(int i=1;i<=n;i++){
lx=min(lx,-cnt[2][i-1]+cnt[1][i-1]);
ly=min(ly,-cnt[2][i-1]+cnt[3][i-1]);
lxy=min(lxy,-cnt[2][i-1]+cnt[1][i-1]+cnt[3][i-1]);
x=min(x,lx+cnt[2][i]-cnt[3][i]+cnt[3][n]);
y=min(y,ly+cnt[2][i]-cnt[1][i]+cnt[1][n]);
xy=min(xy,lxy+cnt[2][i]-cnt[3][i]+cnt[3][n]-cnt[1][i]+cnt[1][n]);
}
xy=min(xy,x+y),x=min(x,xy),y=xy-x;
cout<<x+y<<endl;
priority_queue<array<int,2>> q;
for(int i=1;i<=n;i++){
if(a[i]==1){
if(rk[i]<=x) q.push({-p[3][cnt[3][n]-(x-rk[i]+1)+1],-i});
}else if(a[i]==3){
if(rk[i]<=y) q.push({-p[1][cnt[1][n]-(y-rk[i]+1)+1],-i});
}else if(a[i]==2){
if(q.size()){
cout<<-q.top()[1]-1<<' '<<i-1<<' '<<-q.top()[0]-1<<endl;
q.pop();
}
}
}
return 0;
}
68. Lonely King
首先最优缩边方式肯定是直接缩到叶子,因此整体形态类似对边进行剖分。
设 \(f_x(k)\) 表示经过 \(x\) 连出去的边连到的点点权为 \(k\) 时答案最小值,有转移:\(\displaystyle f_x(k)=\min_{x\rightarrow y}( f_y(k)+\sum_{z\neq y,x\rightarrow z}f_z(c_x)\)。
那么显然地,\(f_x(k)\) 是下凸的,至此可以使用直接一些手法直接维护。
考虑对每个节点使用李超线段树维护 \(f_x(k)\),需要的操作是全局减和合并,可以直接打 tag,也可以记录偏差值。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=2e5+9;
const int inf=1e9+7;
const ll lnf=1e18;
const int V=1e6;
struct Line{
ll k,b;
Line(){}
Line(ll _k,ll _b){k=_k,b=_b;}
inline ll operator ()(ll x){return k*x+b;}
};
struct Node{
ll tag;
int lc,rc;
Line f=Line(inf,lnf);
}tr[N<<6];
int cnt;
inline int Allc(){return ++cnt;}
inline void Push(int &x,ll k){
if(!x) return ;
tr[x].f.b+=k,tr[x].tag+=k;
}
inline void PushDown(int x){
if(tr[x].tag){
Push(tr[x].lc,tr[x].tag);
Push(tr[x].rc,tr[x].tag);
tr[x].tag=0;
}
}
inline bool Beat(int L,int R,Line f,Line g){return f(L)<g(L)&&f(R)<g(R);}
inline bool NoUse(int L,int R,Line f,Line g){return f(L)>=g(L)&&f(R)>=g(R);}
inline bool Invalid(Line f){return f.k>=inf/10||f.b>=lnf/10;}
inline void Update(int &x,int L,int R,int l,int r,Line f){
if(!x) x=Allc();
if(l<=L&&R<=r){
if(L<R) PushDown(x);
if(Beat(L,R,f,tr[x].f)) tr[x].f=f,tr[x].tag=0;
else if(L<R){
int mid=L+R>>1;
if(!NoUse(L,mid,f,tr[x].f)) Update(tr[x].lc,L,mid,l,r,f);
if(!NoUse(mid+1,R,f,tr[x].f)) Update(tr[x].rc,mid+1,R,l,r,f);
}
return ;
}
PushDown(x);
int mid=L+R>>1;
if(l<=mid) Update(tr[x].lc,L,mid,l,r,f);
if(r>mid) Update(tr[x].rc,mid+1,R,l,r,f);
}
inline ll Min(int x,int L,int R,int pos){
if(!x) return lnf;
if(L==R) return tr[x].f(pos);
PushDown(x);
int mid=L+R>>1;
if(pos<=mid) return min(tr[x].f(pos),Min(tr[x].lc,L,mid,pos));
else return min(tr[x].f(pos),Min(tr[x].rc,mid+1,R,pos));
}
inline void Merge(int &x,int y,int L,int R){
if(!y||Invalid(tr[y].f)) return ;
if(!x||Invalid(tr[x].f)) return x=y,void();
Update(x,L,R,L,R,tr[y].f);
if(L==R) return ;
int mid=L+R>>1;
PushDown(x),PushDown(y);
Merge(tr[x].lc,tr[y].lc,L,mid);
Merge(tr[x].rc,tr[y].rc,mid+1,R);
}
vector<int> e[N];
int c[N],fa[N],root[N],n;
inline void Solve(int x){
if(!e[x].size()){
Update(root[x],1,V,1,V,Line(c[x],0));
}else{
ll sum=0;
for(int y:e[x]){
Solve(y);
ll t=Min(root[y],1,V,c[x]);
sum+=t;
Push(root[y],-t);
Merge(root[x],root[y],1,V);
}
Push(root[x],sum);
}
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n;
for(int i=2;i<=n;i++) cin>>fa[i],e[fa[i]].push_back(i);
for(int i=1;i<=n;i++) cin>>c[i];
if(n==1){
cout<<0<<endl;
return 0;
}
Solve(1);
cout<<Min(root[1],1,V,c[1])<<endl;
return 0;
}
69. Beautiful Sequence
首先答案可以刻画成 \(\displaystyle n-\sum_{i=1}^{n-1}[a_i\neq a_{i+1}]+[a_{i-1}>a_i<a_{i+1}]\),原理是相邻的不同值小的那个肯定倒闭了,但是一个点不会倒闭两次。
那么假设当前有 \(c\) 个一样的数,调整法可知,基本上除了填成 \(c\) 个长度为 \(1\) 的谷段或填 \(1\) 个长度为 \(c\) 的段以外均不优。特别地,填成 \(c\) 个长度为 \(1\) 的谷段有 \(1\) 的额外贡献。不从上述式子刻画这点则没那么显然。
考虑从大到小填数,那么填 \(c\) 个长度为 \(1\) 的谷段相当于合并 \(c+1\) 个连续段,而填 \(1\) 个长度为 \(c\) 的段至多可以新增一个连续段,最后连续段有多个直接合并成一个即可。
因此问题转化为,给定序列 \(a_i\),求 \(\sum x_i\) 最大值使得 \(\displaystyle \forall i,x_i\in\{0,1\},\sum_{j=1}^i x_ia_i\leq i\)。考虑直接贪心,从 \(i-1\) 推到 \(i\) 的时候,能直接加入 \(a_i\) 肯定直接加入 \(a_i\) 了, 考虑无法加入 \(a_i\) 肯定是因为 \(i-1\) 的 \(\sum x_ia_i\) 太大了,因此每次贪心考虑把 \(\sum x_ia_i\) 当作第二关键字,如果无法直接加入就尝试把 \(i-1\) 的答案中最大值换掉,不难发现这样不影响答案正确性。
时间复杂度 \(O(n\log n)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=3e5+9;
int a[N],c[N],n,m;
inline void Solve(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
vector<int> val(a,a+n+1);
sort(val.begin(),val.end());
val.erase(unique(val.begin(),val.end()),val.end());
m=val.size()-1;
for(int i=1;i<=n;i++){
c[lower_bound(val.begin(),val.end(),a[i])-val.begin()]++;
}
int ans=n-m+1,sum=0;
priority_queue<int> q;
for(int i=m;i>=1;i--){
q.push(c[i]+1),sum+=c[i]+1,ans++;
if(sum>=m-i+1) sum-=q.top(),ans--,q.pop();
c[i]=0;
}
cout<<ans<<endl;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int T;
cin>>T;
while(T--) Solve();
return 0;
}
73. Sets May Be Good
考虑把答案刻画成:\(\displaystyle \sum_{x\in\Z_2^n} (\sum_{(i,j)\in E} x_ix_j)\bmod 2\),这是二次型,令 \(A\) 为原图的邻接矩阵,那么该式等同于 \(\displaystyle \sum_{x\in\Z_2^n} x^TAx\bmod 2\)。
考虑将变换 \(R\) 作用于 \(x\),使得 \(x\) 仍然能够取遍 \(\Z_2^n\),即:\(\displaystyle \sum_{x\in\Z_2^n} (Rx)^TA(Rx)\bmod 2=\sum_{x\in\Z_2^n} x^T(R^TAR)x\bmod 2\)。
这说明把 \(A\) 变换到 \(R^TAR\) 答案仍旧不变,组合意义就是施加基本行变换之后再施加相同的基本列变换。
因此考虑消元:
-
首先,先将 \(A_{1,i}\) 翻折到 \(A_{i,1}\) 上,即 \(A_{i,1}\leftarrow A_{1,i}+A_{i,1}\),\(A_{1,i}\leftarrow 0\)。
-
然后找到行 \(i\) 使得 \(A_{i,1}\neq 0\),将其与第 \(2\) 行交换,同时交换第 \(2\) 列与第 \(i\) 列。
-
之后,对于第 \(3\) 行及以后的所有 \(A_{i,1}\neq 0\) 的行 \(i\),把第 \(2\) 行与第 \(i\) 行相消,即 \(A_{i,j}\leftarrow A_{i,j}+A_{2,j}\)。同时把第 \(2\) 列加到第 \(i\) 列上。
-
此时第 \(1\) 列只有 \(A_{1,1},A_{2,1}\) 有值,第 \(1\) 行只有 \(A_{1,1}\) 有值,递归地消元即可。
最后只有 \(A_{i,i},A_{i+1,i}\) 有值,那么此时形态就是一条有自环的链,可以通过简单的 DP 解决。
消元可以用 bitset 加以优化,时间复杂度 \(O(\dfrac {n^3}w)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=1e3+9;
const int mod=998244353;
inline void AddAs(int &x,int y){if((x+=y)>=mod) x-=mod;}
inline void SubAs(int &x,int y){if((x-=y)<0) x+=mod;}
inline void MulAs(int &x,int y){x=1ll*x*y%mod;}
inline int Add(int x,int y){if((x+=y)>=mod) x-=mod;return x;}
inline int Sub(int x,int y){if((x-=y)<0) x+=mod;return x;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int QPow(int x,int y){
int res=1;
while(y){
if(y&1) MulAs(res,x);
MulAs(x,x);
y>>=1;
}
return res;
}
inline int Inv(int x){return QPow(x,mod-2);}
int f[N][2][2],n,m;
bitset<N> a[N];
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
a[u].flip(v);
}
for(int i=1;i<n;i++){
for(int j=i+1;j<=n;j++) a[j][i]=a[j][i]^a[i][j],a[i][j]=0;
int p=i+1;
while(p<=n&&!a[p][i]) p++;
if(p>n) continue ;
else if(p!=i+1){
swap(a[i+1],a[p]);
for(int j=1;j<=n;j++){
a[j][i+1]=a[j][p]^a[j][i+1];
a[j][p]=a[j][p]^a[j][i+1];
a[j][i+1]=a[j][p]^a[j][i+1];
}
}
bitset<N> b;
for(int j=i+2;j<=n;j++){
if(!a[j][i]) continue ;
a[j]^=a[i+1];
b.set(j);
}
for(int j=1;j<=n;j++) if(a[j][i+1]) a[j]^=b;
}
f[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int s:{0,1}){
for(int p:{0,1}){
for(int q:{0,1}){
AddAs(f[i][s^(q&a[i][i])^(p&q&a[i][i-1])][q],f[i-1][s][p]);
}
}
}
}
int ans=0;
for(int p:{0,1}) AddAs(ans,f[n][0][p]);
cout<<ans<<endl;
return 0;
}
75. Fast Spanning Tree
考虑折半报警器,把每个限制剩余的量拆成两半分给两个连通块,这样就是 \(O(n\log^2 m+n\log m\log V)\) 的。
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int N=3e5+9;
ll wsu[N];
int fa[N],u[N],v[N],lim[N],n,m;
priority_queue<int> td;
priority_queue<pair<ll,int>> cn[N];
inline int Find(int x){return fa[x]==x?x:fa[x]=Find(fa[x]);}
inline void Upd(int x){
while(cn[x].size()&&wsu[x]>=-cn[x].top().first){
td.push(-cn[x].top().second);
cn[x].pop();
}
}
inline void Merge(int x,int y){
x=Find(x),y=Find(y);
if(x==y) return ;
if(cn[x].size()<cn[y].size()) swap(x,y);
fa[y]=x;
wsu[x]+=wsu[y];
while(cn[y].size()) cn[x].push(cn[y].top()),cn[y].pop();
Upd(x);
}
vector<int> ans;
inline bool Work(){
while(td.size()){
int i=-td.top();
td.pop();
if(Find(u[i])==Find(v[i])) continue ;
if(wsu[Find(u[i])]+wsu[Find(v[i])]>=lim[i]){
Merge(u[i],v[i]);
ans.push_back(i);
}else{
int dlt=lim[i]-(wsu[Find(u[i])]+wsu[Find(v[i])])+1>>1;
cn[Find(u[i])].push({-(dlt+wsu[Find(u[i])]),i});
cn[Find(v[i])].push({-(dlt+wsu[Find(v[i])]),i});
}
}
return 0;
}
signed main(){
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>wsu[i],fa[i]=i;
for(int i=1;i<=m;i++){
cin>>u[i]>>v[i]>>lim[i];
int dlt=lim[i]+1>>1;
cn[u[i]].push({-dlt,i});
cn[v[i]].push({-dlt,i});
}
for(int i=1;i<=n;i++) Upd(i);
while(Work()) ;
cout<<ans.size()<<endl;
for(int x:ans) cout<<x<<' ';cout<<endl;
return 0;
}
77. Honorable Mention
首先以 \(k\) 为自变量,\([l,r]\) 的 \(k\) 子段和是凸的,感性理解就是如果两个较大区间有交就可以直接合并,所以一定是从大到小选出若干段连续区间,然后将这些区间分裂,最后再选一些负的单点,代数证明是容易的。
建立静态的线段树维护这个凸包,对于每次询问,考虑 wqs 二分斜率切凸包,以答案为第一关键字,段数为第二关键字 DP 选取最大值,时间复杂度 \(O(n\log^2 n\log V)\)。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
using ll=long long;
const int N=5e4+9;
const int V=1e9;
const ll inf=1e18,hnf=1e12;
int a[N],n,q;
struct Node{
int l,r;
vector<ll> f[2][2],df[2][2];
}tr[N<<2];
inline vector<ll> Shift(vector<ll> f,int k){
f.erase(f.begin(),f.begin()+k);
return f;
}
inline vector<ll> Merge(vector<ll> f,vector<ll> g){
adjacent_difference(f.begin(),f.end(),f.begin());
adjacent_difference(g.begin(),g.end(),g.begin());
vector<ll> h(f.size()+g.size()-1);
h[0]=f[0]+g[0];
merge(f.begin()+1,f.end(),g.begin()+1,g.end(),h.begin()+1,greater<ll>());
partial_sum(h.begin(),h.end(),h.begin());
return h;
}
inline void ChMax(vector<ll> &f,vector<ll> g){
int len=max(f.size(),g.size());
f.resize(len,LLONG_MIN);
g.resize(len,LLONG_MIN);
for(int i=0;i<len;i++) f[i]=max(f[i],g[i]);
}
inline void DF(int x){
for(int i:{0,1}){
for(int j:{0,1}){
tr[x].df[i][j].resize(tr[x].f[i][j].size());
adjacent_difference(tr[x].f[i][j].begin(),tr[x].f[i][j].end(),tr[x].df[i][j].begin());
tr[x].df[i][j][0]=LLONG_MAX;
}
}
}
inline void PushUp(int x){
for(int i:{0,1}){
for(int j:{0,1}){
for(int k:{0,1}){
ChMax(tr[x].f[i][j],Shift(Merge(tr[x<<1].f[i][k],tr[x<<1|1].f[k][j]),k));
}
}
}
DF(x);
}
inline void Build(int x,int l,int r){
tr[x].l=l,tr[x].r=r;
if(tr[x].l==tr[x].r){
for(int i:{0,1}) for(int j:{0,1}) tr[x].f[i][j]={-(i|j)*hnf,a[l]};
DF(x);
return ;
}
int mid=tr[x].l+tr[x].r>>1;
Build(x<<1,l,mid),Build(x<<1|1,mid+1,r);
PushUp(x);
}
struct Data{
array<ll,2> f[2][2];
friend inline Data operator +(Data x,Data y){
Data z;
for(int i:{0,1}){
for(int j:{0,1}){
z.f[i][j]={LLONG_MIN,0};
for(int k:{0,1}){
z.f[i][j]=max(z.f[i][j],{x.f[i][k][0]+y.f[k][j][0],x.f[i][k][1]+y.f[k][j][1]-k});
}
}
}
return z;
}
};
inline Data GetData(int x,ll dlt){
Data t;
for(int i:{0,1}){
for(int j:{0,1}){
int p=upper_bound(tr[x].df[i][j].begin(),tr[x].df[i][j].end(),dlt,greater<ll>())-tr[x].df[i][j].begin()-1;
t.f[i][j]={(i||j)&&!p||!~p?-inf:tr[x].f[i][j][p]-p*dlt+i*dlt,p};
}
}
return t;
}
inline Data Query(int x,int l,int r,ll dlt){
if(l<=tr[x].l&&tr[x].r<=r) return GetData(x,dlt);
int mid=tr[x].l+tr[x].r>>1;
if(r<=mid) return Query(x<<1,l,r,dlt);
else if(l>mid) return Query(x<<1|1,l,r,dlt);
else return Query(x<<1,l,r,dlt)+Query(x<<1|1,l,r,dlt);
}
inline ll Calc(int l,int r,int k){
ll L=-V,R=V;
while(L+1<R){
ll mid=L+R>>1;
if(Query(1,l,r,mid).f[0][0][1]>=k) L=mid;
else R=mid;
}
return Query(1,l,r,L).f[0][0][0]+1ll*L*k;
}
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
cin>>n>>q;
for(int i=1;i<=n;i++) cin>>a[i];
Build(1,1,n);
while(q--){
int l,r,k;
cin>>l>>r>>k;
cout<<Calc(l,r,k)<<endl;
}
return 0;
}
80. Bit Component
首先考虑格雷码,然而发现 \(n=13\) 也有解,考察形态:
oooooo
oooo oo
oooo oo
oo ooooo
可以看出,方法是将次高位和最高位联通,然后下面则通过一个 110...001 和之前格雷码中 100...001 联通,然后刨去最高位和次高位再填一遍小一号的格雷码,有重的放在一起即可,最后 110...000 收尾,再补上漏的 010...001。
同时也可以证明有解的集合就是 \(\mathbb{Z}\cap\displaystyle \bigcup _{k}(3\times2^{k-2},2^k)\) 。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
const int N=(1<<18)+9;
int a[N];
signed main(){
cin.tie(0),cout.tie(0);
ios::sync_with_stdio(0);
int n;
cin>>n;
int k=__lg(n);
for(int i=0;i<=k;i++){
for(int j=(1<<i);j<(1<<i+1);j++){
a[j]=(a[j^(1<<i)]^a[(1<<i)-1])|(1<<i);
}
}
if(n==(1<<k+1)-1){
cout<<"YES"<<endl;
for(int i=1;i<=n;i++) cout<<a[i]<<' ';cout<<endl;
}else if(k<3) cout<<"NO"<<endl;
else if(n<=((1<<k)|(1<<k-1))) cout<<"NO"<<endl;
else{
cout<<"YES"<<endl;
for(int i=1;i<(1<<k)-1;i++) cout<<a[i]<<' ';
for(int i=1;i<(1<<k-1);i++){
if((a[i]|(1<<k)|(1<<k-1))<=n) cout<<(a[i]|(1<<k)|(1<<k-1))<<' ';
cout<<(a[i]|(1<<k))<<' ';
}
cout<<(1<<k)<<' ';
cout<<((1<<k)|(1<<k-1))<<' ';
cout<<(1<<k-1)<<' ';
cout<<endl;
}
return 0;
}

浙公网安备 33010602011771号